Merge with master.

This commit is contained in:
Nicholas Staples
2016-01-19 09:49:01 +00:00
48 changed files with 686 additions and 327 deletions

View File

@@ -1,7 +1,7 @@
import os
import re
from flask import Flask, session, Markup, render_template
from flask import Flask, session, Markup, escape, render_template
from flask._compat import string_types
from flask.ext.sqlalchemy import SQLAlchemy
from flask_login import LoginManager
@@ -47,6 +47,7 @@ def create_app(config_name, config_overrides=None):
application.add_template_filter(placeholders)
application.add_template_filter(replace_placeholders)
application.add_template_filter(nl2br)
application.after_request(useful_headers_after_request)
register_errorhandlers(application)
@@ -108,6 +109,14 @@ def placeholders(value):
))
def nl2br(value):
_paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', Markup('<br>\n'))
for p in _paragraph_re.split(escape(value)))
return Markup(result)
def replace_placeholders(template, values):
if not template:
return template

View File

@@ -0,0 +1,58 @@
(function(Modules) {
"use strict";
Modules.ApiKey = function() {
const states = {
'initial': `
<input type='button' class='api-key-button-show' value='Show API key' />
`,
'keyVisibleBasic': key => `
<span class="api-key-key">${key}</span>
`,
'keyVisible': key => `
<span class="api-key-key">${key}</span>
<input type='button' class='api-key-button-copy' value='Copy API key to clipboard' />
`,
'keyCopied': `
<span class="api-key-key">Copied to clipboard</span>
<input type='button' class='api-key-button-show' value='Show API key' />
`
};
this.copyKey = function(keyElement, callback) {
var selection = window.getSelection ? window.getSelection() : document.selection,
range = document.createRange();
selection.removeAllRanges();
range.selectNodeContents(keyElement);
selection.addRange(range);
document.execCommand('copy');
selection.removeAllRanges();
callback();
};
this.start = function(component) {
const $component = $(component).html(states.initial).attr('aria-live', 'polite'),
key = $component.data('key');
$component
.on(
'click', '.api-key-button-show', () =>
$component.html(
document.queryCommandSupported('copy') ?
states.keyVisible(key) : states.keyVisibleBasic(key)
)
)
.on(
'click', '.api-key-button-copy', () =>
this.copyKey(
$('.api-key-key', component)[0], () =>
$component.html(states.keyCopied)
)
);
};
};
})(window.GOVUK.Modules);

View File

@@ -1 +1,3 @@
$(() => GOVUK.modules.start());
$(() => new GOVUK.SelectionButtons('.block-label input'));

View File

@@ -5,3 +5,7 @@
.column-three-quarters {
@include grid-column(3/4);
}
.bottom-gutter {
margin-bottom: $gutter;
}

View File

@@ -0,0 +1,17 @@
.api-key {
&-key {
font-family: monospace;
display: block;
margin-bottom: 10px;
}
&-button-show {
@include button($grey-3);
}
&-button-copy {
@include button($grey-3);
}
}

View File

@@ -27,3 +27,20 @@
}
}
.banner-dangerous {
@extend .banner;
background: $white;
color: $error-colour;
border: 5px solid $error-colour;
margin: 15px 0;
@include bold-19;
text-align: left;
.button {
@include button($error-colour);
margin-top: 10px;
}
}

View File

@@ -0,0 +1,24 @@
.email-message {
margin-bottom: $gutter;
border: 1px solid $border-colour;
&-subject {
border-bottom: 1px solid $border-colour;;
padding: 10px;
@include bold-19;
}
&-body {
border-bottom: 1px solid white;
padding: 10px;
overflow: hidden;
max-height: 103px;
}
&-name {
@include bold-19;
margin: 50px 0 10px 0;
}
}

View File

@@ -7,6 +7,26 @@
margin-top: $gutter;
}
&-delete-link {
line-height: 40px;
padding: 0 0 0 5px;
a:visited,
a:link {
color: $error-colour;
display: inline-block;
vertical-align: center;
}
a:hover,
a:active {
color: $mellow-red;
}
}
.button {}
.button-destructive {

View File

@@ -45,4 +45,9 @@
margin: -$gutter-half 0 $gutter 0;
}
&-name {
@include bold-19;
margin: 50px 0 10px 0;
}
}

