Skip to content

Commit

Permalink
feat(ks-handler): add UI for creating youth application without SSN
Browse files Browse the repository at this point in the history
TODO:
 - YJDH-701: refactor to reduce code duplication
 - YJDH-702: add UI component & browser tests

also:
 - add link to handler UI's header to the page for creating a youth
   application without a social security number
 - add link from backend's excel-download page to the handler UI page
   for creating a youth application without a social security number,
   this was done with request from Kesäseteli's product owner
 - add footer to handler UI
 - fix types
   - change non_vtj_birthdate from Date to string

refs YJDH-697
  • Loading branch information
karisal-anders committed May 6, 2024
1 parent 2e7806b commit e46958c
Show file tree
Hide file tree
Showing 29 changed files with 989 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
</div>
{% endif %}


<div class="col-md-12 text-end mt-2">
<a href="{{ handler_create_application_without_ssn_url }}" class="btn btn-link btn-md">
{% trans 'Luo hakemus ilman henkilötunnusta' %}
</a>
<a href="{% url 'logout' %}" class="btn btn-info btn-md">{% trans 'Kirjaudu ulos' %}</a>
</div>

Expand Down
7 changes: 7 additions & 0 deletions backend/kesaseteli/applications/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
)
from applications.models import EmployerSummerVoucher, YouthApplication
from common.decorators import enforce_handler_view_adfs_login
from common.urls import handler_create_application_without_ssn_url
from shared.audit_log.viewsets import AuditLoggingModelViewSet


Expand All @@ -41,6 +42,12 @@ class EmployerApplicationExcelDownloadView(TemplateView):

template_name = "application_excel_download.html"

def get_context_data(self, **kwargs):
return super().get_context_data(
**kwargs,
handler_create_application_without_ssn_url=handler_create_application_without_ssn_url(),
)

