mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-09-07 16:35:43 +00:00
feat: introduce useLocalized hook for bilingual value retrieval.(localization round 2)
- Added useLocalized hook to provide a stable function for retrieving bilingual values based on the current language. - Updated various components across the portal and backoffice to utilize the new useLocalized hook for consistent bilingual label rendering. - Refactored localized function in licensing.helpers to handle empty Amharic strings correctly. - Enhanced localization handling in LicenseApplicationPage, ProfilePage, and other components to ensure proper language switching.
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
STATUS_LABELS,
|
||||
TERMINAL_STATUSES,
|
||||
extractErrorMessage,
|
||||
useLocalized,
|
||||
useGetCertificateUrlMutation,
|
||||
useGetMyApplicationsQuery,
|
||||
useGetMyLicensesQuery,
|
||||
@@ -74,6 +75,7 @@ export function CertificatesPage() {
|
||||
const { data: licenses } = useGetMyLicensesQuery();
|
||||
const [getCertificateUrl] = useGetCertificateUrlMutation();
|
||||
const showDate = useDateDisplayer();
|
||||
const localized = useLocalized();
|
||||
|
||||
const registered =
|
||||
Boolean(profile?.seafarerNumber) && profile?.seafarerStatus === 'ACTIVE';
|
||||
@@ -189,7 +191,7 @@ export function CertificatesPage() {
|
||||
<div>
|
||||
<Text fw={600}>{app.applicationNumber}</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{app.licenseType?.name?.en}
|
||||
{localized(app.licenseType?.name)}
|
||||
</Text>
|
||||
</div>
|
||||
<Group>
|
||||
@@ -245,7 +247,7 @@ export function CertificatesPage() {
|
||||
{license.certificateNumber}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>{license.licenseType?.name?.en}</Table.Td>
|
||||
<Table.Td>{localized(license.licenseType?.name)}</Table.Td>
|
||||
<Table.Td>{showDate(license.issueDate)}</Table.Td>
|
||||
<Table.Td>{showDate(license.expiryDate)}</Table.Td>
|
||||
<Table.Td>
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
STATUS_LABELS,
|
||||
STATUS_PROGRESS,
|
||||
TERMINAL_STATUSES,
|
||||
localized,
|
||||
useLocalized,
|
||||
useGetCertificateUrlMutation,
|
||||
useGetMyApplicationsQuery,
|
||||
useGetMyLicensesQuery,
|
||||
@@ -457,6 +457,7 @@ function ApplicationTable({
|
||||
applications: LicenseApplication[];
|
||||
navigate: (path: string) => void;
|
||||
}) {
|
||||
const localized = useLocalized();
|
||||
return (
|
||||
<Card withBorder radius="md" padding={0}>
|
||||
<Table.ScrollContainer minWidth={640}>
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
STATUS_LABELS,
|
||||
TERMINAL_STATUSES,
|
||||
extractErrorMessage,
|
||||
localized,
|
||||
useLocalized,
|
||||
useGetCertificateUrlMutation,
|
||||
useGetMyApplicationsQuery,
|
||||
useGetMyLicensesQuery,
|
||||
@@ -68,6 +68,7 @@ export function EndorsementPage() {
|
||||
useGetMyApplicationsQuery();
|
||||
const { data: licenses } = useGetMyLicensesQuery();
|
||||
const showDate = useDateDisplayer();
|
||||
const localized = useLocalized();
|
||||
const [getCertificateUrl] = useGetCertificateUrlMutation();
|
||||
|
||||
const registered =
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
useApiMutation,
|
||||
extractErrorMessage,
|
||||
openAuthedDocument,
|
||||
useLocalized,
|
||||
} from '@ema-platform/api';
|
||||
|
||||
interface OpenExam {
|
||||
@@ -83,6 +84,7 @@ const ATTENDANCE_COLOR: Record<AttendanceStatus, string> = {
|
||||
*/
|
||||
export function ExamsPage() {
|
||||
const showDate = useDateDisplayer();
|
||||
const localized = useLocalized();
|
||||
const [appealFor, setAppealFor] = useState<MyResult | null>(null);
|
||||
const [appealReason, setAppealReason] = useState('');
|
||||
|
||||
@@ -197,9 +199,9 @@ export function ExamsPage() {
|
||||
<Card key={exam.id} withBorder radius="md" p="md">
|
||||
<Group justify="space-between">
|
||||
<div>
|
||||
<Text fw={600}>{exam.title?.en}</Text>
|
||||
<Text fw={600}>{localized(exam.title)}</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{exam.certification?.name?.en ?? ''} ·{' '}
|
||||
{localized(exam.certification?.name)} ·{' '}
|
||||
{showDate(exam.date)}
|
||||
{exam.venue ? ` · ${exam.venue}` : ''}
|
||||
</Text>
|
||||
@@ -254,7 +256,7 @@ export function ExamsPage() {
|
||||
{registration.admissionNumber}
|
||||
</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>{registration.exam?.title?.en ?? '—'}</Table.Td>
|
||||
<Table.Td>{localized(registration.exam?.title) || '—'}</Table.Td>
|
||||
<Table.Td>{showDate(registration.exam?.date)}</Table.Td>
|
||||
<Table.Td>{registration.exam?.venue ?? '—'}</Table.Td>
|
||||
<Table.Td>
|
||||
@@ -326,7 +328,7 @@ export function ExamsPage() {
|
||||
);
|
||||
return (
|
||||
<Table.Tr key={result.id}>
|
||||
<Table.Td>{result.exam?.title?.en ?? '—'}</Table.Td>
|
||||
<Table.Td>{localized(result.exam?.title) || '—'}</Table.Td>
|
||||
<Table.Td>{showDate(result.publishedAt)}</Table.Td>
|
||||
<Table.Td>
|
||||
<Text fw={600} size="sm">
|
||||
@@ -376,7 +378,7 @@ export function ExamsPage() {
|
||||
<Stack>
|
||||
<Text size="sm" c="dimmed">
|
||||
Explain what you believe went wrong with the marking or the
|
||||
administration of {appealFor?.exam?.title?.en ?? 'this examination'}.
|
||||
administration of {localized(appealFor?.exam?.title) || 'this examination'}.
|
||||
Appeals must be lodged within 14 days of publication.
|
||||
</Text>
|
||||
<Textarea
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
conditionHolds,
|
||||
localized,
|
||||
useLocalized,
|
||||
type FormFieldConfig,
|
||||
type FormSectionConfig,
|
||||
type Vessel,
|
||||
@@ -72,7 +72,8 @@ export function fillFromVessel(
|
||||
onChange: (key: string, value: unknown) => void,
|
||||
) {
|
||||
for (const f of fields) {
|
||||
const label = localized(f.label).toLowerCase();
|
||||
// English-pinned: matched against the English strings in VESSEL_FIELD_FILLERS.
|
||||
const label = (f.label.en ?? '').toLowerCase();
|
||||
const filler = VESSEL_FIELD_FILLERS.find((m) => m.matches(label, f.key));
|
||||
if (filler) onChange(f.key, filler.value(vessel) ?? '');
|
||||
}
|
||||
@@ -95,6 +96,7 @@ export function ConfigDrivenSection({
|
||||
vessels = [],
|
||||
onVesselSelected,
|
||||
}: Props) {
|
||||
const localized = useLocalized();
|
||||
const fields = [...(section.fields ?? [])].sort(
|
||||
(a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0),
|
||||
);
|
||||
@@ -105,6 +107,8 @@ export function ConfigDrivenSection({
|
||||
if (!conditionHolds(field.showWhen, formData)) return null;
|
||||
|
||||
const label = localized(field.label);
|
||||
// English-pinned: the picker overrides below match English labels.
|
||||
const labelEn = (field.label.en ?? '').toLowerCase();
|
||||
const value = values?.[field.key];
|
||||
const error = errors[`${section.key}.${field.key}`];
|
||||
const common = {
|
||||
@@ -118,11 +122,11 @@ export function ConfigDrivenSection({
|
||||
const span = field.type === 'TEXTAREA' ? 12 : 6;
|
||||
// Same field the profile Address tab collects — give it the same
|
||||
// searchable, flag-labeled picker instead of a plain option list.
|
||||
const isNationality = field.key === 'nationality' || label.toLowerCase().includes('nationality');
|
||||
const isNationality = field.key === 'nationality' || labelEn.includes('nationality');
|
||||
// Options here can't be seeded statically — they're the applicant's
|
||||
// own vessel register, so this overrides whatever type the backend
|
||||
// configured, the same way nationality overrides SELECT above.
|
||||
const isVesselPicker = field.key === 'vesselId' || field.key === 'vessel' || /^(select\s+)?vessel$/i.test(label.trim());
|
||||
const isVesselPicker = field.key === 'vesselId' || field.key === 'vessel' || /^(select\s+)?vessel$/i.test(labelEn.trim());
|
||||
|
||||
return (
|
||||
<Grid.Col span={{ base: 12, md: span }} key={field.key}>
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import {
|
||||
conditionHolds,
|
||||
localized,
|
||||
useLocalized,
|
||||
uploadDocument,
|
||||
type Attachment,
|
||||
type DocumentRequirement,
|
||||
@@ -58,6 +58,7 @@ export function DocumentSlots({
|
||||
onUploaded,
|
||||
readOnly,
|
||||
}: Props) {
|
||||
const localized = useLocalized();
|
||||
const [busy, setBusy] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const resetRefs = useRef<Record<string, () => void>>({});
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { IconDownload, IconRefresh } from '@tabler/icons-react';
|
||||
import {
|
||||
extractErrorMessage,
|
||||
localized,
|
||||
useLocalized,
|
||||
useCreateApplicationMutation,
|
||||
type IssuedLicense,
|
||||
} from '@ema-platform/api';
|
||||
@@ -76,6 +76,7 @@ export function LicenseCard({
|
||||
const expired = license.status === 'EXPIRED' || days < 0;
|
||||
const renewable = license.renewable ?? false;
|
||||
const showDate = useDateDisplayer();
|
||||
const localized = useLocalized();
|
||||
|
||||
return (
|
||||
<Card withBorder radius="md" padding="md" className="ema-hover-lift">
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
IconTrendingUp,
|
||||
} from '@tabler/icons-react';
|
||||
import {
|
||||
localized,
|
||||
useLocalized,
|
||||
useGetLicenseCategoriesQuery,
|
||||
useGetLicenseTypesQuery,
|
||||
useGetMyOperatorTypesQuery,
|
||||
@@ -55,6 +55,7 @@ function formatFee(amount: string | number | null, currency: string): string {
|
||||
|
||||
export function LicenseCatalogue() {
|
||||
const navigate = useNavigate();
|
||||
const localized = useLocalized();
|
||||
const { data: types } = useGetLicenseTypesQuery();
|
||||
const { data: categories } = useGetLicenseCategoriesQuery();
|
||||
const { data: operatorTypes, isLoading: loadingOperatorTypes } =
|
||||
@@ -253,6 +254,7 @@ function LicenseTypeCard({
|
||||
canApply: boolean;
|
||||
onSelect: (type: LicenseType) => void;
|
||||
}) {
|
||||
const localized = useLocalized();
|
||||
const capital = type.capitalThreshold ? Number(type.capitalThreshold) : null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Badge, Button, FileButton, Group, Loader } from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconCheck } from '@tabler/icons-react';
|
||||
import {
|
||||
localized,
|
||||
useLocalized,
|
||||
uploadDocument,
|
||||
useGetAttachmentsQuery,
|
||||
type StaffEvidenceRequirement,
|
||||
@@ -30,6 +30,7 @@ export function StaffEvidence({ staffId, evidence, readOnly, onUploaded }: Props
|
||||
ownerId: staffId,
|
||||
});
|
||||
const [busy, setBusy] = useState<string | null>(null);
|
||||
const localized = useLocalized();
|
||||
|
||||
if (!evidence?.length) return null;
|
||||
|
||||
|
||||
@@ -29,12 +29,13 @@ import {
|
||||
IconTrash,
|
||||
} from '@tabler/icons-react';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
buildWizardSteps,
|
||||
conditionHolds,
|
||||
extractErrorMessage,
|
||||
extractValidationIssues,
|
||||
localized,
|
||||
useLocalized,
|
||||
validateSections,
|
||||
useAddStaffMutation,
|
||||
useCreateApplicationMutation,
|
||||
@@ -66,6 +67,8 @@ import { StaffEvidence } from '../components/StaffEvidence';
|
||||
export function LicenseApplicationPage() {
|
||||
const { typeCode = 'FREIGHT_FORWARDER', applicationId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { i18n } = useTranslation();
|
||||
const localized = useLocalized();
|
||||
|
||||
const { data: config, isLoading: loadingConfig } =
|
||||
useGetLicenseTypeRequirementsQuery({ idOrKey: typeCode });
|
||||
@@ -136,7 +139,8 @@ export function LicenseApplicationPage() {
|
||||
toFieldValue: (field: FormFieldConfig) => unknown,
|
||||
) => {
|
||||
for (const section of config.licenseType.formSchema.sections) {
|
||||
const field = section.fields.find((f) => matchField(localized(f.label).toLowerCase(), f.key));
|
||||
// English-pinned: matched against English substrings below ('nationality', 'fayda').
|
||||
const field = section.fields.find((f) => matchField((f.label.en ?? '').toLowerCase(), f.key));
|
||||
if (!field) continue;
|
||||
if (next[section.key]?.[field.key]) return; // already set — leave it
|
||||
next = { ...next, [section.key]: { ...next[section.key], [field.key]: toFieldValue(field) } };
|
||||
@@ -193,8 +197,9 @@ export function LicenseApplicationPage() {
|
||||
() =>
|
||||
buildWizardSteps(config?.licenseType?.formSchema?.sections ?? [], draft, {
|
||||
hasStaff: (config?.staffRoleRequirements?.length ?? 0) > 0,
|
||||
language: i18n.language,
|
||||
}),
|
||||
[config, draft],
|
||||
[config, draft, i18n.language],
|
||||
);
|
||||
const sections = useMemo(
|
||||
() => steps.flatMap((step) => step.sections),
|
||||
@@ -234,7 +239,8 @@ export function LicenseApplicationPage() {
|
||||
// the profile Address endpoint, stores the full country name.
|
||||
const nationalityField = config?.licenseType.formSchema.sections
|
||||
.find((s) => s.key === sectionKey)
|
||||
?.fields.find((f) => f.key === 'nationality' || localized(f.label).toLowerCase().includes('nationality'));
|
||||
// English-pinned: same reasoning as the fill() matcher above.
|
||||
?.fields.find((f) => f.key === 'nationality' || (f.label.en ?? '').toLowerCase().includes('nationality'));
|
||||
if (nationalityField && values[nationalityField.key]) {
|
||||
values[nationalityField.key] = getCountryName(values[nationalityField.key] as string) || values[nationalityField.key];
|
||||
}
|
||||
@@ -256,7 +262,7 @@ export function LicenseApplicationPage() {
|
||||
async function handleSubmit() {
|
||||
setIssues([]);
|
||||
if (!readOnly && currentStep?.sections?.length) {
|
||||
const errors = validateSections(currentStep.sections, draft);
|
||||
const errors = validateSections(currentStep.sections, draft, i18n.language);
|
||||
setFieldErrors(errors);
|
||||
if (Object.keys(errors).length) {
|
||||
notifications.show({
|
||||
@@ -316,7 +322,7 @@ export function LicenseApplicationPage() {
|
||||
if (!currentStep || !config) return true;
|
||||
|
||||
if (currentStep.kind === 'sections') {
|
||||
const errors = validateSections(currentStep.sections, draft);
|
||||
const errors = validateSections(currentStep.sections, draft, i18n.language);
|
||||
setFieldErrors(errors);
|
||||
const count = Object.keys(errors).length;
|
||||
if (count > 0) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { Stack, Select, Group, Text, Loader, Center, Badge } from '@mantine/core';
|
||||
import { ErrorState } from '@ema-platform/ui';
|
||||
import { useLocalized } from '@ema-platform/api';
|
||||
import { useGetLocationTypesQuery, useGetLocationsQuery } from '../api/location-api';
|
||||
import type { Location, LocationType } from '../types/location';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -16,6 +17,7 @@ interface LocationPickerProps {
|
||||
|
||||
export function LocationPicker({ value, onChange, onChainChange, required, maxDepth }: LocationPickerProps) {
|
||||
const { t } = useTranslation();
|
||||
const localized = useLocalized();
|
||||
const { data: typesRes, isLoading: typesLoading, isError: typesError, refetch: refetchTypes } = useGetLocationTypesQuery();
|
||||
const { data: locsRes, isLoading: locsLoading, isError: locsError, refetch: refetchLocs } = useGetLocationsQuery({ take: 10000 });
|
||||
|
||||
@@ -74,10 +76,10 @@ export function LocationPicker({ value, onChange, onChainChange, required, maxDe
|
||||
if (currentLevelChildren.length === 0) return '';
|
||||
const typeIds = [...new Set(currentLevelChildren.map((c) => c.locationTypeId))];
|
||||
const names = typeIds
|
||||
.map((id) => typeMap.get(id)?.names.en)
|
||||
.filter(Boolean) as string[];
|
||||
.map((id) => localized(typeMap.get(id)?.names))
|
||||
.filter(Boolean);
|
||||
return names.join(' / ');
|
||||
}, [currentLevelChildren, typeMap]);
|
||||
}, [currentLevelChildren, typeMap, localized]);
|
||||
|
||||
const depth = selectedChain.length;
|
||||
|
||||
@@ -116,7 +118,7 @@ export function LocationPicker({ value, onChange, onChainChange, required, maxDe
|
||||
if (levelIdx === 0) {
|
||||
const roots = childrenByParentId.get('__root__') ?? [];
|
||||
return roots
|
||||
.map((loc) => ({ value: loc.id, label: loc.names.en }))
|
||||
.map((loc) => ({ value: loc.id, label: localized(loc.names) }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
|
||||
@@ -124,7 +126,7 @@ export function LocationPicker({ value, onChange, onChainChange, required, maxDe
|
||||
if (!parent) return [];
|
||||
const children = childrenByParentId.get(parent.id) ?? [];
|
||||
return children
|
||||
.map((loc) => ({ value: loc.id, label: loc.names.en }))
|
||||
.map((loc) => ({ value: loc.id, label: localized(loc.names) }))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
};
|
||||
|
||||
@@ -134,8 +136,8 @@ export function LocationPicker({ value, onChange, onChainChange, required, maxDe
|
||||
if (roots.length === 0) return '';
|
||||
const typeIds = [...new Set(roots.map((r) => r.locationTypeId))];
|
||||
const names = typeIds
|
||||
.map((id) => typeMap.get(id)?.names.en)
|
||||
.filter(Boolean) as string[];
|
||||
.map((id) => localized(typeMap.get(id)?.names))
|
||||
.filter(Boolean);
|
||||
return names.join(' / ');
|
||||
}
|
||||
|
||||
@@ -144,16 +146,16 @@ export function LocationPicker({ value, onChange, onChainChange, required, maxDe
|
||||
const children = childrenByParentId.get(parent.id) ?? [];
|
||||
const typeIds = [...new Set(children.map((c) => c.locationTypeId))];
|
||||
const names = typeIds
|
||||
.map((id) => typeMap.get(id)?.names.en)
|
||||
.filter(Boolean) as string[];
|
||||
.map((id) => localized(typeMap.get(id)?.names))
|
||||
.filter(Boolean);
|
||||
return names.join(' / ');
|
||||
};
|
||||
|
||||
const selectedPath = useMemo(() => {
|
||||
return selectedChain
|
||||
.map((loc) => loc.names.en)
|
||||
.map((loc) => localized(loc.names))
|
||||
.join(' → ');
|
||||
}, [selectedChain]);
|
||||
}, [selectedChain, localized]);
|
||||
|
||||
if (typesLoading || locsLoading) {
|
||||
return (
|
||||
@@ -236,8 +238,8 @@ export function LocationPicker({ value, onChange, onChainChange, required, maxDe
|
||||
color="blue"
|
||||
style={{ textTransform: 'none' }}
|
||||
>
|
||||
{typeInfo ? `${typeInfo.names.en}: ` : ''}
|
||||
{loc.names.en}
|
||||
{typeInfo ? `${localized(typeInfo.names)}: ` : ''}
|
||||
{localized(loc.names)}
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { IconBellOff, IconCheck } from '@tabler/icons-react';
|
||||
import {
|
||||
localized,
|
||||
useLocalized,
|
||||
useGetNotificationsQuery,
|
||||
useGetUnseenNotificationsQuery,
|
||||
useMarkNotificationReadMutation,
|
||||
@@ -39,6 +39,7 @@ const EMPTY_COPY: Record<Tab, string> = {
|
||||
export function NotificationsPage() {
|
||||
const navigate = useNavigate();
|
||||
const showDate = useDateDisplayer();
|
||||
const localized = useLocalized();
|
||||
const [tab, setTab] = useState<Tab>('all');
|
||||
const all = useGetNotificationsQuery(undefined, { skip: tab === 'unseen' });
|
||||
const unseen = useGetUnseenNotificationsQuery(undefined, { skip: tab !== 'unseen' });
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { IconAlertTriangle, IconBuildingWarehouse } from '@tabler/icons-react';
|
||||
import {
|
||||
extractErrorMessage,
|
||||
localized,
|
||||
useLocalized,
|
||||
useGetLicenseTypesQuery,
|
||||
useGetMyOperatorTypesQuery,
|
||||
useUpdateMyOperatorTypesMutation,
|
||||
@@ -41,6 +41,7 @@ export function OperationsFormContent({
|
||||
const { data: catalogue, isLoading: loadingTypes } = useGetLicenseTypesQuery();
|
||||
const { data: mine, isLoading: loadingMine } = useGetMyOperatorTypesQuery();
|
||||
const [save, { isLoading: saving }] = useUpdateMyOperatorTypesMutation();
|
||||
const localized = useLocalized();
|
||||
|
||||
const declaredIds = useMemo(
|
||||
() => (mine?.items ?? []).map((o) => o.licenseTypeId),
|
||||
|
||||
@@ -48,7 +48,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { notify, PageHeader, useErrorHandler, passwordSchema as strongPasswordSchema, PasswordRequirements, getCountryCode } from '@ema-platform/ui';
|
||||
import { useApiMutation } from '@ema-platform/api';
|
||||
import { useApiMutation, useLocalized } from '@ema-platform/api';
|
||||
import { setUser, useCurrentProfile } from '@ema-platform/auth';
|
||||
import { SUPPORTED_LANGUAGES, type AppLanguage } from '../../../i18n/config';
|
||||
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
|
||||
@@ -119,7 +119,8 @@ export function ProfilePage() {
|
||||
|
||||
const [updateTrigger] = useApiMutation<AuthUser>();
|
||||
const [passwordTrigger] = useApiMutation<unknown>();
|
||||
const [fetchProfessions] = useApiMutation<{ count: number; items: Array<{ id: string; name: { en: string } }> }>();
|
||||
const localized = useLocalized();
|
||||
const [fetchProfessions] = useApiMutation<{ count: number; items: Array<{ id: string; name: { en: string; am?: string } }> }>();
|
||||
|
||||
const [isSavingProfile, setIsSavingProfile] = useState(false);
|
||||
const [isSavingPassword, setIsSavingPassword] = useState(false);
|
||||
@@ -129,7 +130,7 @@ export function ProfilePage() {
|
||||
const [emailNotifications, setEmailNotifications] = useState(true);
|
||||
|
||||
// ---- Profession list (for Profile tab) ----
|
||||
const [professions, setProfessions] = useState<Array<{ id: string; name: { en: string } }>>([]);
|
||||
const [professions, setProfessions] = useState<Array<{ id: string; name: { en: string; am?: string } }>>([]);
|
||||
const [professionsLoading, setProfessionsLoading] = useState(true);
|
||||
const professionsFetched = useRef(false);
|
||||
|
||||
@@ -144,8 +145,8 @@ export function ProfilePage() {
|
||||
}, [fetchProfessions]);
|
||||
|
||||
const professionOptions = useMemo(
|
||||
() => professions.map((p) => ({ value: p.id, label: p.name.en })),
|
||||
[professions],
|
||||
() => professions.map((p) => ({ value: p.id, label: localized(p.name) })),
|
||||
[professions, localized],
|
||||
);
|
||||
|
||||
// ---- Profile data ----
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
STATUS_LABELS,
|
||||
TERMINAL_STATUSES,
|
||||
extractErrorMessage,
|
||||
localized,
|
||||
useLocalized,
|
||||
useGetCertificateUrlMutation,
|
||||
useGetMyApplicationsQuery,
|
||||
useGetMyLicensesQuery,
|
||||
@@ -48,6 +48,7 @@ export function WaiverPage() {
|
||||
const { data: licenses } = useGetMyLicensesQuery();
|
||||
const [getCertificateUrl] = useGetCertificateUrlMutation();
|
||||
const showDate = useDateDisplayer();
|
||||
const localized = useLocalized();
|
||||
|
||||
const waiverApplications = (applications?.items ?? []).filter((app) =>
|
||||
WAIVER_TYPE_KEYS.includes(app.licenseType?.key ?? ''),
|
||||
|
||||
Reference in New Issue
Block a user