mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
feat: enforce physical characteristics for seafarer profiles and add dynamic validation and form support
This commit is contained in:
@@ -270,13 +270,18 @@ export function LicenseApplicationPage() {
|
|||||||
const nameFallback = accountName?.en
|
const nameFallback = accountName?.en
|
||||||
? splitPersonName(accountName.en)
|
? splitPersonName(accountName.en)
|
||||||
: null;
|
: null;
|
||||||
|
const firstName = profile.firstName || nameFallback?.firstName || "";
|
||||||
|
const middleName = profile.middleName || nameFallback?.middleName || "";
|
||||||
|
const lastName = profile.lastName || nameFallback?.lastName || "";
|
||||||
const context = {
|
const context = {
|
||||||
user: accountUser ?? profile.user,
|
user: accountUser ?? profile.user,
|
||||||
profile: {
|
profile: {
|
||||||
...profile,
|
...profile,
|
||||||
firstName: profile.firstName || nameFallback?.firstName || "",
|
firstName,
|
||||||
middleName: profile.middleName || nameFallback?.middleName || "",
|
middleName,
|
||||||
lastName: profile.lastName || nameFallback?.lastName || "",
|
lastName,
|
||||||
|
// The profile has no single "full name" column — it's first/middle/last.
|
||||||
|
fullName: [firstName, middleName, lastName].filter(Boolean).join(" "),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -291,7 +296,12 @@ export function LicenseApplicationPage() {
|
|||||||
const untouched =
|
const untouched =
|
||||||
current === undefined || current === null || current === "";
|
current === undefined || current === null || current === "";
|
||||||
if (!field.readOnly && !untouched) continue;
|
if (!field.readOnly && !untouched) continue;
|
||||||
const value = readSourcePath(context, source);
|
const raw = readSourcePath(context, source);
|
||||||
|
// Profile dates arrive as ISO datetimes; a DATE field's picker wants
|
||||||
|
// yyyy-MM-dd. Seafarer registration's `profile.dob` source hits the
|
||||||
|
// same mismatch today — fixed once here rather than per config.
|
||||||
|
const value =
|
||||||
|
field.type === "DATE" && typeof raw === "string" ? raw.slice(0, 10) : raw;
|
||||||
if (value === undefined || value === null || value === "") continue;
|
if (value === undefined || value === null || value === "") continue;
|
||||||
if (current === value) continue;
|
if (current === value) continue;
|
||||||
next[section.key] = { ...next[section.key], [field.key]: value };
|
next[section.key] = { ...next[section.key], [field.key]: value };
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ function isAtLeast18(dob: string): boolean {
|
|||||||
return birth <= cutoff;
|
return birth <= cutoff;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const profileSchema = (t: TFunction) =>
|
// Printed on the Seaman Book, so a seafarer account can't leave them blank —
|
||||||
|
// every other account type may. Blood type offers UNKNOWN, so requiring an
|
||||||
|
// answer never forces a claim. Mirrors seafarer-registration.seed-data.ts's
|
||||||
|
// `required: true` on the same fields, and the backend's own check in
|
||||||
|
// ProfileService.assertPhysicalCharacteristicsForSeafarer.
|
||||||
|
export const profileSchema = (t: TFunction, isSeafarer: boolean) =>
|
||||||
z.object({
|
z.object({
|
||||||
professionId: z.string().min(1, t('profileForm.validation.professionRequired')),
|
professionId: z.string().min(1, t('profileForm.validation.professionRequired')),
|
||||||
firstName: z.string().min(3, t('profileForm.validation.firstNameMin')),
|
firstName: z.string().min(3, t('profileForm.validation.firstNameMin')),
|
||||||
@@ -28,8 +33,32 @@ export const profileSchema = (t: TFunction) =>
|
|||||||
.refine((value) => isAtLeast18(value), {
|
.refine((value) => isAtLeast18(value), {
|
||||||
message: t('profileForm.validation.dobMinAge'),
|
message: t('profileForm.validation.dobMinAge'),
|
||||||
}),
|
}),
|
||||||
pob: z.string().optional(),
|
pob: isSeafarer
|
||||||
|
? z.string().min(1, t('profileForm.validation.pobRequired'))
|
||||||
|
: z.string().optional(),
|
||||||
maritalStatus: z.string().min(1, t('profileForm.validation.maritalStatusRequired')),
|
maritalStatus: z.string().min(1, t('profileForm.validation.maritalStatusRequired')),
|
||||||
|
bloodType: isSeafarer
|
||||||
|
? z.string().min(1, t('profileForm.validation.bloodTypeRequired'))
|
||||||
|
: z.string().optional(),
|
||||||
|
hairColor: isSeafarer
|
||||||
|
? z.string().min(1, t('profileForm.validation.hairColorRequired'))
|
||||||
|
: z.string().optional(),
|
||||||
|
eyeColor: isSeafarer
|
||||||
|
? z.string().min(1, t('profileForm.validation.eyeColorRequired'))
|
||||||
|
: z.string().optional(),
|
||||||
|
heightCm: isSeafarer
|
||||||
|
? z
|
||||||
|
.string()
|
||||||
|
.min(1, t('profileForm.validation.heightRequired'))
|
||||||
|
.refine((v) => Number(v) >= 100 && Number(v) <= 250, {
|
||||||
|
message: t('profileForm.validation.heightRange'),
|
||||||
|
})
|
||||||
|
: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.refine((v) => !v || (Number(v) >= 100 && Number(v) <= 250), {
|
||||||
|
message: t('profileForm.validation.heightRange'),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ProfileValues = z.infer<ReturnType<typeof profileSchema>>;
|
export type ProfileValues = z.infer<ReturnType<typeof profileSchema>>;
|
||||||
@@ -37,6 +66,13 @@ export type ProfileValues = z.infer<ReturnType<typeof profileSchema>>;
|
|||||||
export const GENDERS = ['MALE', 'FEMALE'] as const;
|
export const GENDERS = ['MALE', 'FEMALE'] as const;
|
||||||
export const MARITAL_STATUSES = ['SINGLE', 'MARRIED', 'DIVORCED', 'WIDOWED'] as const;
|
export const MARITAL_STATUSES = ['SINGLE', 'MARRIED', 'DIVORCED', 'WIDOWED'] as const;
|
||||||
export const ID_TYPES = ['NID', 'VITAL', 'PASSPORT', 'DRIVERS_LICENSE'] as const;
|
export const ID_TYPES = ['NID', 'VITAL', 'PASSPORT', 'DRIVERS_LICENSE'] as const;
|
||||||
|
// Must match EBloodType/EHairColor/EEyeColor on the backend (common/enums/user.enum.ts).
|
||||||
|
export const BLOOD_TYPES = [
|
||||||
|
'A_POSITIVE', 'A_NEGATIVE', 'B_POSITIVE', 'B_NEGATIVE',
|
||||||
|
'AB_POSITIVE', 'AB_NEGATIVE', 'O_POSITIVE', 'O_NEGATIVE', 'UNKNOWN',
|
||||||
|
] as const;
|
||||||
|
export const HAIR_COLORS = ['BLACK', 'BROWN', 'BLONDE', 'RED', 'GREY', 'WHITE', 'BALD', 'OTHER'] as const;
|
||||||
|
export const EYE_COLORS = ['BROWN', 'BLACK', 'BLUE', 'GREEN', 'HAZEL', 'GREY', 'OTHER'] as const;
|
||||||
|
|
||||||
interface ProfileFormContentProps {
|
interface ProfileFormContentProps {
|
||||||
register: UseFormRegister<ProfileValues>;
|
register: UseFormRegister<ProfileValues>;
|
||||||
@@ -46,6 +82,8 @@ interface ProfileFormContentProps {
|
|||||||
trigger: UseFormTrigger<ProfileValues>;
|
trigger: UseFormTrigger<ProfileValues>;
|
||||||
professionsLoading: boolean;
|
professionsLoading: boolean;
|
||||||
professionOptions: Array<{ value: string; label: string }>;
|
professionOptions: Array<{ value: string; label: string }>;
|
||||||
|
/** Place of birth, hair/eye colour and height become required for these accounts. */
|
||||||
|
isSeafarer: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ProfileFormContent({
|
export function ProfileFormContent({
|
||||||
@@ -56,6 +94,7 @@ export function ProfileFormContent({
|
|||||||
trigger,
|
trigger,
|
||||||
professionsLoading,
|
professionsLoading,
|
||||||
professionOptions,
|
professionOptions,
|
||||||
|
isSeafarer,
|
||||||
}: ProfileFormContentProps) {
|
}: ProfileFormContentProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -119,6 +158,7 @@ export function ProfileFormContent({
|
|||||||
<TextInput
|
<TextInput
|
||||||
label={t('profileFields.pob')}
|
label={t('profileFields.pob')}
|
||||||
placeholder={t('profileForm.placeholders.pob')}
|
placeholder={t('profileForm.placeholders.pob')}
|
||||||
|
required={isSeafarer}
|
||||||
{...register('pob')}
|
{...register('pob')}
|
||||||
error={errors.pob?.message}
|
error={errors.pob?.message}
|
||||||
/>
|
/>
|
||||||
@@ -133,6 +173,50 @@ export function ProfileFormContent({
|
|||||||
onBlur={() => trigger('maritalStatus')}
|
onBlur={() => trigger('maritalStatus')}
|
||||||
name="maritalStatus"
|
name="maritalStatus"
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
label={t('profileFields.bloodType')}
|
||||||
|
placeholder={t('common.select')}
|
||||||
|
required={isSeafarer}
|
||||||
|
clearable={!isSeafarer}
|
||||||
|
data={BLOOD_TYPES.map((b) => ({ value: b, label: t(`profileForm.bloodTypes.${b}`) }))}
|
||||||
|
error={errors.bloodType?.message}
|
||||||
|
value={watch('bloodType') || null}
|
||||||
|
onChange={(val) => setValue('bloodType', val || '', { shouldValidate: true })}
|
||||||
|
onBlur={() => trigger('bloodType')}
|
||||||
|
name="bloodType"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={t('profileFields.heightCm')}
|
||||||
|
placeholder={t('profileForm.placeholders.heightCm')}
|
||||||
|
type="number"
|
||||||
|
required={isSeafarer}
|
||||||
|
{...register('heightCm')}
|
||||||
|
error={errors.heightCm?.message}
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label={t('profileFields.hairColor')}
|
||||||
|
placeholder={t('common.select')}
|
||||||
|
required={isSeafarer}
|
||||||
|
clearable={!isSeafarer}
|
||||||
|
data={HAIR_COLORS.map((h) => ({ value: h, label: t(`profileForm.hairColors.${h}`) }))}
|
||||||
|
error={errors.hairColor?.message}
|
||||||
|
value={watch('hairColor') || null}
|
||||||
|
onChange={(val) => setValue('hairColor', val || '', { shouldValidate: true })}
|
||||||
|
onBlur={() => trigger('hairColor')}
|
||||||
|
name="hairColor"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
label={t('profileFields.eyeColor')}
|
||||||
|
placeholder={t('common.select')}
|
||||||
|
required={isSeafarer}
|
||||||
|
clearable={!isSeafarer}
|
||||||
|
data={EYE_COLORS.map((e) => ({ value: e, label: t(`profileForm.eyeColors.${e}`) }))}
|
||||||
|
error={errors.eyeColor?.message}
|
||||||
|
value={watch('eyeColor') || null}
|
||||||
|
onChange={(val) => setValue('eyeColor', val || '', { shouldValidate: true })}
|
||||||
|
onBlur={() => trigger('eyeColor')}
|
||||||
|
name="eyeColor"
|
||||||
|
/>
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,12 @@ export function ProfilePage() {
|
|||||||
const showSeafarerBanner =
|
const showSeafarerBanner =
|
||||||
can([PORTAL_PERMISSIONS.APPLY_SEAFARER_REGISTRATION]) &&
|
can([PORTAL_PERMISSIONS.APPLY_SEAFARER_REGISTRATION]) &&
|
||||||
!isReadyFor(SEAFARER_PROFILE_REQUIREMENT);
|
!isReadyFor(SEAFARER_PROFILE_REQUIREMENT);
|
||||||
|
// Place of birth, blood type, hair/eye colour and height print on the
|
||||||
|
// Seaman Book, so a seafarer account can't leave them blank — every other
|
||||||
|
// account type may. Mirrors the seafarer-registration wizard's
|
||||||
|
// `required: true` on the same fields and the backend check in
|
||||||
|
// ProfileService.assertPhysicalCharacteristicsForSeafarer.
|
||||||
|
const isSeafarer = (resolvedProfile ?? storedProfile)?.type === 'SEAFARER';
|
||||||
|
|
||||||
const [loadedProfile, setLoadedProfile] = useState<ProfileValues | null>(null);
|
const [loadedProfile, setLoadedProfile] = useState<ProfileValues | null>(null);
|
||||||
const [loadedAddress, setLoadedAddress] = useState<AddressValues | null>(null);
|
const [loadedAddress, setLoadedAddress] = useState<AddressValues | null>(null);
|
||||||
@@ -223,6 +229,10 @@ export function ProfilePage() {
|
|||||||
dob: currentProfile.dob ? currentProfile.dob.split('T')[0] : '',
|
dob: currentProfile.dob ? currentProfile.dob.split('T')[0] : '',
|
||||||
pob: currentProfile.pob || '',
|
pob: currentProfile.pob || '',
|
||||||
maritalStatus: currentProfile.maritalStatus || '',
|
maritalStatus: currentProfile.maritalStatus || '',
|
||||||
|
bloodType: currentProfile.bloodType || '',
|
||||||
|
hairColor: currentProfile.hairColor || '',
|
||||||
|
eyeColor: currentProfile.eyeColor || '',
|
||||||
|
heightCm: currentProfile.heightCm != null ? String(currentProfile.heightCm) : '',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Primary phone and email are the account's contact details (same
|
// Primary phone and email are the account's contact details (same
|
||||||
@@ -367,7 +377,7 @@ export function ProfilePage() {
|
|||||||
trigger: profileTriggerValidation,
|
trigger: profileTriggerValidation,
|
||||||
formState: { errors: profileErrors },
|
formState: { errors: profileErrors },
|
||||||
} = useForm<ProfileValues>({
|
} = useForm<ProfileValues>({
|
||||||
resolver: zodResolver(profileSchema(t)),
|
resolver: zodResolver(profileSchema(t, isSeafarer)),
|
||||||
values: loadedProfile ?? undefined,
|
values: loadedProfile ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -385,7 +395,15 @@ export function ProfilePage() {
|
|||||||
await updateProfile({
|
await updateProfile({
|
||||||
url: `/profiles/${profileId}`,
|
url: `/profiles/${profileId}`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: values,
|
// Empty string is not a valid enum value on the backend — an
|
||||||
|
// untouched Select must clear the column, not fail validation.
|
||||||
|
body: {
|
||||||
|
...values,
|
||||||
|
heightCm: values.heightCm ? Number(values.heightCm) : null,
|
||||||
|
bloodType: values.bloodType || null,
|
||||||
|
hairColor: values.hairColor || null,
|
||||||
|
eyeColor: values.eyeColor || null,
|
||||||
|
},
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
setLoadedProfile({ ...values, pob: values.pob ?? '' });
|
setLoadedProfile({ ...values, pob: values.pob ?? '' });
|
||||||
|
|
||||||
@@ -727,6 +745,7 @@ export function ProfilePage() {
|
|||||||
trigger={profileTriggerValidation}
|
trigger={profileTriggerValidation}
|
||||||
professionsLoading={professionsLoading}
|
professionsLoading={professionsLoading}
|
||||||
professionOptions={professionOptions}
|
professionOptions={professionOptions}
|
||||||
|
isSeafarer={isSeafarer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -256,6 +256,10 @@ export const am: Translations = {
|
|||||||
dob: 'የትውልድ ቀን',
|
dob: 'የትውልድ ቀን',
|
||||||
pob: 'የትውልድ ቦታ',
|
pob: 'የትውልድ ቦታ',
|
||||||
maritalStatus: 'የጋብቻ ሁኔታ',
|
maritalStatus: 'የጋብቻ ሁኔታ',
|
||||||
|
bloodType: 'የደም አይነት',
|
||||||
|
hairColor: 'የፀጉር ቀለም',
|
||||||
|
eyeColor: 'የአይን ቀለም',
|
||||||
|
heightCm: 'ቁመት (ሴ.ሜ)',
|
||||||
professionId: 'ሙያ',
|
professionId: 'ሙያ',
|
||||||
idType: 'የመታወቂያ ዓይነት',
|
idType: 'የመታወቂያ ዓይነት',
|
||||||
idNumber: 'የመታወቂያ ቁጥር',
|
idNumber: 'የመታወቂያ ቁጥር',
|
||||||
@@ -434,6 +438,7 @@ export const am: Translations = {
|
|||||||
middleName: 'የአባት ስም ያስገቡ',
|
middleName: 'የአባት ስም ያስገቡ',
|
||||||
lastName: 'የአያት ስም ያስገቡ',
|
lastName: 'የአያት ስም ያስገቡ',
|
||||||
pob: 'ከተማ፣ ክልል',
|
pob: 'ከተማ፣ ክልል',
|
||||||
|
heightCm: 'ለምሳሌ 175',
|
||||||
},
|
},
|
||||||
genders: {
|
genders: {
|
||||||
MALE: 'ወንድ',
|
MALE: 'ወንድ',
|
||||||
@@ -445,6 +450,36 @@ export const am: Translations = {
|
|||||||
DIVORCED: 'የፈታ/ች',
|
DIVORCED: 'የፈታ/ች',
|
||||||
WIDOWED: 'የሞተበት/ባት',
|
WIDOWED: 'የሞተበት/ባት',
|
||||||
},
|
},
|
||||||
|
bloodTypes: {
|
||||||
|
A_POSITIVE: 'A+',
|
||||||
|
A_NEGATIVE: 'A-',
|
||||||
|
B_POSITIVE: 'B+',
|
||||||
|
B_NEGATIVE: 'B-',
|
||||||
|
AB_POSITIVE: 'AB+',
|
||||||
|
AB_NEGATIVE: 'AB-',
|
||||||
|
O_POSITIVE: 'O+',
|
||||||
|
O_NEGATIVE: 'O-',
|
||||||
|
UNKNOWN: 'የማይታወቅ',
|
||||||
|
},
|
||||||
|
hairColors: {
|
||||||
|
BLACK: 'ጥቁር',
|
||||||
|
BROWN: 'ቡናማ',
|
||||||
|
BLONDE: 'ወርቃማ',
|
||||||
|
RED: 'ቀይ',
|
||||||
|
GREY: 'ግራጫ',
|
||||||
|
WHITE: 'ነጭ',
|
||||||
|
BALD: 'ራሰ በራ',
|
||||||
|
OTHER: 'ሌላ',
|
||||||
|
},
|
||||||
|
eyeColors: {
|
||||||
|
BROWN: 'ቡናማ',
|
||||||
|
BLACK: 'ጥቁር',
|
||||||
|
BLUE: 'ሰማያዊ',
|
||||||
|
GREEN: 'አረንጓዴ',
|
||||||
|
HAZEL: 'ኮክ ቡናማ',
|
||||||
|
GREY: 'ግራጫ',
|
||||||
|
OTHER: 'ሌላ',
|
||||||
|
},
|
||||||
validation: {
|
validation: {
|
||||||
professionRequired: 'ሙያዎን ይምረጡ',
|
professionRequired: 'ሙያዎን ይምረጡ',
|
||||||
firstNameMin: 'የመጀመሪያ ስም ቢያንስ 3 ቁምፊዎች መሆን አለበት',
|
firstNameMin: 'የመጀመሪያ ስም ቢያንስ 3 ቁምፊዎች መሆን አለበት',
|
||||||
@@ -455,6 +490,12 @@ export const am: Translations = {
|
|||||||
dobMinAge: 'ዕድሜዎ ቢያንስ 18 ዓመት መሆን አለበት',
|
dobMinAge: 'ዕድሜዎ ቢያንስ 18 ዓመት መሆን አለበት',
|
||||||
maritalStatusRequired: 'የጋብቻ ሁኔታዎን ይምረጡ',
|
maritalStatusRequired: 'የጋብቻ ሁኔታዎን ይምረጡ',
|
||||||
nameParts: 'የመጀመሪያ፣ የአባት እና የአያት ስምዎን ያስገቡ',
|
nameParts: 'የመጀመሪያ፣ የአባት እና የአያት ስምዎን ያስገቡ',
|
||||||
|
heightRange: 'ቁመት ከ100 እስከ 250 ሴ.ሜ መሆን አለበት',
|
||||||
|
pobRequired: 'የትውልድ ቦታዎን ያስገቡ',
|
||||||
|
bloodTypeRequired: 'የደም አይነትዎን ይምረጡ — ካልተመረመሩ የማይታወቅ ይምረጡ',
|
||||||
|
hairColorRequired: 'የፀጉር ቀለምዎን ይምረጡ',
|
||||||
|
eyeColorRequired: 'የአይን ቀለምዎን ይምረጡ',
|
||||||
|
heightRequired: 'ቁመትዎን ያስገቡ',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -255,6 +255,10 @@ export const en = {
|
|||||||
dob: 'Date of birth',
|
dob: 'Date of birth',
|
||||||
pob: 'Place of birth',
|
pob: 'Place of birth',
|
||||||
maritalStatus: 'Marital status',
|
maritalStatus: 'Marital status',
|
||||||
|
bloodType: 'Blood type',
|
||||||
|
hairColor: 'Hair color',
|
||||||
|
eyeColor: 'Eye color',
|
||||||
|
heightCm: 'Height (cm)',
|
||||||
professionId: 'Profession',
|
professionId: 'Profession',
|
||||||
idType: 'ID type',
|
idType: 'ID type',
|
||||||
idNumber: 'ID number',
|
idNumber: 'ID number',
|
||||||
@@ -433,6 +437,7 @@ export const en = {
|
|||||||
middleName: 'Enter middle name',
|
middleName: 'Enter middle name',
|
||||||
lastName: 'Enter last name',
|
lastName: 'Enter last name',
|
||||||
pob: 'City, Region',
|
pob: 'City, Region',
|
||||||
|
heightCm: 'e.g. 175',
|
||||||
},
|
},
|
||||||
genders: {
|
genders: {
|
||||||
MALE: 'Male',
|
MALE: 'Male',
|
||||||
@@ -444,6 +449,36 @@ export const en = {
|
|||||||
DIVORCED: 'Divorced',
|
DIVORCED: 'Divorced',
|
||||||
WIDOWED: 'Widowed',
|
WIDOWED: 'Widowed',
|
||||||
},
|
},
|
||||||
|
bloodTypes: {
|
||||||
|
A_POSITIVE: 'A+',
|
||||||
|
A_NEGATIVE: 'A-',
|
||||||
|
B_POSITIVE: 'B+',
|
||||||
|
B_NEGATIVE: 'B-',
|
||||||
|
AB_POSITIVE: 'AB+',
|
||||||
|
AB_NEGATIVE: 'AB-',
|
||||||
|
O_POSITIVE: 'O+',
|
||||||
|
O_NEGATIVE: 'O-',
|
||||||
|
UNKNOWN: 'Unknown',
|
||||||
|
},
|
||||||
|
hairColors: {
|
||||||
|
BLACK: 'Black',
|
||||||
|
BROWN: 'Brown',
|
||||||
|
BLONDE: 'Blonde',
|
||||||
|
RED: 'Red',
|
||||||
|
GREY: 'Grey',
|
||||||
|
WHITE: 'White',
|
||||||
|
BALD: 'Bald',
|
||||||
|
OTHER: 'Other',
|
||||||
|
},
|
||||||
|
eyeColors: {
|
||||||
|
BROWN: 'Brown',
|
||||||
|
BLACK: 'Black',
|
||||||
|
BLUE: 'Blue',
|
||||||
|
GREEN: 'Green',
|
||||||
|
HAZEL: 'Hazel',
|
||||||
|
GREY: 'Grey',
|
||||||
|
OTHER: 'Other',
|
||||||
|
},
|
||||||
validation: {
|
validation: {
|
||||||
professionRequired: 'Select your profession',
|
professionRequired: 'Select your profession',
|
||||||
firstNameMin: 'First name must be at least 3 characters',
|
firstNameMin: 'First name must be at least 3 characters',
|
||||||
@@ -454,6 +489,12 @@ export const en = {
|
|||||||
dobMinAge: 'You must be at least 18 years old',
|
dobMinAge: 'You must be at least 18 years old',
|
||||||
maritalStatusRequired: 'Select your marital status',
|
maritalStatusRequired: 'Select your marital status',
|
||||||
nameParts: 'Enter your first, middle, and last name',
|
nameParts: 'Enter your first, middle, and last name',
|
||||||
|
heightRange: 'Height must be between 100 and 250 cm',
|
||||||
|
pobRequired: 'Enter your place of birth',
|
||||||
|
bloodTypeRequired: 'Select your blood type — choose Unknown if untested',
|
||||||
|
hairColorRequired: 'Select your hair color',
|
||||||
|
eyeColorRequired: 'Select your eye color',
|
||||||
|
heightRequired: 'Enter your height',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,11 @@ export interface CurrentProfile {
|
|||||||
seafarerStatus?: 'ACTIVE' | 'INACTIVE' | 'PENDING' | 'SUSPENDED' | null;
|
seafarerStatus?: 'ACTIVE' | 'INACTIVE' | 'PENDING' | 'SUSPENDED' | null;
|
||||||
seafarerDepartment?: 'DECK' | 'ENGINE' | 'CATERING' | null;
|
seafarerDepartment?: 'DECK' | 'ENGINE' | 'CATERING' | null;
|
||||||
seafarerStatusReason?: string | null;
|
seafarerStatusReason?: string | null;
|
||||||
|
/** Identifying particulars for the Seaman Book. Left blank by choice. */
|
||||||
|
bloodType?: string | null;
|
||||||
|
hairColor?: string | null;
|
||||||
|
eyeColor?: string | null;
|
||||||
|
heightCm?: number | null;
|
||||||
user: AuthUser;
|
user: AuthUser;
|
||||||
address: CurrentProfileAddress;
|
address: CurrentProfileAddress;
|
||||||
profession: CurrentProfileProfession;
|
profession: CurrentProfileProfession;
|
||||||
|
|||||||
Reference in New Issue
Block a user