@staticmethod
def base_queryset(filter_pks=None) -> QuerySet[EmployerSummerVoucher]:
newest_submitted = EmployerSummerVoucher.history.filter(
Expand Down
28 changes: 27 additions & 1 deletion backend/kesaseteli/common/tests/test_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import pytest

from common.urls import handler_403_url, handler_youth_application_processing_url
from common.urls import (
handler_403_url,
handler_create_application_without_ssn_url,
handler_youth_application_processing_url,
)


@pytest.mark.parametrize(
Expand All @@ -19,6 +23,28 @@ def test_handler_403_url(settings, handler_url, expected_result):
assert handler_403_url() == expected_result


@pytest.mark.parametrize(
"handler_url, expected_result",
[
("https://example.org", "https://example.org/create-application-without-ssn"),
("https://example.com/", "https://example.com/create-application-without-ssn"),
(
"https://test.org:3200",
"https://test.org:3200/create-application-without-ssn",
),
(
"https://test.example.com:80/",
"https://test.example.com:80/create-application-without-ssn",
),
],
)
def test_handler_create_application_without_ssn_url(
settings, handler_url, expected_result
):
settings.HANDLER_URL = handler_url
assert handler_create_application_without_ssn_url() == expected_result


@pytest.mark.parametrize(
"handler_url, pk, expected_result",
[
Expand Down
8 changes: 8 additions & 0 deletions backend/kesaseteli/common/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ def handler_403_url():
return urljoin(settings.HANDLER_URL, "/fi/403")


def handler_create_application_without_ssn_url():
"""
Get handlers' URL "create youth application without social security number" page URL.
:return: URL for handlers' "create youth application without social security number" page.
"""
return urljoin(settings.HANDLER_URL, "/create-application-without-ssn")


def handler_youth_application_processing_url(youth_application_pk):
"""
Get handlers' youth application processing URL.
Expand Down
1 change: 1 addition & 0 deletions frontend/kesaseteli/employer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"start": "NODE_ENV=production PORT=3000 node ../../shared/src/server/next-server.js",
"lint": "eslint --ext js,ts,tsx src browser-tests",
"pre-commit": "lint-staged -c ../../.lintstagedrc.js",
"typecheck": "tsc --project ./tsconfig.json --noEmit",
"test": "jest --runInBand --no-cache",
"test:debug-nock": "cross-env DEBUG=nock.* yarn test",
"test:debug-dom": "cross-env DEBUG_PRINT_LIMIT=1000000 yarn test",
Expand Down
1 change: 1 addition & 0 deletions frontend/kesaseteli/handler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"start": "NODE_ENV=production PORT=3200 node ../../shared/src/server/next-server.js",
"lint": "eslint --ext js,ts,tsx src browser-tests",
"pre-commit": "lint-staged -c ../../.lintstagedrc.js",
"typecheck": "tsc --project ./tsconfig.json --noEmit",
"test": "jest --runInBand --no-cache",
"test:debug-nock": "cross-env DEBUG=nock.* yarn test",
"test:debug-dom": "cross-env DEBUG_PRINT_LIMIT=1000000 yarn test",
Expand Down
58 changes: 57 additions & 1 deletion frontend/kesaseteli/handler/public/locales/fi/common.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
{
"appName": "Kesäsetelin käsittelijäliittymä",
"header": {
"createApplicationWithoutSsnLabel": "Luo hakemus ilman henkilötunnusta",
"linkSkipToContent": "Siirry sisältöön",
"menuToggleAriaLabel": "Valikko",
"languageMenuButtonAriaLabel": "Valitse kieli"
},
"footer": {
"copyrightText": "Helsingin kaupunki",
"allRightsReservedText": "Kaikki oikeudet pidätetään"
},
"errorPage": {
"title": "Tapahtui odottamaton virhe",
"message": "Jokin meni pieleen. Sivun uudelleenlataaminen saattaa auttaa.",
Expand Down Expand Up @@ -94,9 +99,60 @@
"submit": "Hylkää"
}
},
"applications" : {
"applications": {
"actions": {
"close": "Sulje"
}
},
"applicationWithoutSsn": {
"form": {
"title": "Kesäsetelin tiedot",
"info": "Täytäthän kaikki tiedot mahdollisimman paikkansapitävästi",
"requiredInfo": "Kaikki * merkityt kentät ovat pakollisia julkaisuun",
"sendButton": "Lähetä tiedot",
"usageInfo": "Käytä tätä lomaketta vain, jos henkilöllä ei ole pysyvää suomalaista henkilötunnusta!",
"emailCheckAlert": "Tarkista ennen lähettämistä, että sähköpostiosoite on oikein! Jos se on väärin, niin kesäseteli ei välity henkilölle!",
"helpers": {
"date": "Käytä muotoa P.K.VVVV",
"format": "Käytä muotoa"
},
"errors": {
"required": "Tieto puuttuu tai on virheellinen",
"maxLength": "Syöttämäsi tieto on liian pitkä",
"pattern": "Syöttämäsi tieto on virheellistä muotoa",
"validate": "Syöttämäsi tieto on virheellistä muotoa",
"selectionGroups": "Valitse jokin vaihtoehto"
},
"inputs": {
"firstName": "Etunimi",
"lastName": "Sukunimi",
"nonVtjBirthdate": "Syntymäpäivä",
"email": "Sähköpostiosoite",
"school": "Koulu",
"phoneNumber": "Puhelinnumero",
"postcode": "Postinumero",
"language": "Kieli",
"nonVtjHomeMunicipality": "Kotikunta",
"additionalInfoDescription": "Lisätiedot"
},
"checkNotification": {
"label": "Hups! Jokin tiedoista ei ole oikein",
"validation": "Tarkista seuraavat kentät: {{ fields}}"
},
"selectionGroups": {
"language": {
"fi": "Suomi",
"sv": "Ruotsi",
"en": "Englanti"
}
}
}
},
"notificationPages": {
"thankyou": {
"title": "Hienoa! Olet lähettänyt tiedot kesäsetelijärjestelmään",
"message": "Hakemuksen käsittelylinkki löytyy käsittelijöiden sähköpostista.",
"processingLinkLabel": "KÄSITTELE"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import DateInput from 'kesaseteli/handler/components/form/DateInput';
import SelectionGroup from 'kesaseteli/handler/components/form/SelectionGroup';
import SubmitErrorSummary from 'kesaseteli/handler/components/form/SubmitErrorSummary';
import TextInput from 'kesaseteli/handler/components/form/TextInput';
import useHandleApplicationWithoutSsnSubmit from 'kesaseteli/handler/hooks/application/useHandleApplicationWithoutSsnSubmit';
import useCreateYouthApplicationWithoutSsnQuery from 'kesaseteli/handler/hooks/backend/useCreateYouthApplicationWithoutSsnQuery';
import { useTranslation } from 'next-i18next';
import React from 'react';
import SaveFormButton from 'shared/components/forms/buttons/SaveFormButton';
import { $GridCell } from 'shared/components/forms/section/FormSection.sc';
import $Notification from 'shared/components/notification/Notification.sc';
import {
EMAIL_REGEX,
NAMES_REGEX,
PHONE_NUMBER_REGEX,
POSTAL_CODE_REGEX,
} from 'shared/constants';
import { SUPPORTED_LANGUAGES } from 'shared/i18n/i18n';

const CreateApplicationWithoutSsnForm: React.FC = () => {
const { t } = useTranslation();

const submitQuery = useCreateYouthApplicationWithoutSsnQuery();
const { handleSaveSuccess, handleErrorResponse, submitError } =
useHandleApplicationWithoutSsnSubmit();

return (
<>
<$Notification
label={t('common:applicationWithoutSsn.form.usageInfo')}
type="info"
/>
{submitError && (
<$GridCell $colSpan={2}>
<SubmitErrorSummary error={submitError} />
</$GridCell>
)}
<SelectionGroup
id="language"
validation={{ required: true }}
direction="horizontal"
values={
/* Prioritize English for immigrants/refugees by sorting i.e. ["en", "fi", "sv"] */
[...SUPPORTED_LANGUAGES].sort()
}
$colSpan={2}
/>
<TextInput
id="firstName"
validation={{ required: true, pattern: NAMES_REGEX, maxLength: 128 }}
autoComplete="off"
/>
<TextInput
id="lastName"
validation={{ required: true, pattern: NAMES_REGEX, maxLength: 128 }}
autoComplete="off"
/>
<DateInput id="nonVtjBirthdate" validation={{ required: true }} />
<TextInput
id="postcode"
type="number"
validation={{
required: true,
pattern: POSTAL_CODE_REGEX,
maxLength: 5,
}}
autoComplete="off"
/>
<TextInput
id="school"
validation={{ required: true, pattern: NAMES_REGEX, maxLength: 256 }}
$colSpan={2}
autoComplete="off"
/>
<TextInput
id="phoneNumber"
validation={{
required: true,
pattern: PHONE_NUMBER_REGEX,
maxLength: 64,
}}
autoComplete="off"
/>
<TextInput
id="email"
validation={{ required: true, pattern: EMAIL_REGEX, maxLength: 254 }}
autoComplete="off"
/>
<TextInput
id="additionalInfoDescription"
validation={{ required: true, maxLength: 4096 }}
autoComplete="off"
type="textArea"
$colSpan={2}
/>
<TextInput
id="nonVtjHomeMunicipality"
validation={{ required: false, pattern: NAMES_REGEX, maxLength: 64 }}
autoComplete="off"
$colSpan={2}
/>
<$Notification
label={t('common:applicationWithoutSsn.form.emailCheckAlert')}
type="alert"
/>
<$GridCell $colSpan={2}>
<SaveFormButton
saveQuery={submitQuery}
onSuccess={handleSaveSuccess}
onError={handleErrorResponse}
onInvalidForm={(errors) => {
// this helps debugging when react tests fail
if (process.env.NODE_ENV === 'test') {
// eslint-disable-next-line no-console
console.error('invalid form', errors);
}
}}
>
{t(`common:applicationWithoutSsn.form.sendButton`)}
</SaveFormButton>
</$GridCell>
</>
);
};

export default CreateApplicationWithoutSsnForm;
Loading

0 comments on commit e46958c

Please sign in to comment.