View File

@@ -1,33 +1,33 @@
// Dependencies from GOV.UK Frontend Toolkit
// https://github.com/alphagov/govuk_frontend_toolkit/
@import '../govuk_frontend_toolkit/stylesheets/conditionals';
@import '../govuk_frontend_toolkit/stylesheets/shims';
@import '../govuk_frontend_toolkit/stylesheets/measurements';
@import '../govuk_frontend_toolkit/stylesheets/css3';
@import '../govuk_frontend_toolkit/stylesheets/colours';
@import '../govuk_frontend_toolkit/stylesheets/typography';
@import '../govuk_frontend_toolkit/stylesheets/grid_layout';
@import '../govuk_frontend_toolkit/stylesheets/helpers';
@import '../govuk_frontend_toolkit/stylesheets/url-helpers';
@import '../govuk_frontend_toolkit/stylesheets/design-patterns/buttons';
@import '../govuk_frontend_toolkit/stylesheets/design-patterns/alpha-beta';
@import 'conditionals';
@import 'shims';
@import 'measurements';
@import 'css3';
@import 'colours';
@import 'typography';
@import 'grid_layout';
@import 'helpers';
@import 'url-helpers';
@import 'design-patterns/buttons';
@import 'design-patterns/alpha-beta';
// Dependencies from GOV.UK Elements
// https://github.com/alphagov/govuk_elements
@import '../govuk_elements/public/sass/elements/helpers';
@import '../govuk_elements/public/sass/elements/reset';
@import '../govuk_elements/public/sass/elements/buttons';
@import '../govuk_elements/public/sass/elements/details';
@import '../govuk_elements/public/sass/elements/elements-typography';
@import '../govuk_elements/public/sass/elements/forms';
@import '../govuk_elements/public/sass/elements/forms/form-validation';
@import '../govuk_elements/public/sass/elements/forms/form-block-labels';
@import '../govuk_elements/public/sass/elements/forms/form-validation';
@import '../govuk_elements/public/sass/elements/icons';
@import '../govuk_elements/public/sass/elements/layout';
@import '../govuk_elements/public/sass/elements/lists';
@import '../govuk_elements/public/sass/elements/panels';
@import '../govuk_elements/public/sass/elements/tables';
@import 'elements/helpers';
@import 'elements/reset';
@import 'elements/buttons';
@import 'elements/details';
@import 'elements/elements-typography';
@import 'elements/forms';
@import 'elements/forms/form-validation';
@import 'elements/forms/form-block-labels';
@import 'elements/forms/form-validation';
@import 'elements/icons';
@import 'elements/layout';
@import 'elements/lists';
@import 'elements/panels';
@import 'elements/tables';
// Specific to this application
@@ -44,6 +44,8 @@
@import 'components/browse-list';
@import 'components/management-navigation';
@import 'components/dropdown';
@import 'components/email-message';
@import 'components/api-key';
@import 'views/job';

View File

@@ -5,5 +5,5 @@ main = Blueprint('main', __name__)
from app.main.views import (
index, sign_in, sign_out, register, two_factor, verify, sms, add_service,
code_not_received, jobs, dashboard, templates, service_settings, forgot_password,
new_password, styleguide, user_profile, choose_service
new_password, styleguide, user_profile, choose_service, api_keys
)

View File

@@ -166,8 +166,12 @@ class AddServiceForm(Form):
self._names_func = names_func
super(AddServiceForm, self).__init__(*args, **kwargs)
name = StringField('Service Name', validators=[
DataRequired(message='Service name can not be empty')])
name = StringField(
'Service name',
validators=[
DataRequired(message='Service name can not be empty')
]
)
def validate_name(self, a):
if a.data in self._names_func():

View File

