Skip to content

Commit

Permalink
Feat/add caretaker notice (#2064)
Browse files Browse the repository at this point in the history
* style(notice): add new colors for notice states

* feat: add feature flag to show/hide the notice

* feat(notice): add notice component

Shows a message in 4 possible states: info, success, error, warning

* feat(tou prompt): add caretaker notice behind FF

* feat(storybook): add notice component to storybook

* chore: translations

* chore: regen css

* fix(notice): only allow h1-h3, default to div if param omitted/invalid

* fix(notice): use `caller()` syntax;  add `testids`;

* fix(tou-prompt): update notice component syntax

* test(notice): update storybook page

* test(notice): add ui tests

* chore: formatting; move test to the write directory

* fix(test): update notice import

* fix(storybook): update test page

* chore: update FF default
  • Loading branch information
andrewleith authored Feb 12, 2025
1 parent 7b2d14c commit bbd5c41
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 3 deletions.
4 changes: 2 additions & 2 deletions app/assets/stylesheets/index.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class Config(object):
FF_SALESFORCE_CONTACT = env.bool("FF_SALESFORCE_CONTACT", True)
FF_RTL = env.bool("FF_RTL", True)
FF_ANNUAL_LIMIT = env.bool("FF_ANNUAL_LIMIT", False)
FF_CARETAKER = env.bool("FF_CARETAKER", False)

FREE_YEARLY_EMAIL_LIMIT = env.int("FREE_YEARLY_EMAIL_LIMIT", 20_000_000)
FREE_YEARLY_SMS_LIMIT = env.int("FREE_YEARLY_SMS_LIMIT", 100_000)
Expand Down
60 changes: 60 additions & 0 deletions app/templates/components/notice.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{#
@param type - One of: 'info', 'success', 'error', 'warning'
@param message - Text to display in notice (can include HTML)
#}
{% macro notice(type, title, headingLevel) %}
{# set icon/colors based on type #}
{%
set display = {
'info': {
'icon': 'fa-circle-info',
'color': 'text-blue-500',
'border': 'border-blue-500',
'bg': 'bg-blue-100'
},
'success': {
'icon': 'fa-circle-check',
'color': 'text-green',
'border': 'border-green',
'bg': 'bg-green-light'
},
'error': {
'icon': 'fa-triangle-exclamation',
'color': 'text-red',
'border': 'border-red',
'bg': 'bg-red-light'
},
'warning': {
'icon': 'fa-circle-exclamation',
'color': 'text-orange',
'border': 'border-orange',
'bg': 'bg-orange-light'
}
}
%}
{# Text for screen readers instead of icon #}
{%
set screen_reader_text = {
'info': _('Information'),
'success': _('Success'),
'error': _('Error'),
'warning': _('Warning'),
}
%}
<div class="notice notice-{{ type }} overflow-hidden rounded-lg px-12 pt-12 border-4 {{display[type]['border']}} {{display[type]['bg']}}" role="alert" data-testid="notice">
<div class="flex gap-4 items-baseline">
<div class="notice-icon {{ display[type]['color'] }}" data-testid="notice-icon">
<i aria-hidden="true" class="mb-2 fa-solid fa-lg {{ display[type]['icon'] }}"></i>
<span class="sr-only" data-testid="notice-icon-text">{{ screen_reader_text[type] }}: </span>
</div>
<div class="notice-content ml-6 pb-12">
{% if headingLevel is defined and headingLevel is number and headingLevel >= 1 and headingLevel <= 3 %}
<h{{ headingLevel }} class="notice-heading heading-small md:heading-large mt-4" >{{ title }}</h{{ headingLevel }} data-testid="notice-heading">
{% else %}
<div data-testid="notice-heading">{{ title }}</div>
{% endif %}
<div data-testid="notice-message">{{ caller() }}</div>
</div>
</div>
</div>
{% endmacro %}
10 changes: 9 additions & 1 deletion app/templates/components/tou-prompt.html
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
{% from "components/links.html" import content_link %}
{% from "components/terms.html" import tou_heading_list %}
{% from "components/notice.html" import notice %}

{% macro tou_prompt() %}

<h1 class="heading-large mt-0 mb-4" tabindex="0" autofocus>{{ _('Before you continue') }}</h1>
<p>{{ _('GC Notify always shows your local time, except where we state otherwise.') }}</p>

{% if config["FF_CARETAKER"] %}
{% call notice(type="warning", title=_("Communicating during caretaker period"), headingLevel=2) %}
{{ _("Some communications are prohibited during a federal election. Check with your communications branch if you’re not sure you can send an announcement or other message. For more information, read the <a href='https://www.canada.ca/en/privy-council/services/publications/guidelines-conduct-ministers-state-exempt-staff-public-servants-election.html'>Guidelines</a>") }}
{% endcall %}
{% endif %}

<div class="mt-16 mb-10">
<h2 class="heading-medium">{{ _('Review your activity') }}</h1>
<p>{{ _('GC Notify always shows your local time, except where we state otherwise.') }}</p>
<p>{{ _('Review our records of your last 3 sign in times:') }}</p>
<table>
<tbody>
Expand Down
37 changes: 37 additions & 0 deletions app/templates/views/storybook/notice.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% from "components/notice.html" import notice %}

<div class="heading-large">Notice Component Examples</div>

<hr />

<h2 class="heading-medium">Info Notice - Basic</h2>
{% call notice(type="info", title="System Maintenance Scheduled", headingLevel=3) %}
Scheduled maintenance will occur on July 1st from 2:00 AM to 4:00 AM EDT.
{% endcall %}

<h2 class="heading-medium">Success Notice - with HTML</h2>
{% call notice(type="success", title="API Documentation Updated", headingLevel=3) %}
New endpoints are available. Visit the <a href="#">API documentation</a> to learn more.
{% endcall %}

<h2 class="heading-medium">Warning Notice - Long Content</h2>
{% call notice(type="warning", title="Trial Mode Limitations", headingLevel=3) %}
<p>Your service is in trial mode, which means:</p>
<ul class="list list-bullet ml-10">
<li>You can only send messages to registered team members</li>
<li>Daily message limit is capped at 50 messages</li>
<li>Templates are limited to test content only</li>
</ul>
{% endcall %}

<h2 class="heading-medium">Error Notice - Multiple Paragraphs</h2>
{% call notice(type="error", title="Failed to Upload Template", headingLevel=3) %}
<p>The CSV file could not be processed due to formatting errors.</p>
<p>Common issues include:</p>
<ul class="list list-bullet ml-10">
<li>Missing required columns</li>
<li>Invalid phone numbers</li>
<li>Duplicate entries</li>
</ul>
<p>Please review the <a href="#">template guide</a> and try again.</p>
{% endcall %}
4 changes: 4 additions & 0 deletions app/templates/views/storybook/storybook-menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
'name': 'Remaining Messages',
'path': 'remaining-messages'
},
{
'name': 'Notice component',
'path': 'notice'
}
] %}
<ul class="list list-bullet ml-10">
<!-- loop through components -->
Expand Down
5 changes: 5 additions & 0 deletions app/translations/csv/fr.csv
Original file line number Diff line number Diff line change
Expand Up @@ -2103,6 +2103,11 @@
"SVG Images only!","Images SVG uniquement"
"English Government of Canada signature and custom logo","Signature du gouvernement du Canada en anglais et logo personnalisé"
"Upload a PNG logo","Téléverser un logo PNG"
"Some communications are prohibited during a federal election. Check with your communications branch if you’re not sure you can send an announcement or other message. For more information, read the <a href='https://www.canada.ca/en/privy-council/services/publications/guidelines-conduct-ministers-state-exempt-staff-public-servants-election.html'>Guidelines</a>","Certaines communications sont interdites en période d’élection fédérale. Si vous n’avez pas la certitude de pouvoir envoyer une annonce ou un message, veuillez vous renseigner auprès de la direction des communications de votre organisme. Pour en savoir plus, consultez les lignes <a href='https://www.canada.ca/fr/conseil-prive/services/publications/lignes-directrices-regissant-conduite-ministres-etat-membres-personnel-exonere-fonctionnaires-periode-electorale.html'>directrices</a>"
"Communicating during caretaker period","Communications durant la période de transition"
"Information","Information"
"Success","Succès"
"Warning","Avertissement"
"To join an existing service, check your email for an invitation from the team for that service. GC Notify staff cannot send these invitations.","Pour vous joindre à un service existant, cherchez dans vos courriels une invitation de l’équipe pour ce service. L’équipe de Notification GC ne peut pas envoyer ces invitations."
"If you do not have an invitation","Si vous n’avez pas reçu d’invitation"
"Ask which team members have permission to invite you. If the team is unsure, from a GC Notify account visit the main menu and select “Team members.” That page:","Demandez quels ou quelles membres de l’équipe ont la permission de vous inviter. Si l’équipe n’en est pas certaine, utilisez un compte Notification GC pour visiter le menu principal et rendez-vous dans la section « Votre équipe ». Cette page comprend :"
Expand Down
7 changes: 7 additions & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ module.exports = {
hover: "#990c1a",
border: "#6a0812",
mellow: "#df3034",
light: "#fef6f6",
300: "#D74D42",
},
white: "#FFF",
blue: {
light: "#f5f9ff",
lighter: "#B2E3FF",
DEFAULT: "#213045",
border: "#1A3152",
Expand Down Expand Up @@ -103,6 +105,7 @@ module.exports = {
lightgrey: "#C0C1C3",
visitedlight: "#929AA4",
visiteddark: "#C8CDD1",
light: "#f5fff9",
/* trying to slowly implement a more consistent scale below */
100: "#F0F2F5",
200: "#CFD5DD",
Expand Down Expand Up @@ -132,6 +135,10 @@ module.exports = {
100: "#D3E766",
700: "#545E00",
},
orange: {
light: "#fef9f6",
DEFAULT: "#C86117",
}
},
extend: {
animation: {
Expand Down
28 changes: 28 additions & 0 deletions tests_cypress/cypress/Notify/Admin/Components/Notice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Root element for scoping component interactions
const Root = (selector = '') => {
if (typeof selector === 'number') {
return cy.getByTestId('notice').eq(selector);
}
return cy.get(`[data-testid="notice"].notice-${selector}`);
};


// Parts of the component a user can interact with
let Components = {
Root,
Icon: (selector) => Root(selector).find('[data-testid="notice-icon"] svg'),
IconText: (selector) => Root(selector).find('[data-testid="notice-icon-text"]'),
Heading: (selector) => Root(selector).find('[data-testid="notice-heading"]'),
Message: (selector) => Root(selector).find('[data-testid="notice-message"]'),
};

// Actions users can take on the component
let Actions = {
};

let Notice = {
Components,
...Actions
};

export default Notice;
1 change: 1 addition & 0 deletions tests_cypress/cypress/e2e/admin/ci.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ import "./template/create-template.cy";
import "./template/edit-template.cy";
import "./template/text-direction.cy";
import "./components/remaining_messages_summary.cy";
import "./components/notice.cy";
52 changes: 52 additions & 0 deletions tests_cypress/cypress/e2e/admin/components/notice.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Notice from "../../../Notify/Admin/Components/Notice";

let PageURL = "/_storybook?component=notice";

describe("Notice component", () => {
it("Should pass a11y checks", () => {
cy.a11yScan(PageURL, {
a11y: true,
htmlValidate: true,
deadLinks: false,
mimeTypes: false,
});
});

it("Should use the correct icon depending on type", () => {
cy.visit(PageURL);

// info notice component should be info
Notice.Components.Icon("info").should("have.class", "fa-circle-info");

// success notice component should be success
Notice.Components.Icon("success").should("have.class", "fa-circle-check");

// warning notice component should be warning
Notice.Components.Icon("warning").should(
"have.class",
"fa-circle-exclamation",
);

// error notice component should be error
Notice.Components.Icon("error").should(
"have.class",
"fa-triangle-exclamation",
);
});

it("Should the correct alternative text for the icon", () => {
cy.visit(PageURL);

// info notice component should have alt text "Information"
Notice.Components.IconText("info").should("have.text", "Information: ");

// success notice component should have alt text "Success"
Notice.Components.IconText("success").should("have.text", "Success: ");

// warning notice component should have alt text "Warning"
Notice.Components.IconText("warning").should("have.text", "Warning: ");

// error notice component should have alt text "Error"
Notice.Components.IconText("error").should("have.text", "Error: ");
});
});

0 comments on commit bbd5c41

Please sign in to comment.