Enhance user and address handling by normalizing country data and improving address form functionality. Updated country selection in forms to utilize a dynamic list of countries, ensuring consistency and accuracy. Refactored address processing logic to normalize country codes and names, enhancing user experience and data integrity across the application.
This commit is contained in:
parent
2de2e8ec8a
commit
afa0c5306b
@ -401,6 +401,7 @@ export class UsersService {
|
||||
number: string;
|
||||
issuedAt?: string;
|
||||
paidDate?: string;
|
||||
currency?: string | null;
|
||||
}> = [];
|
||||
if (invoicesData.status === "fulfilled") {
|
||||
const invoices: Invoice[] = invoicesData.value.invoices;
|
||||
@ -446,6 +447,7 @@ export class UsersService {
|
||||
total: inv.total,
|
||||
number: inv.number,
|
||||
issuedAt: inv.issuedAt,
|
||||
currency: inv.currency ?? null,
|
||||
}));
|
||||
} else {
|
||||
this.logger.error(`Failed to fetch invoices for user ${userId}`, {
|
||||
@ -459,6 +461,12 @@ export class UsersService {
|
||||
// Add invoice activities
|
||||
recentInvoices.forEach(invoice => {
|
||||
if (invoice.status === "Paid") {
|
||||
const metadata = {
|
||||
amount: invoice.total,
|
||||
currency: invoice.currency ?? "JPY",
|
||||
} as Record<string, unknown>;
|
||||
if (invoice.dueDate) metadata.dueDate = invoice.dueDate;
|
||||
if (invoice.number) metadata.invoiceNumber = invoice.number;
|
||||
activities.push({
|
||||
id: `invoice-paid-${invoice.id}`,
|
||||
type: "invoice_paid",
|
||||
@ -466,8 +474,16 @@ export class UsersService {
|
||||
description: `Payment of ¥${invoice.total.toLocaleString()} processed`,
|
||||
date: invoice.paidDate || invoice.issuedAt || new Date().toISOString(),
|
||||
relatedId: invoice.id,
|
||||
metadata,
|
||||
});
|
||||
} else if (invoice.status === "Unpaid" || invoice.status === "Overdue") {
|
||||
const metadata = {
|
||||
amount: invoice.total,
|
||||
currency: invoice.currency ?? "JPY",
|
||||
} as Record<string, unknown>;
|
||||
if (invoice.dueDate) metadata.dueDate = invoice.dueDate;
|
||||
if (invoice.number) metadata.invoiceNumber = invoice.number;
|
||||
metadata.status = invoice.status;
|
||||
activities.push({
|
||||
id: `invoice-created-${invoice.id}`,
|
||||
type: "invoice_created",
|
||||
@ -475,12 +491,18 @@ export class UsersService {
|
||||
description: `Amount: ¥${invoice.total.toLocaleString()}`,
|
||||
date: invoice.issuedAt || new Date().toISOString(),
|
||||
relatedId: invoice.id,
|
||||
metadata,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add subscription activities
|
||||
recentSubscriptions.forEach(subscription => {
|
||||
const metadata = {
|
||||
productName: subscription.productName,
|
||||
status: subscription.status,
|
||||
} as Record<string, unknown>;
|
||||
if (subscription.registrationDate) metadata.registrationDate = subscription.registrationDate;
|
||||
activities.push({
|
||||
id: `service-activated-${subscription.id}`,
|
||||
type: "service_activated",
|
||||
@ -488,6 +510,7 @@ export class UsersService {
|
||||
description: "Service successfully provisioned",
|
||||
date: subscription.registrationDate,
|
||||
relatedId: subscription.id,
|
||||
metadata,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { useEffect, useState, useCallback } from "react";
|
||||
import { useAuthStore } from "@/features/auth/services/auth.store";
|
||||
import { accountService } from "@/features/account/services/account.service";
|
||||
import { logger } from "@customer-portal/logging";
|
||||
import { getCountryCodeByName } from "@/lib/constants/countries";
|
||||
|
||||
// Use centralized profile types
|
||||
import type { ProfileEditFormData, Address } from "@customer-portal/domain/customer";
|
||||
@ -38,32 +39,28 @@ export function useProfileData() {
|
||||
try {
|
||||
setLoading(true);
|
||||
const address = await accountService.getAddress().catch(() => null);
|
||||
if (address)
|
||||
setBillingInfo({
|
||||
address: {
|
||||
address1: address.address1 || "",
|
||||
address2: address.address2 || "",
|
||||
city: address.city || "",
|
||||
state: address.state || "",
|
||||
postcode: address.postcode || "",
|
||||
country: address.country || "",
|
||||
countryCode: address.countryCode || "",
|
||||
phoneNumber: address.phoneNumber || "",
|
||||
phoneCountryCode: address.phoneCountryCode || "",
|
||||
},
|
||||
});
|
||||
if (address)
|
||||
setAddress({
|
||||
if (address) {
|
||||
const normalizeCountry = (value?: string | null) => {
|
||||
if (!value) return "";
|
||||
if (value.length === 2) return value.toUpperCase();
|
||||
return getCountryCodeByName(value) ?? value;
|
||||
};
|
||||
const normalizedCountry = normalizeCountry(address.country);
|
||||
const normalizedCountryCode = normalizeCountry(address.countryCode ?? address.country);
|
||||
const normalizedAddress: Address = {
|
||||
address1: address.address1 || "",
|
||||
address2: address.address2 || "",
|
||||
city: address.city || "",
|
||||
state: address.state || "",
|
||||
postcode: address.postcode || "",
|
||||
country: address.country || "",
|
||||
countryCode: address.countryCode || "",
|
||||
country: normalizedCountry,
|
||||
countryCode: normalizedCountryCode,
|
||||
phoneNumber: address.phoneNumber || "",
|
||||
phoneCountryCode: address.phoneCountryCode || "",
|
||||
});
|
||||
};
|
||||
setBillingInfo({ address: normalizedAddress });
|
||||
setAddress(normalizedAddress);
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load address information");
|
||||
} finally {
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
XMarkIcon,
|
||||
ExclamationTriangleIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { getCountryName } from "@/lib/constants/countries";
|
||||
import { COUNTRY_OPTIONS, getCountryName } from "@/lib/constants/countries";
|
||||
|
||||
// Use canonical Address type from domain
|
||||
import type { Address } from "@customer-portal/domain/customer";
|
||||
@ -363,12 +363,12 @@ export function AddressConfirmation({
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">Select Country</option>
|
||||
<option value="JP">Japan</option>
|
||||
<option value="US">United States</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="CA">Canada</option>
|
||||
<option value="AU">Australia</option>
|
||||
</select>
|
||||
{COUNTRY_OPTIONS.map(option => (
|
||||
<option key={option.code} value={option.code}>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-3 pt-4">
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { MapPinIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||
import { COUNTRY_OPTIONS, getCountryCodeByName } from "@/lib/constants/countries";
|
||||
import { useZodForm } from "@customer-portal/validation";
|
||||
import {
|
||||
addressFormSchema,
|
||||
@ -68,16 +69,7 @@ const DEFAULT_REQUIRED_FIELDS: (keyof Address)[] = [
|
||||
"country",
|
||||
];
|
||||
|
||||
const COUNTRY_OPTIONS = [
|
||||
{ value: "", label: "Select Country" },
|
||||
{ value: "JP", label: "Japan" },
|
||||
{ value: "US", label: "United States" },
|
||||
{ value: "GB", label: "United Kingdom" },
|
||||
{ value: "CA", label: "Canada" },
|
||||
{ value: "AU", label: "Australia" },
|
||||
{ value: "DE", label: "Germany" },
|
||||
{ value: "FR", label: "France" },
|
||||
];
|
||||
const SELECT_COUNTRY_OPTIONS = [{ code: "", name: "Select Country" }, ...COUNTRY_OPTIONS];
|
||||
|
||||
export function AddressForm({
|
||||
initialAddress = {},
|
||||
@ -100,14 +92,23 @@ export function AddressForm({
|
||||
const placeholders = { ...DEFAULT_PLACEHOLDERS, ...fieldPlaceholders };
|
||||
|
||||
// Create initial values with proper defaults
|
||||
const normalizeCountryValue = (value?: string | null) => {
|
||||
if (!value) return "";
|
||||
if (value.length === 2) return value.toUpperCase();
|
||||
return getCountryCodeByName(value) ?? value;
|
||||
};
|
||||
|
||||
const initialCountry = normalizeCountryValue(initialAddress.country);
|
||||
const initialCountryCode = normalizeCountryValue(initialAddress.countryCode ?? initialCountry);
|
||||
|
||||
const initialValues: AddressFormData = {
|
||||
address1: initialAddress.address1 || "",
|
||||
address2: initialAddress.address2 || "",
|
||||
city: initialAddress.city || "",
|
||||
state: initialAddress.state || "",
|
||||
postcode: initialAddress.postcode || "",
|
||||
country: initialAddress.country || "",
|
||||
countryCode: initialAddress.countryCode || "",
|
||||
country: initialCountry,
|
||||
countryCode: initialCountryCode,
|
||||
phoneNumber: initialAddress.phoneNumber || "",
|
||||
phoneCountryCode: initialAddress.phoneCountryCode || "",
|
||||
};
|
||||
@ -122,7 +123,15 @@ export function AddressForm({
|
||||
const handleFieldChange = (field: keyof Address, value: string) => {
|
||||
if (disabled) return;
|
||||
|
||||
form.setValue(field, value);
|
||||
if (field === "country") {
|
||||
form.setValue("country", value);
|
||||
form.setValue("countryCode", value);
|
||||
} else if (field === "countryCode") {
|
||||
form.setValue("countryCode", value);
|
||||
form.setValue("country", value);
|
||||
} else {
|
||||
form.setValue(field, value);
|
||||
}
|
||||
|
||||
// Custom validation if provided
|
||||
if (customValidation) {
|
||||
@ -132,7 +141,15 @@ export function AddressForm({
|
||||
}
|
||||
|
||||
// Check if address is complete and valid
|
||||
const updatedValues = { ...form.values, [field]: value };
|
||||
const updatedValues = { ...form.values, [field]: value } as AddressFormData;
|
||||
if (field === "country") {
|
||||
updatedValues.country = value;
|
||||
updatedValues.countryCode = value;
|
||||
} else if (field === "countryCode") {
|
||||
updatedValues.countryCode = value;
|
||||
updatedValues.country = value;
|
||||
}
|
||||
|
||||
const isComplete = requiredFields
|
||||
.filter(f => !hiddenFields.includes(f))
|
||||
.every(f => updatedValues[f] && String(updatedValues[f]).trim());
|
||||
@ -147,9 +164,20 @@ export function AddressForm({
|
||||
if (initialAddress) {
|
||||
Object.entries(initialAddress).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
form.setValue(key as keyof Address, value || "");
|
||||
const normalizedValue =
|
||||
key === "country" || key === "countryCode"
|
||||
? normalizeCountryValue(value as string | undefined)
|
||||
: value || "";
|
||||
form.setValue(key as keyof Address, normalizedValue);
|
||||
}
|
||||
});
|
||||
|
||||
const normalizedCountry = normalizeCountryValue(initialAddress.country);
|
||||
const normalizedCountryCode = normalizeCountryValue(
|
||||
initialAddress.countryCode ?? initialAddress.country
|
||||
);
|
||||
form.setValue("country", normalizedCountry);
|
||||
form.setValue("countryCode", normalizedCountryCode);
|
||||
}
|
||||
}, [initialAddress]);
|
||||
|
||||
@ -191,9 +219,9 @@ export function AddressForm({
|
||||
className={baseInputClasses}
|
||||
disabled={disabled}
|
||||
>
|
||||
{COUNTRY_OPTIONS.map(option => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
{SELECT_COUNTRY_OPTIONS.map(option => (
|
||||
<option key={option.code} value={option.code}>
|
||||
{option.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@ -142,11 +142,7 @@ export function ActivityFeed({
|
||||
return (
|
||||
<DashboardActivityItem
|
||||
key={activity.id}
|
||||
id={activity.id}
|
||||
type={activity.type}
|
||||
title={activity.title ?? ""}
|
||||
description={activity.description ?? ""}
|
||||
date={activity.date}
|
||||
activity={activity}
|
||||
onClick={clickable ? () => handleActivityClick(activity) : undefined}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { ComponentType, SVGProps } from "react";
|
||||
import {
|
||||
DocumentTextIcon,
|
||||
CheckCircleIcon,
|
||||
@ -7,39 +8,37 @@ import {
|
||||
ChatBubbleLeftRightIcon,
|
||||
ExclamationTriangleIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import type { Activity } from "@customer-portal/domain/dashboard";
|
||||
import {
|
||||
formatActivityDate,
|
||||
formatActivityDescription,
|
||||
getActivityIconGradient,
|
||||
} from "../utils/dashboard.utils";
|
||||
|
||||
export function DashboardActivityItem({
|
||||
id,
|
||||
type,
|
||||
title,
|
||||
description,
|
||||
date,
|
||||
onClick,
|
||||
}: {
|
||||
id: string | number;
|
||||
type: string;
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
interface DashboardActivityItemProps {
|
||||
activity: Activity;
|
||||
onClick?: () => void;
|
||||
}) {
|
||||
const map: IconMap = {
|
||||
invoice_created: { icon: DocumentTextIcon, gradient: "from-blue-500 to-cyan-500" },
|
||||
invoice_paid: { icon: CheckCircleIcon, gradient: "from-green-500 to-emerald-500" },
|
||||
service_activated: { icon: ServerIcon, gradient: "from-purple-500 to-pink-500" },
|
||||
case_created: { icon: ChatBubbleLeftRightIcon, gradient: "from-yellow-500 to-orange-500" },
|
||||
case_closed: { icon: CheckCircleIcon, gradient: "from-green-500 to-emerald-500" },
|
||||
};
|
||||
const fallback = { icon: ExclamationTriangleIcon, gradient: "from-gray-500 to-slate-500" };
|
||||
const conf = map[type as keyof IconMap] ?? fallback;
|
||||
const Icon = conf.icon;
|
||||
const gradient = conf.gradient;
|
||||
}
|
||||
|
||||
const ICON_COMPONENTS: Record<Activity["type"], ComponentType<SVGProps<SVGSVGElement>>> = {
|
||||
invoice_created: DocumentTextIcon,
|
||||
invoice_paid: CheckCircleIcon,
|
||||
service_activated: ServerIcon,
|
||||
case_created: ChatBubbleLeftRightIcon,
|
||||
case_closed: CheckCircleIcon,
|
||||
};
|
||||
|
||||
const FALLBACK_ICON = ExclamationTriangleIcon;
|
||||
|
||||
export function DashboardActivityItem({ activity, onClick }: DashboardActivityItemProps) {
|
||||
const Icon = ICON_COMPONENTS[activity.type] ?? FALLBACK_ICON;
|
||||
const gradient = getActivityIconGradient(activity.type);
|
||||
const description = formatActivityDescription(activity);
|
||||
const formattedDate = formatActivityDate(activity.date);
|
||||
const Wrapper = onClick ? "button" : "div";
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
key={id}
|
||||
className={`flex items-start space-x-4 w-full text-left ${
|
||||
onClick ? "p-3 -m-3 rounded-lg hover:bg-gray-50 transition-colors cursor-pointer" : ""
|
||||
}`}
|
||||
@ -54,22 +53,15 @@ export function DashboardActivityItem({
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p
|
||||
className={`text-sm font-medium ${onClick ? "text-blue-900 group-hover:text-blue-700" : "text-gray-900"}`}
|
||||
className={`text-sm font-medium ${
|
||||
onClick ? "text-blue-900 group-hover:text-blue-700" : "text-gray-900"
|
||||
}`}
|
||||
>
|
||||
{title}
|
||||
{activity.title}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mt-1">{description}</p>
|
||||
<p className="text-xs text-gray-400 mt-2">{date}</p>
|
||||
<p className="text-xs text-gray-400 mt-2">{formattedDate}</p>
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Local type helper
|
||||
type IconMap = {
|
||||
invoice_created: { icon: React.ComponentType<React.SVGProps<SVGSVGElement>>; gradient: string };
|
||||
invoice_paid: { icon: React.ComponentType<React.SVGProps<SVGSVGElement>>; gradient: string };
|
||||
service_activated: { icon: React.ComponentType<React.SVGProps<SVGSVGElement>>; gradient: string };
|
||||
case_created: { icon: React.ComponentType<React.SVGProps<SVGSVGElement>>; gradient: string };
|
||||
case_closed: { icon: React.ComponentType<React.SVGProps<SVGSVGElement>>; gradient: string };
|
||||
};
|
||||
|
||||
@ -3,7 +3,12 @@
|
||||
* Helper functions for dashboard data processing and formatting
|
||||
*/
|
||||
|
||||
import type { Activity, ActivityFilter, ActivityFilterConfig } from "@customer-portal/domain/dashboard";
|
||||
import { z } from "zod";
|
||||
import type {
|
||||
Activity,
|
||||
ActivityFilter,
|
||||
ActivityFilterConfig,
|
||||
} from "@customer-portal/domain/dashboard";
|
||||
|
||||
/**
|
||||
* Activity filter configurations
|
||||
@ -118,6 +123,84 @@ export function getActivityIconGradient(activityType: Activity["type"]): string
|
||||
return gradientMap[activityType] || "from-gray-500 to-slate-500";
|
||||
}
|
||||
|
||||
const invoiceActivityMetadataSchema = z
|
||||
.object({
|
||||
amount: z.number(),
|
||||
currency: z.string().optional(),
|
||||
dueDate: z.string().optional(),
|
||||
invoiceNumber: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
})
|
||||
.partial()
|
||||
.refine(data => typeof data.amount === "number", {
|
||||
message: "amount is required",
|
||||
path: ["amount"],
|
||||
});
|
||||
|
||||
const serviceActivityMetadataSchema = z
|
||||
.object({
|
||||
productName: z.string().optional(),
|
||||
registrationDate: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
const currencyFormatterCache = new Map<string, Intl.NumberFormat>();
|
||||
|
||||
const formatCurrency = (amount: number, currency?: string) => {
|
||||
const code = (currency || "JPY").toUpperCase();
|
||||
const formatter =
|
||||
currencyFormatterCache.get(code) ||
|
||||
(() => {
|
||||
try {
|
||||
const intl = new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: code,
|
||||
});
|
||||
currencyFormatterCache.set(code, intl);
|
||||
return intl;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
if (!formatter) {
|
||||
return `${code} ${amount.toLocaleString()}`;
|
||||
}
|
||||
|
||||
return formatter.format(amount);
|
||||
};
|
||||
|
||||
export function formatActivityDescription(activity: Activity): string {
|
||||
switch (activity.type) {
|
||||
case "invoice_created":
|
||||
case "invoice_paid": {
|
||||
const parsed = invoiceActivityMetadataSchema.safeParse(activity.metadata ?? {});
|
||||
if (parsed.success && typeof parsed.data.amount === "number") {
|
||||
const formattedAmount = formatCurrency(parsed.data.amount, parsed.data.currency);
|
||||
if (formattedAmount) {
|
||||
return activity.type === "invoice_paid"
|
||||
? `${formattedAmount} payment completed`
|
||||
: `${formattedAmount} invoice generated`;
|
||||
}
|
||||
}
|
||||
return activity.description ?? "";
|
||||
}
|
||||
case "service_activated": {
|
||||
const parsed = serviceActivityMetadataSchema.safeParse(activity.metadata ?? {});
|
||||
if (parsed.success && parsed.data.productName) {
|
||||
return `${parsed.data.productName} is now active`;
|
||||
}
|
||||
return activity.description ?? "";
|
||||
}
|
||||
case "case_created":
|
||||
case "case_closed":
|
||||
return activity.description ?? "";
|
||||
default:
|
||||
return activity.description ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate text to specified length
|
||||
*/
|
||||
|
||||
@ -370,11 +370,7 @@ function RecentActivityCard({
|
||||
return (
|
||||
<DashboardActivityItem
|
||||
key={activity.id}
|
||||
id={activity.id}
|
||||
type={activity.type}
|
||||
title={activity.title ?? ""}
|
||||
description={activity.description ?? ""}
|
||||
date={format(new Date(activity.date), "MMM d, yyyy · h:mm a")}
|
||||
activity={activity}
|
||||
onClick={isClickable ? () => onItemClick(activity) : undefined}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -1,42 +1,51 @@
|
||||
import countries from "world-countries";
|
||||
|
||||
export interface CountryOption {
|
||||
code: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const COUNTRY_OPTIONS: CountryOption[] = [
|
||||
{ code: "US", name: "United States" },
|
||||
{ code: "CA", name: "Canada" },
|
||||
{ code: "GB", name: "United Kingdom" },
|
||||
{ code: "AU", name: "Australia" },
|
||||
{ code: "DE", name: "Germany" },
|
||||
{ code: "FR", name: "France" },
|
||||
{ code: "IT", name: "Italy" },
|
||||
{ code: "ES", name: "Spain" },
|
||||
{ code: "NL", name: "Netherlands" },
|
||||
{ code: "SE", name: "Sweden" },
|
||||
{ code: "NO", name: "Norway" },
|
||||
{ code: "DK", name: "Denmark" },
|
||||
{ code: "FI", name: "Finland" },
|
||||
{ code: "CH", name: "Switzerland" },
|
||||
{ code: "AT", name: "Austria" },
|
||||
{ code: "BE", name: "Belgium" },
|
||||
{ code: "IE", name: "Ireland" },
|
||||
{ code: "PT", name: "Portugal" },
|
||||
{ code: "GR", name: "Greece" },
|
||||
{ code: "JP", name: "Japan" },
|
||||
];
|
||||
const normalizedCountries = countries
|
||||
.filter(country => country.cca2 && country.cca2.length === 2 && country.name?.common)
|
||||
.map(country => {
|
||||
const code = country.cca2.toUpperCase();
|
||||
const commonName = country.name.common;
|
||||
return {
|
||||
code,
|
||||
name: commonName,
|
||||
searchKeys: [
|
||||
commonName,
|
||||
country.name.official,
|
||||
...(country.altSpellings ?? []),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map(entry => entry.toLowerCase()),
|
||||
};
|
||||
});
|
||||
|
||||
const COUNTRY_NAME_BY_CODE = new Map(COUNTRY_OPTIONS.map(option => [option.code, option.name]));
|
||||
const COUNTRY_CODE_BY_NAME = new Map(
|
||||
COUNTRY_OPTIONS.map(option => [option.name.toLowerCase(), option.code])
|
||||
export const COUNTRY_OPTIONS: CountryOption[] = normalizedCountries
|
||||
.map(({ code, name }) => ({ code, name }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" }));
|
||||
|
||||
const COUNTRY_NAME_BY_CODE = new Map(
|
||||
normalizedCountries.map(({ code, name }) => [code, name] as const)
|
||||
);
|
||||
|
||||
const COUNTRY_CODE_BY_NAME = new Map<string, string>();
|
||||
normalizedCountries.forEach(({ code, searchKeys }) => {
|
||||
searchKeys.forEach(key => {
|
||||
if (key && !COUNTRY_CODE_BY_NAME.has(key)) {
|
||||
COUNTRY_CODE_BY_NAME.set(key, code);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export function getCountryName(code?: string | null): string | undefined {
|
||||
if (!code) return undefined;
|
||||
return COUNTRY_NAME_BY_CODE.get(code) ?? undefined;
|
||||
return COUNTRY_NAME_BY_CODE.get(code.toUpperCase());
|
||||
}
|
||||
|
||||
export function getCountryCodeByName(name?: string | null): string | undefined {
|
||||
if (!name) return undefined;
|
||||
return COUNTRY_CODE_BY_NAME.get(name.toLowerCase()) ?? undefined;
|
||||
return COUNTRY_CODE_BY_NAME.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user