@@ -1,15 +1,22 @@
import os
import uuid
from boto3 import resource
# TODO add service name to bucket name as well
def s3upload(filepath):
filename = filepath.split(os.path.sep)[-1]
def s3upload(service_id, filedata):
upload_id = str(uuid.uuid4())
s3 = resource('s3')
s3.create_bucket(Bucket=upload_id)
key = s3.Object(upload_id, filename)
key.put(Body=open(filepath, 'rb'), ServerSideEncryption='AES256')
bucket_name = 'service-{}-notify'.format(service_id)
s3.create_bucket(Bucket=bucket_name)
contents = '\n'.join(filedata['data'])
key = s3.Object(bucket_name, upload_id)
key.put(Body=contents, ServerSideEncryption='AES256')
return upload_id
def s3download(service_id, upload_id):
s3 = resource('s3')
bucket_name = 'service-{}-notify'.format(service_id)
key = s3.Object(bucket_name, upload_id)
contents = key.get()['Body'].read().decode('utf-8')
return contents

View File

@@ -0,0 +1,39 @@
templates = [
{
'type': 'sms',
'name': 'Confirmation',
'body': 'Lasting power of attorney: Weve received your application. Applications take between 8 and 10 weeks to process.' # noqa
},
{
'type': 'sms',
'name': 'Reminder',
'body': 'Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)). Tax your vehicle at www.gov.uk/vehicle-tax' # noqa
},
{
'type': 'sms',
'name': 'Warning',
'body': 'Vehicle tax: Your vehicle tax for ((registration number)) has expired. Tax your vehicle at www.gov.uk/vehicle-tax' # noqa
},
{
'type': 'email',
'name': 'Application alert 06/2016',
'subject': 'Your lasting power of attorney application',
'body': """Dear ((name)),
When youve made your lasting power of attorney (LPA), you need to register it \
with the Office of the Public Guardian (OPG).
You can apply to register your LPA yourself if youre able to make your own decisions.
Your attorney can also register it for you. Youll be told if they do and you can \
object to the registration.
It takes between 8 and 10 weeks to register an LPA if there are no mistakes in the application.
"""
},
{
'type': 'sms',
'name': 'Air quality alert',
'body': 'Air pollution levels will be ((level)) in ((region)) tomorrow.'
},
]

View File

@@ -1,4 +1,4 @@
from flask import render_template, jsonify, redirect, session, url_for
from flask import request, render_template, jsonify, redirect, session, url_for, abort
from flask_login import login_required
from app.main import main
from app.main.dao import services_dao, users_dao
@@ -8,11 +8,19 @@ from app.main.forms import AddServiceForm
@main.route("/add-service", methods=['GET', 'POST'])
@login_required
def add_service():
# TODO fix up this
form = AddServiceForm(services_dao.find_all_service_names)
services = services_dao.get_services()
if len(services) > 0:
heading = 'Set up notifications for your service'
else:
heading = 'Add a new service'
if form.validate_on_submit():
user = users_dao.get_user_by_id(session['user_id'])
service_id = services_dao.insert_new_service(form.name.data, user)
return redirect(url_for('main.service_dashboard', service_id=service_id))
else:
return render_template('views/add-service.html', form=form)
return render_template(
'views/add-service.html',
form=form,
heading=heading
)

View File

@@ -0,0 +1,9 @@
from flask import render_template
from flask_login import login_required
from app.main import main
@main.route("/services/<int:service_id>/api-keys")
@login_required
def api_keys(service_id):
return render_template('views/api-keys.html', service_id=service_id)

View File

@@ -36,9 +36,3 @@ def check_email(service_id):
@login_required
def manage_users(service_id):
return render_template('views/manage-users.html', service_id=service_id)
@main.route("/services/<int:service_id>/api-keys")
@login_required
def apikeys(service_id):
return render_template('views/api-keys.html', service_id=service_id)

View File

