From 56de3727faa5498189a90c4193f7f02ba183ceec Mon Sep 17 00:00:00 2001 From: estifanos Date: Sat, 8 Aug 2026 07:40:33 +0000 Subject: [PATCH] Refactor AddressFormContent and ProfilePage to integrate CountrySelect for nationality selection and update address payload handling --- .../profile/components/AddressFormContent.tsx | 18 ++++++------------ .../app/features/profile/pages/ProfilePage.tsx | 6 +++--- .../src/app/features/profile/types/address.ts | 8 +++++--- libs/ui/src/lib/input/CountrySelect.tsx | 7 ++++++- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/apps/portal/src/app/features/profile/components/AddressFormContent.tsx b/apps/portal/src/app/features/profile/components/AddressFormContent.tsx index 98e91081e..a07c8aa49 100644 --- a/apps/portal/src/app/features/profile/components/AddressFormContent.tsx +++ b/apps/portal/src/app/features/profile/components/AddressFormContent.tsx @@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react'; import { Select, SimpleGrid, Text, TextInput } from '@mantine/core'; import type { FieldErrors, UseFormRegister, UseFormSetValue, UseFormWatch, UseFormTrigger } from 'react-hook-form'; import { z } from 'zod'; -import { ethiopianPhone, optionalEthiopianPhone } from '@ema-platform/ui'; +import { ethiopianPhone, optionalEthiopianPhone, CountrySelect } from '@ema-platform/ui'; import { LocationPicker } from '../../location/components/LocationPicker'; import { useGetLocationTypesQuery } from '../../location/api/location-api'; import type { Location } from '../../location/types/location'; @@ -10,11 +10,11 @@ import type { Location } from '../../location/types/location'; export const addressSchema = z.object({ idType: z.string().min(1, 'Select ID type'), idNumber: z.string().trim().min(1, 'Enter ID number'), - nationality: z.string().trim().min(1, 'Enter nationality'), + // Alpha-2 country code from CountrySelect; converted to a full name at submit. + nationality: z.string().min(1, 'Select nationality'), primaryPhoneNumber: ethiopianPhone, secondaryPhoneNumber: optionalEthiopianPhone, email: z.string().trim().email('Invalid email').optional().or(z.literal('')), - website: z.string().trim().url('Enter a valid URL').optional().or(z.literal('')), regionId: z.string().optional(), cityId: z.string().optional(), subcityId: z.string().optional(), @@ -115,11 +115,11 @@ export function AddressFormContent({ {...register('idNumber')} error={errors.idNumber?.message} /> - setValue('nationality', val || '', { shouldValidate: true })} error={errors.nationality?.message} /> - diff --git a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx index 1715affd1..2ac740812 100644 --- a/apps/portal/src/app/features/profile/pages/ProfilePage.tsx +++ b/apps/portal/src/app/features/profile/pages/ProfilePage.tsx @@ -47,7 +47,7 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useTranslation } from 'react-i18next'; -import { notify, PageHeader, useErrorHandler, passwordSchema as strongPasswordSchema, PasswordRequirements } from '@ema-platform/ui'; +import { notify, PageHeader, useErrorHandler, passwordSchema as strongPasswordSchema, PasswordRequirements, getCountryCode } from '@ema-platform/ui'; import { useApiMutation } from '@ema-platform/api'; import { setUser, useCurrentProfile } from '@ema-platform/auth'; import type { CurrentProfile } from '@ema-platform/auth'; @@ -201,11 +201,11 @@ export function ProfilePage() { setLoadedAddress({ idType: currentProfile.address?.idType || '', idNumber: currentProfile.address?.idNumber || '', - nationality: currentProfile.address?.nationality || '', + // Stored as a country name; the select works in alpha-2 codes. + nationality: getCountryCode(currentProfile.address?.nationality) || '', primaryPhoneNumber: user?.phoneNumber || '', secondaryPhoneNumber: currentProfile.address?.secondaryPhoneNumber || '', email: user?.email || '', - website: currentProfile.address?.website || '', regionId: currentProfile.address?.regionId || '', cityId: currentProfile.address?.cityId || '', subcityId: currentProfile.address?.subCityId || '', diff --git a/apps/portal/src/app/features/profile/types/address.ts b/apps/portal/src/app/features/profile/types/address.ts index 4dedbbcba..47b1f9850 100644 --- a/apps/portal/src/app/features/profile/types/address.ts +++ b/apps/portal/src/app/features/profile/types/address.ts @@ -1,3 +1,4 @@ +import { getCountryName } from '@ema-platform/ui'; import type { AddressValues } from '../components/AddressFormContent'; /** Exact request body of `POST /addresss/profile/{profileId}`. */ @@ -14,7 +15,7 @@ export interface AddressPayload { primaryPhoneNumber: string; secondaryPhoneNumber?: string; email?: string; - website?: string; + website: null; postalAddress?: string; emergencyContactName?: string; emergencyContactPhone?: string; @@ -28,7 +29,8 @@ export function toAddressPayload(values: AddressValues): AddressPayload { return { idType: values.idType.trim(), idNumber: values.idNumber.trim(), - nationality: values.nationality.trim(), + // Form holds the alpha-2 code (CountrySelect); API stores the full name. + nationality: getCountryName(values.nationality), regionId: clean(values.regionId), cityId: clean(values.cityId), subcityId: clean(values.subcityId), @@ -38,7 +40,7 @@ export function toAddressPayload(values: AddressValues): AddressPayload { primaryPhoneNumber: values.primaryPhoneNumber, secondaryPhoneNumber: clean(values.secondaryPhoneNumber), email: clean(values.email), - website: clean(values.website), + website: null, // no website field in the form — always sent as null postalAddress: clean(values.postalAddress), emergencyContactName: clean(values.emergencyContactName), emergencyContactPhone: clean(values.emergencyContactPhone), diff --git a/libs/ui/src/lib/input/CountrySelect.tsx b/libs/ui/src/lib/input/CountrySelect.tsx index 73c300b5a..06e9f55c5 100644 --- a/libs/ui/src/lib/input/CountrySelect.tsx +++ b/libs/ui/src/lib/input/CountrySelect.tsx @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Group, Select, Text, type ComboboxItem, type SelectProps } from '@mantine/core'; -import { registerLocale, getNames, getName } from 'i18n-iso-countries'; +import { registerLocale, getNames, getName, getAlpha2Code } from 'i18n-iso-countries'; import * as Flags from 'country-flag-icons/react/3x2'; import en from 'i18n-iso-countries/langs/en.json'; import am from 'i18n-iso-countries/langs/am.json'; @@ -21,6 +21,11 @@ export function getCountryName(code: string | null | undefined, lang: CountryLan return code ? (getName(code, lang) ?? '') : ''; } +/** Alpha-2 code for a stored country name (e.g. "Ethiopian" data); undefined when unmatched. */ +export function getCountryCode(name: string | null | undefined, lang: CountryLang = 'en'): string | undefined { + return name ? getAlpha2Code(name, lang) : undefined; +} + function CountryFlag({ code }: { code: string }) { const Flag = Flags[code as keyof typeof Flags]; return Flag ? : null;