diff --git a/frontend/.env.example b/frontend/.env.example index d4142c8e..cf1f21eb 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -187,4 +187,6 @@ AZUREAD_CLIENT_SECRET= # Power Platform lanuage code for English type (default: 1033) PP_ENGLISH_LANGUAGE_CODE= # Power Platform lanuage code for French type (default: 1036) -PP_FRENCH_LANGUAGE_CODE= \ No newline at end of file +PP_FRENCH_LANGUAGE_CODE= +# Power Platform lanuage code for Canada Country Code (default: '0cf5389e-97ae-eb11-8236-000d3af4bfc3') +PP_CANADA_COUNTRY_CODE= \ No newline at end of file diff --git a/frontend/app/.server/environment/power-platform.ts b/frontend/app/.server/environment/power-platform.ts index 4dc3a61d..69da672e 100644 --- a/frontend/app/.server/environment/power-platform.ts +++ b/frontend/app/.server/environment/power-platform.ts @@ -7,9 +7,11 @@ export type PowerPlatform = Readonly>; export const defaults = { PP_ENGLISH_LANGUAGE_CODE: '1033', PP_FRENCH_LANGUAGE_CODE: '1036', + PP_CANADA_COUNTRY_CODE: '0cf5389e-97ae-eb11-8236-000d3af4bfc3', } as const; export const powerPlatform = v.object({ PP_ENGLISH_LANGUAGE_CODE: v.optional(v.pipe(stringToIntegerSchema()), defaults.PP_ENGLISH_LANGUAGE_CODE), PP_FRENCH_LANGUAGE_CODE: v.optional(v.pipe(stringToIntegerSchema()), defaults.PP_FRENCH_LANGUAGE_CODE), + PP_CANADA_COUNTRY_CODE: v.optional(v.string(), defaults.PP_CANADA_COUNTRY_CODE), }); diff --git a/frontend/app/.server/locales/protected-en.json b/frontend/app/.server/locales/protected-en.json index 400674b1..4423c6bc 100644 --- a/frontend/app/.server/locales/protected-en.json +++ b/frontend/app/.server/locales/protected-en.json @@ -240,7 +240,8 @@ "address-help-message": "Include apartment number (if applicable), street number, street name. For example: 123 Main St. Suite 4B", "postal-code-label": "Postal code", "city-label": "City, town, or village", - "province-label": "Province, state, or region", + "canada-province-label": "Province or territory", + "other-country-province-label": "Province, state, or region", "error-messages": { "preferred-language-required": "Please select a preferred language", "primary-phone-required": "Please enter a primary phone number", diff --git a/frontend/app/.server/locales/protected-fr.json b/frontend/app/.server/locales/protected-fr.json index ca3e78b8..e2bf2f06 100644 --- a/frontend/app/.server/locales/protected-fr.json +++ b/frontend/app/.server/locales/protected-fr.json @@ -241,7 +241,8 @@ "address-help-message": "Include apartment number (if applicable), street number, street name. For example: 123 Main St. Suite 4B", "postal-code-label": "Postal code", "city-label": "City, town, or village", - "province-label": "Province, state, or region", + "canada-province-label": "Province or territory", + "other-country-province-label": "Province, state, or region", "error-messages": { "preferred-language-required": "Please select a preferred language", "primary-phone-required": "Please enter a primary phone number", diff --git a/frontend/app/.server/services/locale-data-service.ts b/frontend/app/.server/services/locale-data-service.ts index 7a891193..d5371c62 100644 --- a/frontend/app/.server/services/locale-data-service.ts +++ b/frontend/app/.server/services/locale-data-service.ts @@ -29,32 +29,31 @@ export function getLocalizedCountries(locale: Language = 'en'): readonly Localiz })); } -type ProvinceTerritoryState = Readonly<{ +type ProvinceTerritory = Readonly<{ id: string; - countryId: string; nameEn: string; nameFr: string; }>; -export function getProvincesTerritoriesStates(): readonly ProvinceTerritoryState[] { - return provinceTerritoryStateData.value.map((region) => ({ - id: region.esdc_provinceterritorystateid, - countryId: region._esdc_countryid_value, - nameEn: region.esdc_nameenglish, - nameFr: region.esdc_namefrench, - })); +export function getProvincesTerritories(): readonly ProvinceTerritory[] { + const { PP_CANADA_COUNTRY_CODE } = serverEnvironment; + return provinceTerritoryStateData.value + .filter((region) => region._esdc_countryid_value === PP_CANADA_COUNTRY_CODE) + .map((region) => ({ + id: region.esdc_provinceterritorystateid, + nameEn: region.esdc_nameenglish, + nameFr: region.esdc_namefrench, + })); } -type LocalizedProvinceTerritoryState = Readonly<{ +type LocalizedProvinceTerritory = Readonly<{ id: string; - countryId: string; name: string; }>; -export function getLocalizedProvincesTerritoriesStates(locale: Language = 'en'): readonly LocalizedProvinceTerritoryState[] { - return getProvincesTerritoriesStates().map((region) => ({ +export function getLocalizedProvincesTerritoriesStates(locale: Language = 'en'): readonly LocalizedProvinceTerritory[] { + return getProvincesTerritories().map((region) => ({ id: region.id, - countryId: region.countryId, name: region[locale === 'en' ? 'nameEn' : 'nameFr'], })); } diff --git a/frontend/app/routes/protected/person-case/contact-information.tsx b/frontend/app/routes/protected/person-case/contact-information.tsx index dac35c1d..6b5adac6 100644 --- a/frontend/app/routes/protected/person-case/contact-information.tsx +++ b/frontend/app/routes/protected/person-case/contact-information.tsx @@ -1,4 +1,4 @@ -import { useId } from 'react'; +import { useId, useState } from 'react'; import type { RouteHandle, SessionData } from 'react-router'; import { data, useFetcher } from 'react-router'; @@ -8,11 +8,12 @@ import * as v from 'valibot'; import type { Info, Route } from './+types/contact-information'; +import { serverEnvironment } from '~/.server/environment'; import { getCountries, getLocalizedCountries, getLocalizedProvincesTerritoriesStates, - getProvincesTerritoriesStates, + getProvincesTerritories, getPreferredLanguages, getLocalizedPreferredLanguages, } from '~/.server/services/locale-data-service'; @@ -38,6 +39,7 @@ export const handle = { export async function loader({ context, request }: Route.LoaderArgs) { requireAuth(context.session, new URL(request.url), ['user']); const { lang, t } = await getTranslation(request, handle.i18nNamespace); + const { PP_CANADA_COUNTRY_CODE } = serverEnvironment; return { documentTitle: t('protected:contact-information.page-title'), @@ -45,6 +47,7 @@ export async function loader({ context, request }: Route.LoaderArgs) { localizedpreferredLanguages: getLocalizedPreferredLanguages(lang), localizedCountries: getLocalizedCountries(lang), localizedProvincesTerritoriesStates: getLocalizedProvincesTerritoriesStates(lang), + PP_CANADA_COUNTRY_CODE, }; } @@ -56,6 +59,7 @@ export async function action({ context, request }: Route.ActionArgs) { requireAuth(context.session, new URL(request.url), ['user']); const { lang, t } = await getTranslation(request, handle.i18nNamespace); + const { PP_CANADA_COUNTRY_CODE } = serverEnvironment; const formData = await request.formData(); const action = formData.get('action'); @@ -65,37 +69,60 @@ export async function action({ context, request }: Route.ActionArgs) { } case 'next': { - // TODO beef up validation - const schema = v.object({ - preferredLanguage: v.picklist( - getPreferredLanguages().map(({ id }) => id), - t('protected:contact-information.error-messages.preferred-language-required'), - ), - primaryPhoneNumber: v.pipe( - v.string(), - v.trim(), - v.nonEmpty(t('protected:contact-information.error-messages.primary-phone-required')), - ), - secondaryPhoneNumber: v.optional(v.pipe(v.string(), v.trim())), - emailAddress: v.optional( - v.pipe(v.string(), v.trim(), v.email(t('protected:contact-information.error-messages.email-address-invalid-format'))), - ), - country: v.picklist( - getCountries().map(({ id }) => id), - t('protected:contact-information.error-messages.country-required'), - ), - address: v.pipe(v.string(), v.trim(), v.nonEmpty(t('protected:contact-information.error-messages.address-required'))), - postalCode: v.pipe( - v.string(), - v.trim(), - v.nonEmpty(t('protected:contact-information.error-messages.postal-code-required')), - ), - city: v.pipe(v.string(), v.trim(), v.nonEmpty(t('protected:contact-information.error-messages.city-required'))), - province: v.picklist( - getProvincesTerritoriesStates().map(({ id }) => id), - t('protected:contact-information.error-messages.province-required'), - ), - }) satisfies v.GenericSchema; + const schema = v.intersect([ + v.object({ + preferredLanguage: v.picklist( + getPreferredLanguages().map(({ id }) => id), + t('protected:contact-information.error-messages.preferred-language-required'), + ), + primaryPhoneNumber: v.pipe( + v.string(), + v.trim(), + v.nonEmpty(t('protected:contact-information.error-messages.primary-phone-required')), + ), + secondaryPhoneNumber: v.optional(v.pipe(v.string(), v.trim())), + emailAddress: v.optional( + v.pipe( + v.string(), + v.trim(), + v.email(t('protected:contact-information.error-messages.email-address-invalid-format')), + ), + ), + country: v.picklist( + getCountries().map(({ id }) => id), + t('protected:contact-information.error-messages.country-required'), + ), + address: v.pipe(v.string(), v.trim(), v.nonEmpty(t('protected:contact-information.error-messages.address-required'))), + postalCode: v.pipe( + v.string(), + v.trim(), + v.nonEmpty(t('protected:contact-information.error-messages.postal-code-required')), + ), + city: v.pipe(v.string(), v.trim(), v.nonEmpty(t('protected:contact-information.error-messages.city-required'))), + province: v.pipe( + v.string(), + v.trim(), + v.nonEmpty(t('protected:contact-information.error-messages.province-required')), + ), + }), + v.variant('country', [ + v.object({ + country: v.literal(PP_CANADA_COUNTRY_CODE), + province: v.picklist( + getProvincesTerritories().map(({ id }) => id), + t('protected:contact-information.error-messages.province-required'), + ), + }), + v.object({ + country: v.string(), + province: v.pipe( + v.string(), + v.trim(), + v.nonEmpty(t('protected:contact-information.error-messages.province-required')), + ), + }), + ]), + ]) satisfies v.GenericSchema; const input = { preferredLanguage: formData.get('preferredLanguage') as string, @@ -135,6 +162,8 @@ export default function ContactInformation({ loaderData, actionData, params }: R const isSubmitting = fetcher.state !== 'idle'; const errors = fetcher.data?.errors; + const [country, setCountry] = useState(loaderData.defaultFormValues?.country); + const languageOptions = loaderData.localizedpreferredLanguages.map(({ id, name }) => ({ value: id, children: name, @@ -154,7 +183,6 @@ export default function ContactInformation({ loaderData, actionData, params }: R children: id === 'select-option' ? t('protected:contact-information.select-option') : name, })); - // TODO conditionally render different address fields if Canada is selected as a country return (
{t('protected:contact-information.page-title')} @@ -208,42 +236,58 @@ export default function ContactInformation({ loaderData, actionData, params }: R options={countryOptions} errorMessage={errors?.country?.at(0)} defaultValue={loaderData.defaultFormValues?.country} + onChange={({ target }) => setCountry(target.value)} required /> - - - - + {country && ( + <> + + + + {country === loaderData.PP_CANADA_COUNTRY_CODE ? ( + + ) : ( + + )} + + )}