@@ -20,24 +20,15 @@ from werkzeug import secure_filename
from app.main import main
from app.main.forms import CsvUploadForm
from app.main.uploader import s3upload
from app.main.uploader import (
s3upload,
s3download
)
# TODO move this to the templates directory
message_templates = [
{
'name': 'Reminder',
'body': """
Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)).
Tax your vehicle at www.gov.uk/vehicle-tax
"""
},
{
'name': 'Warning',
'body': """
Vehicle tax: Your vehicle tax for ((registration number)) has expired.
Tax your vehicle at www.gov.uk/vehicle-tax
"""
},
from ._templates import templates
sms_templates = [
template for template in templates if template['type'] == 'sms'
]
@@ -48,68 +39,52 @@ def send_sms(service_id):
if form.validate_on_submit():
try:
csv_file = form.file.data
filename = _format_filename(csv_file.filename)
filepath = os.path.join(current_app.config['UPLOAD_FOLDER'],
filename)
csv_file.save(filepath)
_check_file(csv_file.filename, filepath)
filedata = _get_filedata(csv_file)
upload_id = s3upload(service_id, filedata)
return redirect(url_for('.check_sms',
service_id=service_id,
recipients=filename))
except (IOError, ValueError) as e:
upload_id=upload_id))
except ValueError as e:
message = 'There was a problem uploading: {}'.format(
csv_file.filename)
flash(message)
if isinstance(e, ValueError):
flash(str(e))
os.remove(filepath)
flash(str(e))
return redirect(url_for('.send_sms', service_id=service_id))
return render_template('views/send-sms.html',
message_templates=message_templates,
message_templates=sms_templates,
form=form,
service_id=service_id)
@main.route("/services/<int:service_id>/sms/check", methods=['GET', 'POST'])
@main.route("/services/<int:service_id>/sms/check/<upload_id>",
methods=['GET', 'POST'])
@login_required
def check_sms(service_id):
def check_sms(service_id, upload_id):
if request.method == 'GET':
filename = request.args.get('recipients')
if not filename:
abort(400)
filepath = os.path.join(current_app.config['UPLOAD_FOLDER'],
filename)
upload_result = _build_upload_result(filepath)
if upload_result.get('rejects'):
flash('There was a problem with some of the numbers')
contents = s3download(service_id, upload_id)
upload_result = _get_numbers(contents)
# TODO get original file name
return render_template(
'views/check-sms.html',
upload_result=upload_result,
filename=filename,
message_template=message_templates[0]['body'],
filename='someupload_file_name.csv',
message_template=sms_templates[0]['body'],
service_id=service_id
)
elif request.method == 'POST':
filename = request.form['recipients']
filepath = os.path.join(current_app.config['UPLOAD_FOLDER'],
filename)
try:
upload_id = s3upload(filepath)
# TODO when job is created record filename in job itself
# so downstream pages can get the original filename that way
session[upload_id] = filename
return redirect(url_for('main.view_job', service_id=service_id, job_id=upload_id))
except:
flash('There as a problem saving the file')
return redirect(url_for('.check_sms', recipients=filename))
# TODO create the job with template, file location etc.
return redirect(url_for('main.view_job',
service_id=service_id,
job_id=upload_id))
def _check_file(filename, filepath):
if os.stat(filepath).st_size == 0:
message = 'The file {} contained no data'.format(filename)
def _get_filedata(file):
lines = file.read().decode('utf-8').splitlines()
if len(lines) < 2: # must be at least header and one line
message = 'The file {} contained no data'.format(file.filename)
raise ValueError(message)
return {'filename': file.filename, 'data': lines}
def _format_filename(filename):
@@ -119,24 +94,16 @@ def _format_filename(filename):
return secure_filename(formatted_name)
def _open(file):
return open(file, 'r')
def _build_upload_result(csv_file):
try:
file = _open(csv_file, 'r')
pattern = re.compile(r'^\+44\s?\d{4}\s?\d{6}$')
reader = csv.DictReader(
file.read().splitlines(),
lineterminator='\n',
quoting=csv.QUOTE_NONE)
valid, rejects = [], []
for i, row in enumerate(reader):
if pattern.match(row['phone']):
valid.append(row)
else:
rejects.append({"line_number": i+2, "phone": row['phone']})
return {"valid": valid, "rejects": rejects}
finally:
file.close()
def _get_numbers(contents):
pattern = re.compile(r'^\+44\s?\d{4}\s?\d{6}$') # need better validation
reader = csv.DictReader(
contents.split('\n'),
lineterminator='\n',
quoting=csv.QUOTE_NONE)
valid, rejects = [], []
for i, row in enumerate(reader):
if pattern.match(row['phone']):
valid.append(row)
else:
rejects.append({"line_number": i+2, "phone": row['phone']})
return {"valid": valid, "rejects": rejects}

