mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-26 01:04:00 -04:00
Merge pull request #370 from alphagov/add-tour
Make first time use clearer
This commit is contained in:
BIN
app/assets/images/tour-next.png
Normal file
BIN
app/assets/images/tour-next.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 983 B |
BIN
app/assets/images/tour/2.png
Normal file
BIN
app/assets/images/tour/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
app/assets/images/tour/3.png
Normal file
BIN
app/assets/images/tour/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
app/assets/images/tour/4.png
Normal file
BIN
app/assets/images/tour/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
@@ -7,6 +7,25 @@
|
|||||||
|
|
||||||
const tagPattern = /\(\([^\)\(]+\)\)/g;
|
const tagPattern = /\(\([^\)\(]+\)\)/g;
|
||||||
|
|
||||||
|
const getPlaceholderHint = function(placeholders) {
|
||||||
|
if (0 === placeholders.length) {
|
||||||
|
return `
|
||||||
|
<p>Add fields using ((double brackets))</p>
|
||||||
|
<span class='placeholder-hint-action' tabindex='0' role='button'>Show me how</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
if (1 === placeholders.length) {
|
||||||
|
return `
|
||||||
|
<p>Add fields using ((double brackets))</p>
|
||||||
|
<p>You’ll populate the ‘${placeholders[0]}’ field when you send messages using this template</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<p>Add fields using ((double brackets))</p>
|
||||||
|
<p>You’ll populate your fields when you send some messages</p>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
Modules.HighlightTags = function() {
|
Modules.HighlightTags = function() {
|
||||||
|
|
||||||
this.start = function(textarea) {
|
this.start = function(textarea) {
|
||||||
@@ -22,6 +41,9 @@
|
|||||||
`))
|
`))
|
||||||
.on("input", this.update);
|
.on("input", this.update);
|
||||||
|
|
||||||
|
this.$placeHolderHint = $('#placeholder-hint')
|
||||||
|
.on("click", ".placeholder-hint-action", this.demo);
|
||||||
|
|
||||||
this.initialHeight = this.$textbox.height();
|
this.initialHeight = this.$textbox.height();
|
||||||
|
|
||||||
this.$backgroundMaskForeground.width(
|
this.$backgroundMaskForeground.width(
|
||||||
@@ -40,14 +62,30 @@
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.escapedMessage = () => $('<div/>').text(this.$textbox.val()).html();
|
||||||
|
|
||||||
|
this.listPlaceholders = () => this.escapedMessage().match(tagPattern) || [];
|
||||||
|
|
||||||
|
this.listPlaceholdersWithoutBrackets = () => this.listPlaceholders().map(
|
||||||
|
placeholder => placeholder.substring(2, placeholder.length - 2)
|
||||||
|
);
|
||||||
|
|
||||||
this.replacePlaceholders = () => this.$backgroundMaskForeground.html(
|
this.replacePlaceholders = () => this.$backgroundMaskForeground.html(
|
||||||
$('<div/>').text(this.$textbox.val()).html().replace(
|
this.escapedMessage().replace(
|
||||||
tagPattern, match => `<span class='tag'>${match}</span>`
|
tagPattern, match => `<span class='tag'>${match}</span>`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.hint = () => this.$placeHolderHint.html(
|
||||||
|
getPlaceholderHint(this.listPlaceholdersWithoutBrackets())
|
||||||
|
);
|
||||||
|
|
||||||
this.update = () => (
|
this.update = () => (
|
||||||
this.replacePlaceholders() && this.resize()
|
this.replacePlaceholders() && this.resize() && this.hint()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.demo = () => (
|
||||||
|
this.$textbox.val((i, current) => `Dear ((name)), ${current}`) && this.update()
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -127,4 +127,4 @@ a[rel="external"] {
|
|||||||
@include media(tablet) {
|
@include media(tablet) {
|
||||||
@include external-link-19;
|
@include external-link-19;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,65 +58,60 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
%banner-tip,
|
|
||||||
.banner-tip {
|
|
||||||
|
|
||||||
@extend %banner;
|
|
||||||
@include bold-19;
|
|
||||||
background-color: $yellow;
|
|
||||||
color: $text-colour;
|
|
||||||
text-align: left;
|
|
||||||
margin-top: 0;
|
|
||||||
|
|
||||||
a {
|
|
||||||
&:link,
|
|
||||||
&:visited {
|
|
||||||
color: $text-colour;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ol {
|
|
||||||
list-style-type: decimal;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-tip-with-tick {
|
|
||||||
@extend %banner-with-tick;
|
|
||||||
@extend %banner-tip;
|
|
||||||
background-image: file-url('tick-black.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-info,
|
|
||||||
.banner-important {
|
|
||||||
@extend %banner;
|
|
||||||
background: $white;
|
|
||||||
color: $text-colour;
|
|
||||||
background-image: file-url('icon-important-2x.png');
|
|
||||||
background-size: 34px 34px;
|
|
||||||
background-position: 0 0;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
padding: 7px 0 5px 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-info {
|
|
||||||
background-image: file-url('icon-information-2x.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-mode {
|
.banner-mode {
|
||||||
|
|
||||||
@extend %banner;
|
@extend %banner;
|
||||||
background: $govuk-blue;
|
background: $govuk-blue;
|
||||||
color: $white;
|
color: $white;
|
||||||
margin-top: $gutter;
|
margin-top: $gutter;
|
||||||
|
padding: $gutter-half;
|
||||||
|
|
||||||
.heading-medium {
|
p {
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-action {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-tour {
|
||||||
|
|
||||||
|
@extend %banner;
|
||||||
|
background: $govuk-blue;
|
||||||
|
color: $white;
|
||||||
|
margin-top: $gutter;
|
||||||
|
padding: $gutter * 2;
|
||||||
|
|
||||||
|
.heading-large {
|
||||||
|
margin: 0 0 $gutter 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
|
||||||
|
margin-bottom: $gutter;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
& + p {
|
||||||
|
margin-top: -$gutter-half;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
||||||
|
@include bold-24;
|
||||||
|
display: inline-block;
|
||||||
|
background-image: file-url('tour-next.png');
|
||||||
|
background-size: auto 24px;
|
||||||
|
padding: 0 23px 0 0;
|
||||||
|
background-position: right 3px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
&:link,
|
&:link,
|
||||||
&:visited {
|
&:visited {
|
||||||
color: $white;
|
color: $white;
|
||||||
@@ -124,19 +119,25 @@
|
|||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active {
|
&:active {
|
||||||
color: $light-blue-25;
|
background-color: $link-hover-colour;
|
||||||
|
outline: 10px solid $link-hover-colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
background-color: $yellow;
|
||||||
|
outline: 10px solid $yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.big-number {
|
img {
|
||||||
|
max-width: 100%;
|
||||||
margin-top: 10px;
|
display: block;
|
||||||
|
}
|
||||||
&-label {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
&-image-flush-bottom {
|
||||||
|
margin: 40px 0 -60px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.textbox-highlight {
|
.textbox-highlight {
|
||||||
|
|
||||||
$tag-background: rgba($light-blue, 0.7);
|
$tag-background: rgba($light-blue, 0.6);
|
||||||
|
|
||||||
&-wrapper {
|
&-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
.edit-template {
|
.placeholder-hint {
|
||||||
|
|
||||||
&-placeholder-hint {
|
display: block;
|
||||||
display: block;
|
|
||||||
padding-top: 20px;
|
&-title {
|
||||||
//color: $secondary-text-colour;
|
@include bold-19;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-action {
|
||||||
|
|
||||||
|
@include bold-19;
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: $yellow-25;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,5 +23,6 @@ from app.main.views import (
|
|||||||
api_keys,
|
api_keys,
|
||||||
manage_users,
|
manage_users,
|
||||||
invites,
|
invites,
|
||||||
all_services
|
all_services,
|
||||||
|
tour
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from app.main.validators import (Blacklist, CsvFileValidator, ValidEmailDomainRe
|
|||||||
def email_address(label='Email address'):
|
def email_address(label='Email address'):
|
||||||
return EmailField(label, validators=[
|
return EmailField(label, validators=[
|
||||||
Length(min=5, max=255),
|
Length(min=5, max=255),
|
||||||
DataRequired(message='Email cannot be empty'),
|
DataRequired(message='Can’t be empty'),
|
||||||
Email(message='Enter a valid email address'),
|
Email(message='Enter a valid email address'),
|
||||||
ValidEmailDomainRegex()])
|
ValidEmailDomainRegex()])
|
||||||
|
|
||||||
@@ -37,35 +37,35 @@ class UKMobileNumber(TelField):
|
|||||||
|
|
||||||
def mobile_number():
|
def mobile_number():
|
||||||
return UKMobileNumber('Mobile phone number',
|
return UKMobileNumber('Mobile phone number',
|
||||||
validators=[DataRequired(message='Cannot be empty')])
|
validators=[DataRequired(message='Can’t be empty')])
|
||||||
|
|
||||||
|
|
||||||
def password(label='Create a password'):
|
def password(label='Create a password'):
|
||||||
return PasswordField(label,
|
return PasswordField(label,
|
||||||
validators=[DataRequired(message='Password can not be empty'),
|
validators=[DataRequired(message='Can’t be empty'),
|
||||||
Length(10, 255, message='Password must be at least 10 characters'),
|
Length(10, 255, message='Must be at least 10 characters'),
|
||||||
Blacklist(message='That password is blacklisted, too common')])
|
Blacklist(message='That password is blacklisted, too common')])
|
||||||
|
|
||||||
|
|
||||||
def sms_code():
|
def sms_code():
|
||||||
verify_code = '^\d{5}$'
|
verify_code = '^\d{5}$'
|
||||||
return StringField('Text message code',
|
return StringField('Text message code',
|
||||||
validators=[DataRequired(message='Text message confirmation code can not be empty'),
|
validators=[DataRequired(message='Can’t be empty'),
|
||||||
Regexp(regex=verify_code,
|
Regexp(regex=verify_code,
|
||||||
message='Text message confirmation code must be 5 digits')])
|
message='Must be 5 digits')])
|
||||||
|
|
||||||
|
|
||||||
def email_code():
|
def email_code():
|
||||||
verify_code = '^\d{5}$'
|
verify_code = '^\d{5}$'
|
||||||
return StringField("Email code",
|
return StringField("Email code",
|
||||||
validators=[DataRequired(message='Email confirmation code can not be empty'),
|
validators=[DataRequired(message='Can’t be empty'),
|
||||||
Regexp(regex=verify_code, message='Email confirmation code must be 5 digits')])
|
Regexp(regex=verify_code, message='Must be 5 digits')])
|
||||||
|
|
||||||
|
|
||||||
class LoginForm(Form):
|
class LoginForm(Form):
|
||||||
email_address = StringField('Email address', validators=[
|
email_address = StringField('Email address', validators=[
|
||||||
Length(min=5, max=255),
|
Length(min=5, max=255),
|
||||||
DataRequired(message='Email cannot be empty'),
|
DataRequired(message='Can’t be empty'),
|
||||||
Email(message='Enter a valid email address')
|
Email(message='Enter a valid email address')
|
||||||
])
|
])
|
||||||
password = PasswordField('Password', validators=[
|
password = PasswordField('Password', validators=[
|
||||||
@@ -76,7 +76,7 @@ class LoginForm(Form):
|
|||||||
class RegisterUserForm(Form):
|
class RegisterUserForm(Form):
|
||||||
|
|
||||||
name = StringField('Full name',
|
name = StringField('Full name',
|
||||||
validators=[DataRequired(message='Name can not be empty')])
|
validators=[DataRequired(message='Can’t be empty')])
|
||||||
email_address = email_address()
|
email_address = email_address()
|
||||||
mobile_number = mobile_number()
|
mobile_number = mobile_number()
|
||||||
password = password()
|
password = password()
|
||||||
@@ -84,7 +84,7 @@ class RegisterUserForm(Form):
|
|||||||
|
|
||||||
class RegisterUserFromInviteForm(Form):
|
class RegisterUserFromInviteForm(Form):
|
||||||
name = StringField('Full name',
|
name = StringField('Full name',
|
||||||
validators=[DataRequired(message='Name can not be empty')])
|
validators=[DataRequired(message='Can’t be empty')])
|
||||||
mobile_number = mobile_number()
|
mobile_number = mobile_number()
|
||||||
password = password()
|
password = password()
|
||||||
service = HiddenField('service')
|
service = HiddenField('service')
|
||||||
@@ -108,7 +108,7 @@ class InviteUserForm(PermissionsForm):
|
|||||||
|
|
||||||
def validate_email_address(self, field):
|
def validate_email_address(self, field):
|
||||||
if field.data.lower() == self.invalid_email_address:
|
if field.data.lower() == self.invalid_email_address:
|
||||||
raise ValidationError("You can't send an invitation to yourself")
|
raise ValidationError("You can’t send an invitation to yourself")
|
||||||
|
|
||||||
|
|
||||||
class TwoFactorForm(Form):
|
class TwoFactorForm(Form):
|
||||||
@@ -149,7 +149,7 @@ class AddServiceForm(Form):
|
|||||||
name = StringField(
|
name = StringField(
|
||||||
'Service name',
|
'Service name',
|
||||||
validators=[
|
validators=[
|
||||||
DataRequired(message='Service name can’t be empty')
|
DataRequired(message='Can’t be empty')
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ class ServiceNameForm(Form):
|
|||||||
name = StringField(
|
name = StringField(
|
||||||
u'New name',
|
u'New name',
|
||||||
validators=[
|
validators=[
|
||||||
DataRequired(message='Service name can’t be empty')
|
DataRequired(message='Can’t be empty')
|
||||||
])
|
])
|
||||||
|
|
||||||
def validate_name(self, a):
|
def validate_name(self, a):
|
||||||
@@ -199,18 +199,18 @@ class ConfirmPasswordForm(Form):
|
|||||||
class SMSTemplateForm(Form):
|
class SMSTemplateForm(Form):
|
||||||
name = StringField(
|
name = StringField(
|
||||||
u'Template name',
|
u'Template name',
|
||||||
validators=[DataRequired(message="Template name cannot be empty")])
|
validators=[DataRequired(message="Can’t be empty")])
|
||||||
|
|
||||||
template_content = TextAreaField(
|
template_content = TextAreaField(
|
||||||
u'Message',
|
u'Message content',
|
||||||
validators=[DataRequired(message="Template content cannot be empty")])
|
validators=[DataRequired(message="Can’t be empty")])
|
||||||
|
|
||||||
|
|
||||||
class EmailTemplateForm(SMSTemplateForm):
|
class EmailTemplateForm(SMSTemplateForm):
|
||||||
|
|
||||||
subject = StringField(
|
subject = StringField(
|
||||||
u'Subject',
|
u'Subject',
|
||||||
validators=[DataRequired(message="Subject cannot be empty")])
|
validators=[DataRequired(message="Can’t be empty")])
|
||||||
|
|
||||||
|
|
||||||
class ForgotPasswordForm(Form):
|
class ForgotPasswordForm(Form):
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ def add_service():
|
|||||||
user_id=session['user_id'],
|
user_id=session['user_id'],
|
||||||
email_from=email_from)
|
email_from=email_from)
|
||||||
|
|
||||||
return redirect(url_for('main.service_dashboard', service_id=service_id))
|
return redirect(url_for('main.tour', service_id=service_id, page=1))
|
||||||
else:
|
else:
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/add-service.html',
|
'views/add-service.html',
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ def choose_template(service_id, template_type):
|
|||||||
|
|
||||||
if template_type not in ['email', 'sms']:
|
if template_type not in ['email', 'sms']:
|
||||||
abort(404)
|
abort(404)
|
||||||
jobs = job_api_client.get_job(service_id)['data']
|
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/choose-template.html',
|
'views/choose-template.html',
|
||||||
@@ -91,7 +90,6 @@ def choose_template(service_id, template_type):
|
|||||||
template_type=template_type,
|
template_type=template_type,
|
||||||
page_heading=get_page_headings(template_type),
|
page_heading=get_page_headings(template_type),
|
||||||
service=service,
|
service=service,
|
||||||
has_jobs=len(jobs),
|
|
||||||
service_id=service_id
|
service_id=service_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
15
app/main/views/tour.py
Normal file
15
app/main/views/tour.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from flask import render_template
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
|
from app.main import main
|
||||||
|
|
||||||
|
|
||||||
|
@main.route("/services/<service_id>/tour/<int:page>")
|
||||||
|
@login_required
|
||||||
|
def tour(service_id, page):
|
||||||
|
return render_template(
|
||||||
|
'views/tour/{}.html'.format(page),
|
||||||
|
service_id=service_id, # TODO: fix when Nick’s PR is merged
|
||||||
|
current_page=page,
|
||||||
|
next_page=(page + 1)
|
||||||
|
)
|
||||||
@@ -15,25 +15,6 @@
|
|||||||
When people receive notifications, who should they be from?
|
When people receive notifications, who should they be from?
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p>
|
|
||||||
Be specific to your service. Remember that there might be
|
|
||||||
other people in your organisation using GOV.UK Notify.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Users will see this:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<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">
|
<form autocomplete="off" method="post">
|
||||||
|
|
||||||
{{ textbox(form.name, hint="You can change this later") }}
|
{{ textbox(form.name, hint="You can change this later") }}
|
||||||
|
|||||||
@@ -22,13 +22,6 @@
|
|||||||
developer documentation</a>.
|
developer documentation</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{{ banner(
|
|
||||||
'You can only send messages to yourself until you <a href="{}">request to go live</a>'.format(
|
|
||||||
url_for('.service_request_to_go_live', service_id=service_id)
|
|
||||||
)|safe,
|
|
||||||
type='important'
|
|
||||||
) }}
|
|
||||||
|
|
||||||
<h2 class="api-key-name">
|
<h2 class="api-key-name">
|
||||||
Service ID
|
Service ID
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -37,17 +37,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if not has_jobs %}
|
|
||||||
{% if current_user.has_permissions(permissions=['send_texts', 'send_emails', 'send_letters'], any_=True) %}
|
|
||||||
{{ banner(
|
|
||||||
"""
|
|
||||||
Send yourself a test
|
|
||||||
""",
|
|
||||||
subhead='Next step:',
|
|
||||||
type="tip"
|
|
||||||
)}}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
<div class="grid-row">
|
<div class="grid-row">
|
||||||
{% for template in templates %}
|
{% for template in templates %}
|
||||||
<div class="column-two-thirds">
|
<div class="column-two-thirds">
|
||||||
|
|||||||
@@ -5,26 +5,26 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block maincolumn_content %}
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
{% if not templates and current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], any_=True) %}
|
||||||
|
{% include 'views/dashboard/get-started.html' %}
|
||||||
|
{% elif service.restricted %}
|
||||||
|
<div class="dashboard">
|
||||||
|
{% include 'views/dashboard/trial-mode-banner.html' %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if service.restricted %}
|
|
||||||
{% include 'views/dashboard/trial-mode-banner.html' %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not templates and current_user.has_permissions(['send_texts', 'send_emails', 'send_letters'], any_=True) %}
|
<div
|
||||||
{% include 'views/dashboard/get-started.html' %}
|
data-module="update-content"
|
||||||
{% endif %}
|
data-resource="{{url_for(".service_dashboard_updates", service_id=service_id)}}"
|
||||||
|
data-key="today"
|
||||||
{% if templates %}
|
data-interval-seconds="10"
|
||||||
<div
|
>
|
||||||
data-module="update-content"
|
{% include 'views/dashboard/today.html' %}
|
||||||
data-resource="{{url_for(".service_dashboard_updates", service_id=service_id)}}"
|
</div>
|
||||||
data-key="today"
|
|
||||||
data-interval-seconds="10"
|
|
||||||
>
|
|
||||||
{% include 'views/dashboard/today.html' %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% include 'views/dashboard/jobs.html' %}
|
{% include 'views/dashboard/jobs.html' %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,30 +1,23 @@
|
|||||||
{% from "components/banner.html" import banner_wrapper %}
|
{% from "components/banner.html" import banner_wrapper %}
|
||||||
|
|
||||||
<h2 class="heading-medium">Get started</h2>
|
|
||||||
{% if current_user.has_permissions(['manage_templates']) %}
|
{% if current_user.has_permissions(['manage_templates']) %}
|
||||||
<p>
|
{% call banner_wrapper(type='tour') %}
|
||||||
You need to set up a template before you can send messages
|
<h2 class="heading-large">You’re ready to get started</h2>
|
||||||
</p>
|
<p>
|
||||||
<ol class="grid-row">
|
<a href='{{ url_for(".add_service_template", service_id=service_id, template_type="sms") }}'>
|
||||||
<li class="column-half">
|
Set up a text message template
|
||||||
{% call banner_wrapper(type="tip") %}
|
</a>
|
||||||
<a href='{{ url_for(".add_service_template", service_id=service_id, template_type="sms") }}' style="white-space: nowrap;">
|
</p>
|
||||||
Set up a text message template
|
<p>
|
||||||
</a>
|
<a href='{{ url_for(".add_service_template", service_id=service_id, template_type="email") }}'>
|
||||||
{% endcall %}
|
Set up an email template
|
||||||
</li>
|
</a>
|
||||||
<li class="column-half">
|
</p>
|
||||||
{% call banner_wrapper(type="tip") %}
|
{% endcall %}
|
||||||
<a href='{{ url_for(".add_service_template", service_id=service_id, template_type="email") }}'>
|
|
||||||
Set up an email template
|
|
||||||
</a>
|
|
||||||
{% endcall %}
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
{% call banner_wrapper(type='mode') %}
|
||||||
<p>
|
<p>
|
||||||
You need to ask your service manager to set up some templates before you can send messages
|
You need to ask your service manager to set up some templates before you can send messages
|
||||||
</p>
|
</p>
|
||||||
</p>
|
{% endcall %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -2,24 +2,12 @@
|
|||||||
{% from "components/big-number.html" import big_number %}
|
{% from "components/big-number.html" import big_number %}
|
||||||
|
|
||||||
{% call banner_wrapper(type="mode") %}
|
{% call banner_wrapper(type="mode") %}
|
||||||
|
|
||||||
<h2 class="heading-medium">Trial mode</h2>
|
|
||||||
<div class="grid-row">
|
<div class="grid-row">
|
||||||
<div class="column-one-half">
|
<div class="column-one-half">
|
||||||
<p>
|
Your service is in trial mode
|
||||||
We’ll only deliver messages to you and members of your team
|
|
||||||
<br>
|
|
||||||
<a href="{{ url_for(".help", _anchor="trial-mode") }}">Find out more</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="column-one-sixth">
|
<div class="column-one-half">
|
||||||
|
<a href="{{ url_for(".help", _anchor="trial-mode") }}" class="banner-mode-action">Find out more</a>
|
||||||
</div>
|
|
||||||
<div class="column-one-third">
|
|
||||||
{{ big_number(
|
|
||||||
service.limit - statistics.get('emails_requested', 0) - statistics.get('sms_requested', 0),
|
|
||||||
'messages left today'
|
|
||||||
) }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|||||||
@@ -20,23 +20,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column-two-thirds">
|
<div class="column-two-thirds">
|
||||||
{{ textbox(form.template_content, highlight_tags=True, width='1-1') }}
|
{{ textbox(form.template_content, highlight_tags=True, width='1-1') }}
|
||||||
|
{{ page_footer(
|
||||||
|
'Save',
|
||||||
|
delete_link=url_for('.delete_service_template', service_id=service_id, template_id=template_id) if template_id or None,
|
||||||
|
delete_link_text='Delete this template'
|
||||||
|
) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column-one-third">
|
<div class="column-one-third">
|
||||||
<label for='template_content' class='edit-template-placeholder-hint'>
|
<label for='template_content' class='placeholder-hint'>
|
||||||
<p>
|
<div class="banner-mode" id="placeholder-hint" aria-live="polite">
|
||||||
Add personalisation with double brackets, eg Dear ((name))
|
<p>Add fields using ((double brackets))</p>
|
||||||
</p>
|
</div>
|
||||||
<p>
|
|
||||||
You’ll fill in the real data when you send a message.
|
|
||||||
</p>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ page_footer(
|
|
||||||
'Save',
|
|
||||||
delete_link=url_for('.delete_service_template', service_id=service_id, template_id=template_id) if template_id or None,
|
|
||||||
delete_link_text='Delete this template'
|
|
||||||
) }}
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,23 +19,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column-two-thirds">
|
<div class="column-two-thirds">
|
||||||
{{ textbox(form.template_content, highlight_tags=True, width='1-1') }}
|
{{ textbox(form.template_content, highlight_tags=True, width='1-1') }}
|
||||||
|
{{ page_footer(
|
||||||
|
'Save',
|
||||||
|
delete_link=url_for('.delete_service_template', service_id=service_id, template_id=template_id) if template_id or None,
|
||||||
|
delete_link_text='Delete this template'
|
||||||
|
) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="column-one-third">
|
<div class="column-one-third">
|
||||||
<label for='template_content' class='edit-template-placeholder-hint'>
|
<label for='template_content' class='placeholder-hint'>
|
||||||
<p>
|
<div class="banner-mode" id="placeholder-hint" aria-live="polite">
|
||||||
Add personalisation with double brackets, eg Dear ((name))
|
<p>Add fields using ((double brackets))</p>
|
||||||
</p>
|
</div>
|
||||||
<p>
|
|
||||||
You'll have to add the real data when you send a message.
|
|
||||||
</p>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ page_footer(
|
|
||||||
'Save',
|
|
||||||
delete_link=url_for('.delete_service_template', service_id=service_id, template_id=template_id) if template_id or None,
|
|
||||||
delete_link_text='Delete this template'
|
|
||||||
) }}
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,13 +36,6 @@
|
|||||||
|
|
||||||
{{ banner('Are you sure you want to delete?', 'dangerous', delete_button="Yes, delete this thing")}}
|
{{ banner('Are you sure you want to delete?', 'dangerous', delete_button="Yes, delete this thing")}}
|
||||||
|
|
||||||
{{ banner(
|
|
||||||
'<a href="#">Send your first message</a>'|safe,
|
|
||||||
subhead='Get started',
|
|
||||||
type='tip'
|
|
||||||
)}}
|
|
||||||
|
|
||||||
{{ banner('You could go to jail', 'important')}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
25
app/templates/views/tour/1.html
Normal file
25
app/templates/views/tour/1.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "withoutnav_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
{% from "components/banner.html" import banner_wrapper %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
{{heading}} – GOV.UK Notify
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
{% call banner_wrapper(type='tour') %}
|
||||||
|
<h2 class="heading-large">Trial mode</h2>
|
||||||
|
<p>
|
||||||
|
To start off with, you can only send messages to yourself.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We can remove these restrictions when you’re ready.
|
||||||
|
</p>
|
||||||
|
<a href='{{ url_for('.tour', service_id=service_id, page=next_page) }}'>
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
29
app/templates/views/tour/2.html
Normal file
29
app/templates/views/tour/2.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% extends "withoutnav_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
{% from "components/banner.html" import banner_wrapper %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
{{heading}} – GOV.UK Notify
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
{% call banner_wrapper(type='tour') %}
|
||||||
|
<h2 class="heading-large">Start with templates</h2>
|
||||||
|
<p>
|
||||||
|
Set up a template like this:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<img
|
||||||
|
src="/static/images/tour/{{ current_page }}.png"
|
||||||
|
width="554" height="97"
|
||||||
|
alt="A template for a text message with placeholders for the recipients name, document and date"
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<a href='{{ url_for('.tour', service_id=service_id, page=next_page) }}'>
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
32
app/templates/views/tour/3.html
Normal file
32
app/templates/views/tour/3.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{% extends "withoutnav_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
{% from "components/banner.html" import banner_wrapper %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
{{heading}} – GOV.UK Notify
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
{% call banner_wrapper(type='tour') %}
|
||||||
|
<h2 class="heading-large">Add recipients</h2>
|
||||||
|
<p>
|
||||||
|
Add recipients by uploading a .csv spreadsheet:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<img
|
||||||
|
src="/static/images/tour/{{ current_page }}.png"
|
||||||
|
width="554" height="118"
|
||||||
|
alt="A screenshot of a spreadsheet containing data about three people"
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Developers, you can add data automatically using an API
|
||||||
|
</p>
|
||||||
|
<a href='{{ url_for('.tour', service_id=service_id, page=next_page) }}'>
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
28
app/templates/views/tour/4.html
Normal file
28
app/templates/views/tour/4.html
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{% extends "withoutnav_template.html" %}
|
||||||
|
{% from "components/textbox.html" import textbox %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
{% from "components/banner.html" import banner_wrapper %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
{{heading}} – GOV.UK Notify
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
{% call banner_wrapper(type='tour') %}
|
||||||
|
<h2 class="heading-large">Send your messages</h2>
|
||||||
|
<p>
|
||||||
|
Notify merges your data with the template and sends the messages
|
||||||
|
</p>
|
||||||
|
<a href="{{ url_for('.service_dashboard', service_id=service_id) }}">
|
||||||
|
Next
|
||||||
|
</a>
|
||||||
|
<img
|
||||||
|
src="/static/images/tour/{{ current_page }}.png"
|
||||||
|
class="banner-tour-image-flush-bottom"
|
||||||
|
width="840" height="290"
|
||||||
|
alt="Three mobiles phones, each showing a text message personalised with data about the recipient"
|
||||||
|
>
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -26,7 +26,7 @@ def test_should_add_service_and_redirect_to_next_page(app_,
|
|||||||
url_for('main.add_service'),
|
url_for('main.add_service'),
|
||||||
data={'name': 'testing the post'})
|
data={'name': 'testing the post'})
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.location == url_for('main.service_dashboard', service_id=101, _external=True)
|
assert response.location == url_for('main.tour', service_id=101, page=1, _external=True)
|
||||||
assert mock_get_services.called
|
assert mock_get_services.called
|
||||||
mock_create_service.asset_called_once_with(service_name='testing the post',
|
mock_create_service.asset_called_once_with(service_name='testing the post',
|
||||||
active=False,
|
active=False,
|
||||||
@@ -44,7 +44,7 @@ def test_should_return_form_errors_when_service_name_is_empty(app_,
|
|||||||
client.login(api_user_active, mocker)
|
client.login(api_user_active, mocker)
|
||||||
response = client.post(url_for('main.add_service'), data={})
|
response = client.post(url_for('main.add_service'), data={})
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert 'Service name can’t be empty' in response.get_data(as_text=True)
|
assert 'Can’t be empty' in response.get_data(as_text=True)
|
||||||
|
|
||||||
|
|
||||||
def test_should_return_form_errors_with_duplicate_service_name_regardless_of_case(app_,
|
def test_should_return_form_errors_with_duplicate_service_name_regardless_of_case(app_,
|
||||||
|
|||||||
@@ -260,7 +260,7 @@ def test_user_cant_invite_themselves(
|
|||||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
assert page.h1.string.strip() == 'Invite a team member'
|
assert page.h1.string.strip() == 'Invite a team member'
|
||||||
form_error = page.find('span', class_='error-message').string.strip()
|
form_error = page.find('span', class_='error-message').string.strip()
|
||||||
assert form_error == "You can't send an invitation to yourself"
|
assert form_error == "You can’t send an invitation to yourself"
|
||||||
assert not mock_create_invite.called
|
assert not mock_create_invite.called
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
tests/app/main/views/test_tour.py
Normal file
19
tests/app/main/views/test_tour.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import pytest
|
||||||
|
from flask import url_for
|
||||||
|
|
||||||
|
import app
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("page", range(1, 5))
|
||||||
|
def test_should_render_tour_pages(
|
||||||
|
app_,
|
||||||
|
api_user_active,
|
||||||
|
mocker,
|
||||||
|
page
|
||||||
|
):
|
||||||
|
with app_.test_request_context():
|
||||||
|
with app_.test_client() as client:
|
||||||
|
client.login(api_user_active, mocker)
|
||||||
|
response = client.get(url_for('main.tour', service_id=101, page=page))
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert 'Next' in response.get_data(as_text=True)
|
||||||
Reference in New Issue
Block a user