diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 1758ee798fde243f90dfa7847318b8949eeda5e9..26c3f0a8f2ed9fe251e2970efe78734030ec6e0c 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -20,7 +20,9 @@ "email": "E-mail", "fullName": "Full name", "mobilePhone": "Mobile phone", - "passportNumber": "Passport number" + "passportNumber": "Passport number", + "passportNationality": "Passport nationality", + "countryCallingCode": "Country code" }, "sponsor": { "contactInfo": "Contact information", @@ -64,7 +66,8 @@ "emailRequired": "E-mail is required", "invalidMobilePhoneNumber": "Invalid phone number", "invalidEmail": "Invalid e-mail address", - "passportNumberRequired": "Passport number required" + "passportNumberRequired": "Passport number required", + "mobilePhoneRequired": "Mobile phone is required" }, "button": { "back": "Back", diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json index 7d5725f41ddb9b722a994d21d8c1d7e4fbb67cc3..82dea3fab69a8396b24ed7ee83ae2d3cdbbaad7b 100644 --- a/frontend/public/locales/nb/common.json +++ b/frontend/public/locales/nb/common.json @@ -20,7 +20,9 @@ "email": "E-post", "fullName": "Fullt navn", "mobilePhone": "Mobilnummer", - "passportNumber": "Passport number" + "passportNumber": "Passnummer", + "passportNationality": "Passnasjonalitet", + "countryCallingCode": "Landkode" }, "sponsor": { "contactInfo": "Kontaktinformasjon", @@ -63,7 +65,8 @@ "emailRequired": "E-post er obligatorisk", "invalidMobilePhoneNumber": "Ugyldig telefonnummer", "invalidEmail": "Ugyldig e-postadresse", - "passportNumberRequired": "Passnummer er obligatorisk" + "passportNumberRequired": "Passnummer er obligatorisk", + "mobilePhoneRequired": "Mobilnummer er obligatorisk" }, "button": { "back": "Tilbake", diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json index b9b4342e9df4a966796a4b133d85ab805ca6a286..1d6b0f8f743dbad1a2820b16389643c84f3083e8 100644 --- a/frontend/public/locales/nn/common.json +++ b/frontend/public/locales/nn/common.json @@ -21,7 +21,9 @@ "email": "E-post", "fullName": "Fullt namn", "mobilePhone": "Mobilnummer", - "passportNumber": "Passport number" + "passportNumber": "Passnummer", + "passportNationality": "Passnasjonalitet", + "countryCallingCode": "Landkode" }, "sponsor": { "contactInfo": "Kontaktinformasjon", @@ -64,7 +66,8 @@ "emailRequired": "E-post er obligatorisk", "invalidMobilePhoneNumber": "Ugyldig telefonnummer", "invalidEmail": "Ugyldig e-postadresse", - "passportNumberRequired": "Passnummer er obligatorisk" + "passportNumberRequired": "Passnummer er obligatorisk", + "mobilePhoneRequired": "Mobilnummer er obligatorisk" }, "button": { "back": "Tilbake", diff --git a/frontend/src/routes/guest/register/enteredGuestData.ts b/frontend/src/routes/guest/register/enteredGuestData.ts index 73b19b6600c0ea9d06b687986e56d2228a009e99..8e4cdb6669a170abdbf636c0948cfecefe17c309 100644 --- a/frontend/src/routes/guest/register/enteredGuestData.ts +++ b/frontend/src/routes/guest/register/enteredGuestData.ts @@ -7,4 +7,5 @@ export type EnteredGuestData = { mobilePhone: string nationalIdNumber?: string passportNumber?: string + passportNationality?: string } diff --git a/frontend/src/routes/guest/register/guestDataForm.ts b/frontend/src/routes/guest/register/guestDataForm.ts index 44ab98237c271f3724206f00fe4891cae7278f88..cb52a87bec8507b4077dbaf17dcce03fc9e9aa82 100644 --- a/frontend/src/routes/guest/register/guestDataForm.ts +++ b/frontend/src/routes/guest/register/guestDataForm.ts @@ -21,6 +21,7 @@ export type ContactInformationBySponsor = { mobile_phone?: string fnr?: string passport?: string + passportNationality?: string authentication_method: AuthenticationMethod } diff --git a/frontend/src/routes/guest/register/index.tsx b/frontend/src/routes/guest/register/index.tsx index 23c4864ab59200324feb92ddf4227d8b6bc87cb9..c0f75c2828f42cd3af8fab6fc471db2bedd3ee06 100644 --- a/frontend/src/routes/guest/register/index.tsx +++ b/frontend/src/routes/guest/register/index.tsx @@ -11,7 +11,7 @@ import { GuestRegisterCallableMethods } from './guestRegisterCallableMethods' import { EnteredGuestData } from './enteredGuestData' import { ContactInformationBySponsor } from './guestDataForm' import AuthenticationMethod from './authenticationMethod' -import { postJsonOpts } from '../../../utils' +import { submitJsonOpts } from '../../../utils' enum SubmitState { NotSubmitted, @@ -51,9 +51,12 @@ export default function GuestRegister() { mobile_phone: '', fnr: '', passport: '', + passportNationality: '', authentication_method: AuthenticationMethod.Invite, }) + const [errorState, setErrorState] = useState<string>('') + const guestContactInfo = async () => { try { const response = await fetch('/api/ui/v1/invited') @@ -82,6 +85,8 @@ export default function GuestRegister() { mobile_phone: jsonResponse.person.mobile_phone, fnr: jsonResponse.fnr, passport: jsonResponse.passport, + // TODO Separate out nationality based on what is in the server response + passportNationality: '', authentication_method: authenticationMethod, }) @@ -112,15 +117,35 @@ export default function GuestRegister() { const handleForwardFromRegister = ( updateFormData: EnteredGuestData ): void => { - const payload = { - person: { - mobile_phone: updateFormData.mobilePhone, - fnr: updateFormData.nationalIdNumber, - passport: updateFormData.passportNumber, - }, + const payload: any = {} + payload.person = {} + payload.person.mobile_phone = updateFormData.mobilePhone + + if (updateFormData.passportNumber && updateFormData.passportNationality) { + // The user has entered some passport information, check that both nationality and number are present + if ( + (updateFormData.passportNumber && + !updateFormData.passportNationality) || + (!updateFormData.passportNumber && updateFormData.passportNationality) + ) { + // TODO Make better text and use translation + setErrorState( + 'Both passport nationality and passport number need to be set' + ) + return + } + setErrorState('') + payload.person.passport = `${updateFormData.passportNationality}-${updateFormData.passportNumber}` + } + + if (updateFormData.nationalIdNumber) { + payload.person.fnr = updateFormData.nationalIdNumber } - fetch('/api/ui/v1/invited/', postJsonOpts(payload)) + // TODO Remove after development + console.log(`Payload: ${JSON.stringify(payload)}`) + + fetch('/api/ui/v1/invited/', submitJsonOpts('POST', payload)) .then((response) => { if (response.ok) { setSubmitState(SubmitState.Submitted) @@ -130,8 +155,8 @@ export default function GuestRegister() { } }) .catch((error) => { - console.error(error) setSubmitState(SubmitState.SubmittedError) + console.error(error) }) } @@ -177,6 +202,9 @@ export default function GuestRegister() { {/* TODO This button is only for testing while developing */} <Button onClick={handleSave}>{t('button.save')}</Button> </Box> + + {/* TODO Give better feedback to user */} + {errorState && <h2>{errorState}</h2>} </Page> ) } diff --git a/frontend/src/routes/guest/register/registerPage.tsx b/frontend/src/routes/guest/register/registerPage.tsx index 32180f46f4b04bdcde55775d5e272641b8d21a16..a8f05d5ec1ef9ce4f2c32963f444fc8e3220db07 100644 --- a/frontend/src/routes/guest/register/registerPage.tsx +++ b/frontend/src/routes/guest/register/registerPage.tsx @@ -12,10 +12,11 @@ import React, { forwardRef, Ref, useImperativeHandle, useState } from 'react' import { useTranslation } from 'react-i18next' import { CountryCallingCode, + CountryCode, getCountries, getCountryCallingCode, } from 'libphonenumber-js' -import { getName } from 'i18n-iso-countries' +import { getAlpha2Codes, getName } from 'i18n-iso-countries' import { ContactInformationBySponsor } from './guestDataForm' import { EnteredGuestData } from './enteredGuestData' import { GuestRegisterCallableMethods } from './guestRegisterCallableMethods' @@ -65,7 +66,7 @@ const GuestRegisterStep = forwardRef( setValue, trigger, formState: { errors }, - } = useForm() + } = useForm<EnteredGuestData>() const onSubmit = handleSubmit<EnteredGuestData>(submit) function doSubmit() { @@ -73,7 +74,7 @@ const GuestRegisterStep = forwardRef( } register('mobilePhone', { - required: t<string>('validation.roleTypeRequired'), + required: t<string>('validation.mobilePhoneRequired'), validate: isValidMobilePhoneNumber, }) @@ -112,7 +113,7 @@ const GuestRegisterStep = forwardRef( <TextField id="email" label={t('input.email')} - value={guestData.email} + value={!guestData.email ? '' : guestData.email} disabled /> @@ -120,7 +121,6 @@ const GuestRegisterStep = forwardRef( sx={{ display: 'flex', flexDirection: 'row', - paddingBottom: '2rem', }} > <Select @@ -131,12 +131,26 @@ const GuestRegisterStep = forwardRef( }} labelId="phone-number-select" id="phone-number-select" + displayEmpty + defaultValue="" onChange={handleCountryCodeChange} + renderValue={(selected: any) => { + if (selected.length === 0 || selected === '') { + return t('input.countryCallingCode') + } + return `${getName( + selected, + i18n.language + )} (${getCountryCallingCode(selected as CountryCode)})` + }} > + <MenuItem disabled value=""> + {t('input.countryCallingCode')} + </MenuItem> {getCountries().map((country) => ( <MenuItem key={country} value={country}> - {getName(country, i18n.language)} + - {getCountryCallingCode(country)} + {getName(country, i18n.language)} ( + {getCountryCallingCode(country)}) </MenuItem> ))} </Select> @@ -160,30 +174,64 @@ const GuestRegisterStep = forwardRef( }} /> </Box> - {guestData.authentication_method === AuthenticationMethod.Invite && ( <> - <TextField - id="passport" - data-testid="passport_number_input" - label={t('input.passportNumber')} - {...register('passportNumber', { - required: t<string>('validation.passportNumberRequired'), - })} - /> - + {/* The guest should fill in one of national ID number or passport number */} <TextField id="national_id_number" label={t('input.nationalIdNumber')} - error={!!errors.national_id_number} + error={!!errors.nationalIdNumber} helperText={ errors.nationalIdNumber && errors.nationalIdNumber.message } {...register('nationalIdNumber', { - validate: isValidFnr, + // It is not required that the national ID number be filled in, the guest may not have + // one, so allow empty values for the validation to pass + validate: (value) => isValidFnr(value, true), })} /> + + <TextField + id="passport-number-id" + data-testid="passport_number_input" + label={t('input.passportNumber')} + {...register('passportNumber', { + required: false, + })} + /> + + <Select + sx={{ + maxHeight: '2.5rem', + }} + id="passport-nationality-id" + labelId="passport-nationality-label" + label={t('input.passportNationality')} + displayEmpty + defaultValue="" + {...register('passportNationality', { + required: false, + })} + renderValue={(selected: any) => { + if (!selected || selected.length === 0) { + return t('input.passportNationality') + } + return getName(selected, i18n.language) + }} + > + <MenuItem disabled value=""> + {t('input.passportNationality')} + </MenuItem> + {Object.keys(getAlpha2Codes()).map((countryAlpha2Code) => ( + <MenuItem + key={countryAlpha2Code} + value={countryAlpha2Code} + > + {getName(countryAlpha2Code, i18n.language)} + </MenuItem> + ))} + </Select> </> )} diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index f5a45b7f11337b9db62db3be61b757b7e99500a8..528734dd17370f1f3149f061a59a1a15d5a6e631 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -43,8 +43,14 @@ export function submitJsonOpts(method: string, data: object): RequestInit { } } -export function isValidFnr(data: string | undefined): boolean | string { - if (data === undefined) { +export function isValidFnr( + data: string | undefined, + allowEmpty = false +): boolean | string { + if (!data) { + if (allowEmpty) { + return true + } return i18n.t<string>('common:validation.invalidIdNumber').toString() } const valid = validator.idnr(data as string).status === 'valid'