View File

@@ -1,16 +1,19 @@
from flask import request, render_template, redirect, url_for
from flask import request, render_template, redirect, url_for, flash
from flask_login import login_required
from app.main import main
from app.main.forms import TemplateForm
from ._templates import templates
@main.route("/services/<int:service_id>/templates")
@login_required
def manage_templates(service_id):
return render_template(
'views/manage-templates.html',
service_id=service_id
service_id=service_id,
templates=templates,
)
@@ -31,21 +34,49 @@ def add_template(service_id):
return redirect(url_for('.manage_templates', service_id=service_id))
@main.route("/services/<int:service_id>/templates/<template_id>", methods=['GET', 'POST'])
@main.route("/services/<int:service_id>/templates/<int:template_id>", methods=['GET', 'POST'])
@login_required
def edit_template(service_id, template_id):
form = TemplateForm()
form.template_name.data = 'Reminder'
form.template_body.data = 'Vehicle tax: Your vehicle tax for ((registration number)) expires on ((date)). Tax your vehicle at www.gov.uk/vehicle-tax' # noqa
form.template_name.data = templates[template_id - 1]['name']
form.template_body.data = templates[template_id - 1]['body']
if request.method == 'GET':
return render_template(
'views/edit-template.html',
h1='Edit template',
form=form,
service_id=service_id
service_id=service_id,
template_id=template_id
)
elif request.method == 'POST':
return redirect(url_for('.manage_templates', service_id=service_id))
@main.route("/services/<int:service_id>/templates/<int:template_id>/delete", methods=['GET', 'POST'])
@login_required
def delete_template(service_id, template_id):
form = TemplateForm()
form.template_name.data = templates[template_id - 1]['name']
form.template_body.data = templates[template_id - 1]['body']
if request.method == 'GET':
flash('Are you sure you want to delete {}?'.format(form.template_name.data), 'delete')
return render_template(
'views/edit-template.html',
h1='Edit template',
form=form,
service_id=service_id,
template_id=template_id
)
elif request.method == 'POST':
if request.form.get('delete'):
return redirect(url_for('.manage_templates', service_id=service_id))
else:
return redirect(url_for('.manage_templates', service_id=service_id))

View File

@@ -20,5 +20,5 @@ def verify():
verify_codes_dao.use_code_for_user_and_type(user_id=user.id, code_type='sms')
users_dao.activate_user(user.id)
login_user(user)
return redirect(url_for('.add_service'))
return redirect(url_for('.add_service', first='first'))
return render_template('views/verify.html', form=form)

View File

@@ -14,7 +14,6 @@ def send_sms_code(user_id, mobile_number):
verify_codes_dao.add_code(user_id=user_id, code=sms_code, code_type='sms')
notifications_api_client.send_sms(mobile_number=mobile_number,
message=sms_code)
return sms_code

View File

@@ -56,7 +56,7 @@
Service name
</div>
<a href="#">Switch to A N Other service</a>
<a href="#">Add a new service to Notify</a>
<a href="{{ url_for('.add_service') }}">Add a new service to GOV.UK Notify</a>
</div>
</div>
<div class="column-half management-navigation-account">
@@ -68,16 +68,22 @@
{% endif %}
<main id="content" role="main" class="page-container">
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="error-summary">
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="banner-dangerous">
{% for category, message in messages %}
<li class="flash-message">
{{ message }}
{% if 'delete' == category %}
<form method='post'>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" class="button" name="delete" value="Yes, delete this template" />
</form>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endif %}
{% endwith %}
{% block fullwidth_content %}{% endblock %}
</main>

View File

@@ -0,0 +1,5 @@
{% macro api_key(key) %}
<div data-module="api-key" data-key="{{ key }}">
<span class="api-key-key">{{ key }}</span>
</div>
{% endmacro %}

View File

@@ -0,0 +1,19 @@
{% macro email_message(subject, body, name=None, edit_link=None) %}
{% if name %}
<h3 class="email-message-name">
{% if edit_link %}
<a href="{{ edit_link }}">{{ name }}</a>
{% else %}
{{ name }}
{% endif %}
</h3>
{% endif %}
<div class="email-message">
<div class="email-message-subject">
{{ subject|placeholders }}
</div>
<div class="email-message-body">
{{ body|nl2br|placeholders }}
</div>
</div>
{% endmacro %}

View File

@@ -1,11 +1,23 @@
{% macro page_footer(button_text=None, back_link=False, back_link_text="Back", destructive=False) %}
{% macro page_footer(
button_text=None,
destructive=False,
back_link=False,
back_link_text="Back",
delete_link=False,
delete_link_text="delete"
) %}
<div class="page-footer">
{% if button_text %}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" class="button{% if destructive %}-destructive{% endif %}" value="{{ button_text }}" />
{% endif %}
{% if delete_link %}
<span class="page-footer-delete-link">
or <a href="{{ delete_link }}">{{ delete_link_text }}</a>
</span>
{% endif %}
{% if back_link %}
<a class="page-footer-back-link" role="button" href="{{ back_link }}">{{ back_link_text }}</a>
<a class="page-footer-back-link" href="{{ back_link }}">{{ back_link_text }}</a>
{% endif %}
</div>
{% endmacro %}

View File

@@ -1,4 +1,13 @@
{% macro sms_message(body, recipient) %}
{% macro sms_message(body, recipient=None, name=None, edit_link=None) %}
{% if name %}
<h3 class="sms-message-name">
{% if edit_link %}
<a href="{{ edit_link }}">{{ name }}</a>
{% else %}
{{ name }}
{% endif %}
</h3>
{% endif %}
<div class="sms-message">
<div class="sms-message-wrapper">
{{ body|placeholders }}

View File

@@ -3,8 +3,12 @@
{% block fullwidth_content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1>404</h1>
<p>Sorry, that page doesn't exist.</p>
<h1 class="heading-xlarge">
Page could not be found
</h1>
<p>
Check you've entered the correct web address.
</p>
</div>
</div>
{% endblock %}

View File

@@ -3,8 +3,10 @@
{% block fullwidth_content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1>500</h1>
<p>Sorry, something went wrong on our system.</p>
<h1 class="heading-xlarge">
Sorry, we're experiencing technical difficulties
</h1>
<p>Try again later.</p>
</div>
</div>
{% endblock %}

View File

@@ -1,17 +1,18 @@
<nav class="navigation">
<ul>
<li><a href="{{ url_for('.service_dashboard', service_id=123) }}">Dashboard</a></li>
<li><a href="{{ url_for('.service_dashboard', service_id=service_id) }}">Dashboard</a></li>
</ul>
<ul>
<li><a href="{{ url_for('.send_sms', service_id=123) }}">Send text messages</a></li>
<li><a href="{{ url_for('.send_email', service_id=123) }}">Send emails</a></li>
<li><a href="{{ url_for('.view_jobs', service_id=123) }}">Activity</a></li>
<li><a href="{{ url_for('.send_sms', service_id=service_id) }}">Send text messages</a></li>
<li><a href="{{ url_for('.send_email', service_id=service_id) }}">Send emails</a></li>
<li><a href="{{ url_for('.view_jobs', service_id=service_id) }}">Activity</a></li>
<li><a href="{{ url_for('.manage_templates', service_id=service_id) }}">Templates</a></li>
</ul>
<ul>
<li><a href="{{ url_for('.apikeys', service_id=123) }}">API keys and documentation</a></li>
<li><a href="{{ url_for('.api_keys', service_id=service_id) }}">API keys and documentation</a></li>
</ul>
<ul>
<li><a href="{{ url_for('.manage_users', service_id=123) }}">Manage users</a></li>
<li><a href="{{ url_for('.service_settings', service_id=123) }}">Service settings</a></li>
<li><a href="{{ url_for('.manage_users', service_id=service_id) }}">Manage users</a></li>
<li><a href="{{ url_for('.service_settings', service_id=service_id) }}">Service settings</a></li>
</ul>
</nav>

View File

@@ -1,5 +1,6 @@
{% extends "admin_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import page_footer %}
{% block page_title %}
GOV.UK Notify | Set up service
@@ -8,24 +9,34 @@ GOV.UK Notify | Set up service
{% block fullwidth_content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1 class="heading-xlarge">Set up notifications for your service</h1>
<div class="column-two-thirds">
<p>Users will see your service name:</p>
<ul class="list-bullet">
<li>at the start of every text message, eg 'Vehicle tax: we received your payment, thank you'</li>
<li>as your email sender name</li>
</ul>
<h1 class="heading-xlarge">
{{ heading }}
</h1>
<form autocomplete="off" method="post">
{{ textbox(form.name) }}
<p>
Users will see your service name:
</p>
<p>
<button class="button" href="dashboard" role="button">Continue</button>
</p>
</form>
</div>
</div>
<ul class="list-bullet bottom-gutter">
<li>
at the start of every text message, eg Vehicle tax: we received your
payment, thank you
</li>
<li>
as your email sender name
</li>
</ul>
<form autocomplete="off" method="post">
{{ textbox(form.name) }}
{{ page_footer(
'Continue'
) }}
</form>
</div>
</div>
{% endblock %}

View File

@@ -1,15 +1,52 @@
{% extends "withnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/api-key.html" import api_key %}
{% block page_title %}
GOV.UK Notify | API keys and documentation
GOV.UK Notify | API keys and documentation
{% endblock %}
{% block maincolumn_content %}
<h1 class="heading-xlarge">API keys and documentation</h1>
<div class="grid-row">
<div class="column-two-thirds">
<p>Here's where developers can access information about the API and access keys</p>
<h1 class="heading-xlarge">
API keys and documentation
</h1>
<h2 class="heading-medium">
How to integrate GOV.UK Notify into your service
</h2>
<p>
blah blah blah this is where we tell you how the API works
</p>
<h2 class="heading-medium">Repositories</h2>
<p>
<a href="https://github.com/alphagov/notifications-api">GOV.UK Notify API</a>
</p>
<p>
<a href="https://github.com/alphagov/notify-api-client">GOV.UK Notify Python client</a>
</p>
<h2 class="heading-medium">API key for [service name]</h2>
{{ api_key('d30512af92e1386d63b90e5973b49a10') }}
<h2 class="heading-medium">API endpoint</h2>
<p>
https://www.notify.works/api/endpoint
</p>
</div>
</div>
{{ page_footer(
back_link=url_for('.service_dashboard', service_id=service_id),

View File

@@ -59,8 +59,6 @@
back_link = url_for(".send_sms", service_id=service_id)
)}}
<input type='hidden' name='recipients' value='{{filename}}'>
</form>
{% endif %}
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends "withnav_template.html" %}
{% extends "admin_template.html" %}
{% from "components/browse-list.html" import browse_list %}
{% block page_title %}

View File

@@ -14,9 +14,10 @@ GOV.UK Notify | Edit template
{{ textbox(form.template_name) }}
{{ textbox(form.template_body, highlight_tags=True) }}
{{ page_footer(
'Save and continue',
back_link=url_for('.service_dashboard', service_id=service_id),
back_link_text='Back to manage templates'
'Save',
delete_link=url_for('.delete_template', service_id=service_id, template_id=template_id) if template_id or None,
back_link=url_for('.manage_templates', service_id=service_id),
back_link_text='Back to templates'
) }}
</form>

View File

@@ -1,4 +1,7 @@
{% extends "withnav_template.html" %}
{% from "components/sms-message.html" import sms_message %}
{% from "components/email-message.html" import email_message %}
{% from "components/browse-list.html" import browse_list %}
{% block page_title %}
GOV.UK Notify | Manage templates
@@ -6,18 +9,32 @@ GOV.UK Notify | Manage templates
{% block maincolumn_content %}
<div class="grid-row">
<div class="column-two-thirds">
<h1 class="heading-xlarge">Manage templates</h1>
<h1 class="heading-xlarge">Templates</h1>
<p>Here's where you can view templates, choose to add one, or edit/delete one.</p>
<p>
<a href="{{ url_for('.add_template', service_id=service_id) }}">Create new template</a>
<p>
<a href="{{ url_for('.edit_template', service_id=service_id, template_id=1) }}">Here is my first template</a>
</p>
<p>
<a class="button" href="{{ url_for('.add_template', service_id=service_id) }}" role="button">Add a new message template</a>
</p>
{% for template in templates %}
{% if template.type == 'sms' %}
{{ sms_message(
template.body,
name=template.name,
edit_link=url_for('.edit_template', service_id=service_id, template_id=loop.index)
) }}
{% elif template.type == 'email' %}
{{ email_message(
template.subject,
template.body,
name=template.name,
edit_link=url_for('.edit_template', service_id=service_id, template_id=loop.index)
) }}
{% endif %}
{% endfor %}
</div>
</div>
{% endblock %}

View File

@@ -4,45 +4,42 @@
{% from "components/textbox.html" import textbox %}
{% block page_title %}
GOV.UK Notify | Send text messages
GOV.UK Notify | Send text messages
{% endblock %}
{% block maincolumn_content %}
<form method="POST" enctype="multipart/form-data">
<form method="POST" enctype="multipart/form-data">
<h1 class="heading-xlarge">Send text messages</h1>
<h1 class="heading-xlarge">Send text messages</h1>
<h2 class="heading-medium">1. Choose text message template</h2>
{% for template in message_templates %}
<div class="template-picker-option">
<div class="template-picker-option-radio">
<label class="block-label" for="template-{{loop.index}}">
{{ template.name }}
<input type="radio" name="template" id="template-{{loop.index}}" value="{{ template.name }}" />
</label>
</div>
{{ sms_message(template.body) }}
</div>
{% endfor %}
<fieldset class='form-group'>
<legend class="heading-medium">1. Choose text message template</legend>
{% for template in message_templates %}
<label class="block-label" for="template-{{loop.index}}">
{{ template.name }}
<input type="radio" name="template" id="template-{{loop.index}}" value="{{ template.name }}" />
</label>
{% endfor %}
</fieldset>
<p>
or <a href="{{ url_for(".add_template", service_id=service_id) }}">create a new template</a>
</p>
<p>
or <a href="{{ url_for(".add_template", service_id=service_id) }}">create a new template</a>
</p>
<h2 class="heading-medium">2. Add recipients</h2>
<h2 class="heading-medium">2. Add recipients</h2>
<p>
Upload a CSV file to add your recipients details.
</p>
<p>
You can also <a href="#">download an example CSV</a>.
</p>
<p>
{{textbox(form.file)}}
</p>
<p>
Upload a CSV file to add your recipients details.
</p>
<p>
You can also <a href="#">download an example CSV</a>.
</p>
<p>
{{textbox(form.file)}}
</p>
{{ page_footer("Continue") }}
{{ page_footer("Continue") }}
</form>
</form>
{% endblock %}

View File

@@ -7,6 +7,7 @@
{% from "components/sms-message.html" import sms_message %}
{% from "components/table.html" import mapping_table, list_table, row, field %}
{% from "components/textbox.html" import textbox %}
{% from "components/api-key.html" import api_key %}
{% block page_title %}
Styleguide GOV.UK Notify
@@ -152,4 +153,8 @@
{{ textbox(form.password) }}
{{ textbox(form.message, highlight_tags=True) }}
<h2 class="heading-large">API key</h2>
{{ api_key('d30512af92e1386d63b90e5973b49a10') }}
{% endblock %}