Browse Source
feat: add bottom sheet for currency selection with auto-mapping based on location country code
master
feat: add bottom sheet for currency selection with auto-mapping based on location country code
master
5 changed files with 827 additions and 399 deletions
-
68src/components/Componentes/currency-sheet.test.tsx
-
274src/components/Componentes/currency-sheet.tsx
-
439src/components/Componentes/question-number.tsx
-
58src/data/currencies.test.ts
-
381src/data/currencies.ts
@ -0,0 +1,68 @@ |
|||||
|
import { render, screen, fireEvent, cleanup } from "@testing-library/react"; |
||||
|
import { describe, expect, it, vi, afterEach } from "vitest"; |
||||
|
import { CurrencySheet } from "./currency-sheet"; |
||||
|
|
||||
|
describe("CurrencySheet", () => { |
||||
|
afterEach(() => { |
||||
|
cleanup(); |
||||
|
}); |
||||
|
|
||||
|
it("should render currency sheet when isOpen is true", () => { |
||||
|
const handleClose = vi.fn(); |
||||
|
const handleSelect = vi.fn(); |
||||
|
|
||||
|
render( |
||||
|
<CurrencySheet |
||||
|
isOpen={true} |
||||
|
onClose={handleClose} |
||||
|
selectedCurrency="TOMAN" |
||||
|
onSelectCurrency={handleSelect} |
||||
|
title="انتخاب ارز" |
||||
|
/>, |
||||
|
); |
||||
|
|
||||
|
expect(screen.getByText("انتخاب ارز")).toBeDefined(); |
||||
|
expect(screen.getByText("TOMAN")).toBeDefined(); |
||||
|
expect(screen.getAllByText("USD").length).toBeGreaterThan(0); |
||||
|
}); |
||||
|
|
||||
|
it("should filter currencies when typing in search input", () => { |
||||
|
const handleClose = vi.fn(); |
||||
|
const handleSelect = vi.fn(); |
||||
|
|
||||
|
render( |
||||
|
<CurrencySheet |
||||
|
isOpen={true} |
||||
|
onClose={handleClose} |
||||
|
selectedCurrency="USD" |
||||
|
onSelectCurrency={handleSelect} |
||||
|
/>, |
||||
|
); |
||||
|
|
||||
|
const searchInput = screen.getByPlaceholderText("Search currency or country..."); |
||||
|
fireEvent.change(searchInput, { target: { value: "EUR" } }); |
||||
|
|
||||
|
expect(screen.getByText("EUR")).toBeDefined(); |
||||
|
expect(screen.queryByText("TOMAN")).toBeNull(); |
||||
|
}); |
||||
|
|
||||
|
it("should call onSelectCurrency when a currency item is clicked", () => { |
||||
|
const handleClose = vi.fn(); |
||||
|
const handleSelect = vi.fn(); |
||||
|
|
||||
|
render( |
||||
|
<CurrencySheet |
||||
|
isOpen={true} |
||||
|
onClose={handleClose} |
||||
|
selectedCurrency="TOMAN" |
||||
|
onSelectCurrency={handleSelect} |
||||
|
/>, |
||||
|
); |
||||
|
|
||||
|
const eurButton = screen.getByText("Euro").closest("button"); |
||||
|
expect(eurButton).not.toBeNull(); |
||||
|
fireEvent.click(eurButton!); |
||||
|
|
||||
|
expect(handleSelect).toHaveBeenCalledWith("EUR"); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,274 @@ |
|||||
|
"use client"; |
||||
|
|
||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; |
||||
|
import { createPortal } from "react-dom"; |
||||
|
import { CURRENCIES, type CurrencyItem } from "@/data/currencies"; |
||||
|
import { useI18n } from "@/translations/provider"; |
||||
|
import { useSheetScrollLock } from "./use-sheet-scroll-lock"; |
||||
|
|
||||
|
const EXIT_ANIMATION_MS = 300; |
||||
|
|
||||
|
export type CurrencySheetProps = { |
||||
|
isOpen: boolean; |
||||
|
onClose: () => void; |
||||
|
selectedCurrency: string; |
||||
|
onSelectCurrency: (code: string) => void; |
||||
|
title?: string; |
||||
|
}; |
||||
|
|
||||
|
export function CurrencySheet({ |
||||
|
isOpen, |
||||
|
onClose, |
||||
|
selectedCurrency, |
||||
|
onSelectCurrency, |
||||
|
title, |
||||
|
}: CurrencySheetProps) { |
||||
|
const { dictionary: t, locale } = useI18n(); |
||||
|
const [isClosing, setIsClosing] = useState(false); |
||||
|
const [searchQuery, setSearchQuery] = useState(""); |
||||
|
const listRef = useRef<HTMLDivElement>(null); |
||||
|
const sheetRef = useRef<HTMLElement>(null); |
||||
|
|
||||
|
const isRtl = locale === "fa" || locale === "ar" || locale === "ur"; |
||||
|
const defaultTitle = isRtl ? "انتخاب ارز" : "Select Currency"; |
||||
|
const sheetTitle = title || defaultTitle; |
||||
|
|
||||
|
const closeSheet = useCallback(() => { |
||||
|
setIsClosing(true); |
||||
|
window.setTimeout(() => { |
||||
|
onClose(); |
||||
|
setIsClosing(false); |
||||
|
setSearchQuery(""); |
||||
|
}, EXIT_ANIMATION_MS); |
||||
|
}, [onClose]); |
||||
|
|
||||
|
useSheetScrollLock(isOpen && !isClosing, { onBack: closeSheet }); |
||||
|
|
||||
|
// Escape key handler
|
||||
|
useEffect(() => { |
||||
|
if (!isOpen) return; |
||||
|
const handleKeyDown = (e: KeyboardEvent) => { |
||||
|
if (e.key === "Escape") { |
||||
|
closeSheet(); |
||||
|
} |
||||
|
}; |
||||
|
window.addEventListener("keydown", handleKeyDown); |
||||
|
return () => window.removeEventListener("keydown", handleKeyDown); |
||||
|
}, [isOpen, closeSheet]); |
||||
|
|
||||
|
const filteredCurrencies = useMemo(() => { |
||||
|
const q = searchQuery.toLowerCase().trim(); |
||||
|
if (!q) return CURRENCIES; |
||||
|
return CURRENCIES.filter( |
||||
|
(c) => |
||||
|
c.code.toLowerCase().includes(q) || |
||||
|
c.nameEn.toLowerCase().includes(q) || |
||||
|
c.nameFa.toLowerCase().includes(q) || |
||||
|
(c.symbol && c.symbol.toLowerCase().includes(q)), |
||||
|
); |
||||
|
}, [searchQuery]); |
||||
|
|
||||
|
const handleSelect = (code: string) => { |
||||
|
onSelectCurrency(code); |
||||
|
closeSheet(); |
||||
|
}; |
||||
|
|
||||
|
const searchPlaceholder = |
||||
|
locale === "fa" |
||||
|
? "جستجوی ارز یا کشور..." |
||||
|
: locale === "ar" |
||||
|
? "بحث عن العملة..." |
||||
|
: "Search currency or country..."; |
||||
|
|
||||
|
const noResultsText = |
||||
|
locale === "fa" |
||||
|
? "ارزی یافت نشد" |
||||
|
: locale === "ar" |
||||
|
? "لم يتم العثور على عملات" |
||||
|
: "No currencies found"; |
||||
|
|
||||
|
if (!isOpen && !isClosing) return null; |
||||
|
|
||||
|
return createPortal( |
||||
|
<div |
||||
|
className="fixed inset-0 z-50 flex items-end justify-center bg-black/40 backdrop-blur-[2px] transition-opacity duration-200" |
||||
|
role="dialog" |
||||
|
aria-label={sheetTitle} |
||||
|
onKeyDown={(e) => { |
||||
|
if (e.key === "Escape") closeSheet(); |
||||
|
}} |
||||
|
onClick={(event) => { |
||||
|
if (event.target === event.currentTarget) closeSheet(); |
||||
|
}} |
||||
|
> |
||||
|
<section |
||||
|
ref={sheetRef} |
||||
|
className={[ |
||||
|
"flex w-full flex-col overflow-hidden rounded-t-[24px] bg-white pb-[env(safe-area-inset-bottom)] shadow-[0_-10px_32px_rgba(0,0,0,0.16)] transition-transform duration-[300ms] ease-out sm:max-w-[420px] h-[82svh] min-h-[82svh] max-h-[82svh]", |
||||
|
isClosing ? "translate-y-full" : "translate-y-0", |
||||
|
].join(" ")} |
||||
|
> |
||||
|
{/* Header */} |
||||
|
<div className="flex items-center justify-between px-5 pt-3 pb-3 border-b border-[#F2F4F7]"> |
||||
|
<h3 className="text-[17px] font-bold text-[#181818] truncate pr-2"> |
||||
|
{sheetTitle} |
||||
|
</h3> |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={closeSheet} |
||||
|
className="flex size-8 shrink-0 items-center justify-center rounded-full text-[#667085] hover:bg-[#F2F4F7] hover:text-[#181818] transition-colors cursor-pointer" |
||||
|
aria-label="Close" |
||||
|
> |
||||
|
<svg |
||||
|
aria-hidden="true" |
||||
|
width="18" |
||||
|
height="18" |
||||
|
viewBox="0 0 24 24" |
||||
|
fill="none" |
||||
|
stroke="currentColor" |
||||
|
strokeWidth="2.2" |
||||
|
strokeLinecap="round" |
||||
|
strokeLinejoin="round" |
||||
|
> |
||||
|
<line x1="18" y1="6" x2="6" y2="18" /> |
||||
|
<line x1="6" y1="6" x2="18" y2="18" /> |
||||
|
</svg> |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
{/* Search Bar */} |
||||
|
<div className="px-5 pt-3.5 pb-2"> |
||||
|
<div className="flex h-[46px] w-full items-center gap-2.5 rounded-[14px] bg-[#F2F4F7] px-3.5 transition-colors focus-within:bg-[#EAECF0]"> |
||||
|
<svg |
||||
|
aria-hidden="true" |
||||
|
width="18" |
||||
|
height="18" |
||||
|
viewBox="0 0 18 18" |
||||
|
fill="none" |
||||
|
className="shrink-0 text-[#667085]" |
||||
|
> |
||||
|
<path |
||||
|
d="M8.25 14.25C11.5637 14.25 14.25 11.5637 14.25 8.25C14.25 4.93629 11.5637 2.25 8.25 2.25C4.93629 2.25 2.25 4.93629 2.25 8.25C2.25 11.5637 4.93629 14.25 8.25 14.25Z" |
||||
|
stroke="#667085" |
||||
|
strokeWidth="1.75" |
||||
|
strokeLinecap="round" |
||||
|
strokeLinejoin="round" |
||||
|
/> |
||||
|
<path |
||||
|
d="M15.75 15.75L12.5 12.5" |
||||
|
stroke="#667085" |
||||
|
strokeWidth="1.75" |
||||
|
strokeLinecap="round" |
||||
|
strokeLinejoin="round" |
||||
|
/> |
||||
|
</svg> |
||||
|
<input |
||||
|
type="text" |
||||
|
name="currency_sheet_search" |
||||
|
autoComplete="off" |
||||
|
autoCorrect="off" |
||||
|
autoCapitalize="none" |
||||
|
spellCheck="false" |
||||
|
value={searchQuery} |
||||
|
onChange={(e) => setSearchQuery(e.target.value)} |
||||
|
placeholder={searchPlaceholder} |
||||
|
className="flex-1 bg-transparent text-[14px] font-medium text-[#181818] outline-none placeholder:text-[#98A2B3]" |
||||
|
/> |
||||
|
{searchQuery ? ( |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => setSearchQuery("")} |
||||
|
className="text-[#667085] hover:text-[#181818] text-xs font-semibold p-1 cursor-pointer" |
||||
|
> |
||||
|
✕ |
||||
|
</button> |
||||
|
) : null} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{/* Currencies List */} |
||||
|
<div |
||||
|
ref={listRef} |
||||
|
onTouchStart={(event) => event.stopPropagation()} |
||||
|
onTouchMove={(event) => event.stopPropagation()} |
||||
|
onTouchEnd={(event) => event.stopPropagation()} |
||||
|
className="flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto overscroll-contain px-5 py-3" |
||||
|
> |
||||
|
{filteredCurrencies.length > 0 ? ( |
||||
|
filteredCurrencies.map((item: CurrencyItem) => { |
||||
|
const isSelected = item.code === selectedCurrency; |
||||
|
const displayName = |
||||
|
locale === "fa" || locale === "fa-ir" |
||||
|
? item.nameFa |
||||
|
: item.nameEn; |
||||
|
|
||||
|
return ( |
||||
|
<button |
||||
|
key={item.code} |
||||
|
type="button" |
||||
|
onClick={() => handleSelect(item.code)} |
||||
|
className={[ |
||||
|
"flex min-h-[52px] w-full items-center justify-between gap-3 rounded-[14px] border px-3.5 py-2.5 text-start transition-all cursor-pointer", |
||||
|
isSelected |
||||
|
? "bg-[#FFF4F5] border-[#F0445B]/40 text-[#181818] shadow-xs" |
||||
|
: "bg-white hover:bg-[#F8F9FA] border-[#EAECF0] text-[#181818]", |
||||
|
].join(" ")} |
||||
|
> |
||||
|
<div className="flex items-center gap-3 min-w-0"> |
||||
|
{/* Radio selection circle */} |
||||
|
<div |
||||
|
className={[ |
||||
|
"size-[22px] shrink-0 rounded-full transition-all duration-150 flex items-center justify-center", |
||||
|
isSelected |
||||
|
? "border-[6px] border-[#F0445B] bg-white" |
||||
|
: "border-[2px] border-[#98A2B3] bg-white", |
||||
|
].join(" ")} |
||||
|
/> |
||||
|
|
||||
|
{/* Currency name and symbol */} |
||||
|
<div className="flex flex-col min-w-0"> |
||||
|
<span |
||||
|
className={[ |
||||
|
"text-[15px] leading-tight truncate", |
||||
|
isSelected |
||||
|
? "font-bold text-[#181818]" |
||||
|
: "font-medium text-[#344054]", |
||||
|
].join(" ")} |
||||
|
> |
||||
|
{displayName} |
||||
|
</span> |
||||
|
{item.symbol && item.symbol !== item.code && ( |
||||
|
<span className="text-[12px] text-[#667085] mt-0.5"> |
||||
|
{item.symbol} |
||||
|
</span> |
||||
|
)} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{/* Currency code badge */} |
||||
|
<span |
||||
|
className={[ |
||||
|
"shrink-0 px-2.5 py-1 rounded-[8px] text-[13px] font-bold tracking-wider uppercase font-mono", |
||||
|
isSelected |
||||
|
? "bg-[#F0445B] text-white" |
||||
|
: "bg-[#F2F4F7] text-[#475467]", |
||||
|
].join(" ")} |
||||
|
> |
||||
|
{item.code} |
||||
|
</span> |
||||
|
</button> |
||||
|
); |
||||
|
}) |
||||
|
) : ( |
||||
|
<div className="py-12 text-center text-[14px] text-[#667085]"> |
||||
|
{noResultsText} |
||||
|
</div> |
||||
|
)} |
||||
|
</div> |
||||
|
</section> |
||||
|
</div>, |
||||
|
document.body, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export default CurrencySheet; |
||||
@ -0,0 +1,58 @@ |
|||||
|
import { describe, expect, it } from "vitest"; |
||||
|
import { |
||||
|
COUNTRY_CODE_TO_CURRENCY, |
||||
|
resolveDefaultCurrency, |
||||
|
CURRENCIES, |
||||
|
} from "./currencies"; |
||||
|
|
||||
|
describe("currencies", () => { |
||||
|
it("should map country code IR to TOMAN", () => { |
||||
|
expect(COUNTRY_CODE_TO_CURRENCY["IR"]).toBe("TOMAN"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "IR" })).toBe("TOMAN"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "ir" })).toBe("TOMAN"); |
||||
|
}); |
||||
|
|
||||
|
it("should map country codes correctly for other countries", () => { |
||||
|
expect(resolveDefaultCurrency({ countryCode: "US" })).toBe("USD"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "GB" })).toBe("GBP"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "DE" })).toBe("EUR"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "AE" })).toBe("AED"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "TR" })).toBe("TRY"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "IQ" })).toBe("IQD"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "AF" })).toBe("AFN"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "CA" })).toBe("CAD"); |
||||
|
expect(resolveDefaultCurrency({ countryCode: "TJ" })).toBe("TJS"); |
||||
|
}); |
||||
|
|
||||
|
it("should map country names in English and Persian correctly", () => { |
||||
|
expect(resolveDefaultCurrency({ countryName: "Iran" })).toBe("TOMAN"); |
||||
|
expect(resolveDefaultCurrency({ countryName: "ایران" })).toBe("TOMAN"); |
||||
|
expect(resolveDefaultCurrency({ countryName: "Iraq" })).toBe("IQD"); |
||||
|
expect(resolveDefaultCurrency({ countryName: "عراق" })).toBe("IQD"); |
||||
|
expect(resolveDefaultCurrency({ countryName: "Turkey" })).toBe("TRY"); |
||||
|
expect(resolveDefaultCurrency({ countryName: "ترکیه" })).toBe("TRY"); |
||||
|
expect(resolveDefaultCurrency({ countryName: "United Arab Emirates" })).toBe("AED"); |
||||
|
expect(resolveDefaultCurrency({ countryName: "امارات" })).toBe("AED"); |
||||
|
}); |
||||
|
|
||||
|
it("should fallback to TOMAN for Persian locale if no country is detected", () => { |
||||
|
expect(resolveDefaultCurrency({ fallbackLocale: "fa" })).toBe("TOMAN"); |
||||
|
expect(resolveDefaultCurrency({ fallbackLocale: "fa-ir" })).toBe("TOMAN"); |
||||
|
}); |
||||
|
|
||||
|
it("should fallback to USD for other locales if no country is detected", () => { |
||||
|
expect(resolveDefaultCurrency({ fallbackLocale: "en" })).toBe("USD"); |
||||
|
expect(resolveDefaultCurrency({})).toBe("USD"); |
||||
|
}); |
||||
|
|
||||
|
it("should have valid currency items in CURRENCIES list", () => { |
||||
|
expect(CURRENCIES.length).toBeGreaterThan(50); |
||||
|
const toman = CURRENCIES.find((c) => c.code === "TOMAN"); |
||||
|
expect(toman).toBeDefined(); |
||||
|
expect(toman?.nameFa).toBe("تومان ایران"); |
||||
|
|
||||
|
const usd = CURRENCIES.find((c) => c.code === "USD"); |
||||
|
expect(usd).toBeDefined(); |
||||
|
expect(usd?.nameEn).toBe("US Dollar"); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,381 @@ |
|||||
|
export type CurrencyItem = { |
||||
|
code: string; |
||||
|
nameEn: string; |
||||
|
nameFa: string; |
||||
|
symbol?: string; |
||||
|
}; |
||||
|
|
||||
|
export const COUNTRY_CODE_TO_CURRENCY: Record<string, string> = { |
||||
|
IR: "TOMAN", |
||||
|
US: "USD", |
||||
|
GB: "GBP", |
||||
|
UK: "GBP", |
||||
|
DE: "EUR", |
||||
|
FR: "EUR", |
||||
|
IT: "EUR", |
||||
|
ES: "EUR", |
||||
|
NL: "EUR", |
||||
|
BE: "EUR", |
||||
|
AT: "EUR", |
||||
|
PT: "EUR", |
||||
|
GR: "EUR", |
||||
|
FI: "EUR", |
||||
|
IE: "EUR", |
||||
|
SK: "EUR", |
||||
|
SI: "EUR", |
||||
|
LT: "EUR", |
||||
|
LV: "EUR", |
||||
|
EE: "EUR", |
||||
|
CY: "EUR", |
||||
|
MT: "EUR", |
||||
|
LU: "EUR", |
||||
|
MC: "EUR", |
||||
|
SM: "EUR", |
||||
|
VA: "EUR", |
||||
|
AD: "EUR", |
||||
|
ME: "EUR", |
||||
|
XK: "EUR", |
||||
|
AE: "AED", |
||||
|
TR: "TRY", |
||||
|
IQ: "IQD", |
||||
|
AF: "AFN", |
||||
|
PK: "PKR", |
||||
|
SA: "SAR", |
||||
|
QA: "QAR", |
||||
|
KW: "KWD", |
||||
|
OM: "OMR", |
||||
|
BH: "BHD", |
||||
|
CA: "CAD", |
||||
|
AU: "AUD", |
||||
|
SE: "SEK", |
||||
|
NO: "NOK", |
||||
|
DK: "DKK", |
||||
|
CH: "CHF", |
||||
|
RU: "RUB", |
||||
|
CN: "CNY", |
||||
|
JP: "JPY", |
||||
|
KR: "KRW", |
||||
|
IN: "INR", |
||||
|
MY: "MYR", |
||||
|
ID: "IDR", |
||||
|
TH: "THB", |
||||
|
SG: "SGD", |
||||
|
NZ: "NZD", |
||||
|
ZA: "ZAR", |
||||
|
BR: "BRL", |
||||
|
MX: "MXN", |
||||
|
AR: "ARS", |
||||
|
AZ: "AZN", |
||||
|
TJ: "TJS", |
||||
|
UZ: "UZS", |
||||
|
TM: "TMT", |
||||
|
KZ: "KZT", |
||||
|
KG: "KGS", |
||||
|
GE: "GEL", |
||||
|
AM: "AMD", |
||||
|
LB: "LBP", |
||||
|
SY: "SYP", |
||||
|
JO: "JOD", |
||||
|
EG: "EGP", |
||||
|
YE: "YER", |
||||
|
MA: "MAD", |
||||
|
DZ: "DZD", |
||||
|
TN: "TND", |
||||
|
LY: "LYD", |
||||
|
SD: "SDG", |
||||
|
BD: "BDT", |
||||
|
LK: "LKR", |
||||
|
NP: "NPR", |
||||
|
PH: "PHP", |
||||
|
VN: "VND", |
||||
|
PL: "PLN", |
||||
|
CZ: "CZK", |
||||
|
HU: "HUF", |
||||
|
RO: "RON", |
||||
|
BG: "BGN", |
||||
|
HR: "EUR", |
||||
|
RS: "RSD", |
||||
|
BA: "BAM", |
||||
|
AL: "ALL", |
||||
|
MD: "MDL", |
||||
|
UA: "UAH", |
||||
|
BY: "BYN", |
||||
|
IS: "ISK", |
||||
|
CL: "CLP", |
||||
|
CO: "COP", |
||||
|
PE: "PEN", |
||||
|
VE: "VES", |
||||
|
EC: "USD", |
||||
|
PA: "USD", |
||||
|
CR: "CRC", |
||||
|
DO: "DOP", |
||||
|
GT: "GTQ", |
||||
|
NG: "NGN", |
||||
|
KES: "KES", |
||||
|
GH: "GHS", |
||||
|
ET: "ETB", |
||||
|
TZ: "TZS", |
||||
|
UG: "UGX", |
||||
|
}; |
||||
|
|
||||
|
export const COUNTRY_NAME_TO_CURRENCY: Record<string, string> = { |
||||
|
// English
|
||||
|
Iran: "TOMAN", |
||||
|
"United States": "USD", |
||||
|
"United States of America": "USD", |
||||
|
USA: "USD", |
||||
|
"United Kingdom": "GBP", |
||||
|
UK: "GBP", |
||||
|
Britain: "GBP", |
||||
|
Canada: "CAD", |
||||
|
Germany: "EUR", |
||||
|
France: "EUR", |
||||
|
Italy: "EUR", |
||||
|
Spain: "EUR", |
||||
|
Netherlands: "EUR", |
||||
|
Belgium: "EUR", |
||||
|
Austria: "EUR", |
||||
|
Portugal: "EUR", |
||||
|
Greece: "EUR", |
||||
|
Finland: "EUR", |
||||
|
Ireland: "EUR", |
||||
|
"United Arab Emirates": "AED", |
||||
|
UAE: "AED", |
||||
|
Turkey: "TRY", |
||||
|
Iraq: "IQD", |
||||
|
Afghanistan: "AFN", |
||||
|
Pakistan: "PKR", |
||||
|
"Saudi Arabia": "SAR", |
||||
|
Qatar: "QAR", |
||||
|
Kuwait: "KWD", |
||||
|
Oman: "OMR", |
||||
|
Bahrain: "BHD", |
||||
|
Sweden: "SEK", |
||||
|
Norway: "NOK", |
||||
|
Denmark: "DKK", |
||||
|
Switzerland: "CHF", |
||||
|
Australia: "AUD", |
||||
|
"New Zealand": "NZD", |
||||
|
Russia: "RUB", |
||||
|
China: "CNY", |
||||
|
Japan: "JPY", |
||||
|
"South Korea": "KRW", |
||||
|
India: "INR", |
||||
|
Malaysia: "MYR", |
||||
|
Indonesia: "IDR", |
||||
|
Singapore: "SGD", |
||||
|
Thailand: "THB", |
||||
|
Tajikistan: "TJS", |
||||
|
Azerbaijan: "AZN", |
||||
|
Uzbekistan: "UZS", |
||||
|
Turkmenistan: "TMT", |
||||
|
Kazakhstan: "KZT", |
||||
|
Kyrgyzstan: "KGS", |
||||
|
Georgia: "GEL", |
||||
|
Armenia: "AMD", |
||||
|
Lebanon: "LBP", |
||||
|
Syria: "SYP", |
||||
|
Jordan: "JOD", |
||||
|
Egypt: "EGP", |
||||
|
Yemen: "YER", |
||||
|
Morocco: "MAD", |
||||
|
Algeria: "DZD", |
||||
|
Tunisia: "TND", |
||||
|
Libya: "LYD", |
||||
|
Sudan: "SDG", |
||||
|
Brazil: "BRL", |
||||
|
Mexico: "MXN", |
||||
|
Argentina: "ARS", |
||||
|
Bulgaria: "BGN", |
||||
|
Poland: "PLN", |
||||
|
"Czech Republic": "CZK", |
||||
|
Hungary: "HUF", |
||||
|
Romania: "RON", |
||||
|
Serbia: "RSD", |
||||
|
Ukraine: "UAH", |
||||
|
Belarus: "BYN", |
||||
|
|
||||
|
// Persian
|
||||
|
ایران: "TOMAN", |
||||
|
"ایالات متحده": "USD", |
||||
|
"ایالات متحده آمریکا": "USD", |
||||
|
آمریکا: "USD", |
||||
|
بریتانیا: "GBP", |
||||
|
انگلیس: "GBP", |
||||
|
کانادا: "CAD", |
||||
|
آلمان: "EUR", |
||||
|
فرانسه: "EUR", |
||||
|
ایتالیا: "EUR", |
||||
|
اسپانیا: "EUR", |
||||
|
هلند: "EUR", |
||||
|
بلژیک: "EUR", |
||||
|
اتریش: "EUR", |
||||
|
پرتغال: "EUR", |
||||
|
یونان: "EUR", |
||||
|
فنلاند: "EUR", |
||||
|
ایرلند: "EUR", |
||||
|
"امارات متحده عربی": "AED", |
||||
|
امارات: "AED", |
||||
|
ترکیه: "TRY", |
||||
|
عراق: "IQD", |
||||
|
افغانستان: "AFN", |
||||
|
پاکستان: "PKR", |
||||
|
"عربستان سعودی": "SAR", |
||||
|
عربستان: "SAR", |
||||
|
قطر: "QAR", |
||||
|
کویت: "KWD", |
||||
|
عمان: "OMR", |
||||
|
بحرین: "BHD", |
||||
|
سوئد: "SEK", |
||||
|
نروژ: "NOK", |
||||
|
دانمارک: "DKK", |
||||
|
سوئیس: "CHF", |
||||
|
استرالیا: "AUD", |
||||
|
نیوزیلند: "NZD", |
||||
|
روسیه: "RUB", |
||||
|
چین: "CNY", |
||||
|
ژاپن: "JPY", |
||||
|
"کره جنوبی": "KRW", |
||||
|
هند: "INR", |
||||
|
مالزی: "MYR", |
||||
|
اندونزی: "IDR", |
||||
|
سنگاپور: "SGD", |
||||
|
تایلند: "THB", |
||||
|
تاجیکستان: "TJS", |
||||
|
آذربایجان: "AZN", |
||||
|
ازبکستان: "UZS", |
||||
|
ترکمنستان: "TMT", |
||||
|
قزاقستان: "KZT", |
||||
|
قرقیزستان: "KGS", |
||||
|
گرجستان: "GEL", |
||||
|
ارمنستان: "AMD", |
||||
|
لبنان: "LBP", |
||||
|
سوریه: "SYP", |
||||
|
اردن: "JOD", |
||||
|
مصر: "EGP", |
||||
|
یمن: "YER", |
||||
|
مراکش: "MAD", |
||||
|
الجزایر: "DZD", |
||||
|
تونس: "TND", |
||||
|
لیبی: "LYD", |
||||
|
سودان: "SDG", |
||||
|
برزیل: "BRL", |
||||
|
مکزیک: "MXN", |
||||
|
آرژانتین: "ARS", |
||||
|
بلغارستان: "BGN", |
||||
|
لهستان: "PLN", |
||||
|
چک: "CZK", |
||||
|
مجارستان: "HUF", |
||||
|
رومانی: "RON", |
||||
|
صربستان: "RSD", |
||||
|
اوکراین: "UAH", |
||||
|
بلاروس: "BYN", |
||||
|
}; |
||||
|
|
||||
|
export const CURRENCIES: CurrencyItem[] = [ |
||||
|
{ code: "TOMAN", nameEn: "Iranian Toman", nameFa: "تومان ایران", symbol: "تومان" }, |
||||
|
{ code: "USD", nameEn: "US Dollar", nameFa: "دلار آمریکا", symbol: "$" }, |
||||
|
{ code: "EUR", nameEn: "Euro", nameFa: "یورو", symbol: "€" }, |
||||
|
{ code: "AED", nameEn: "UAE Dirham", nameFa: "درهم امارات", symbol: "د.إ" }, |
||||
|
{ code: "TRY", nameEn: "Turkish Lira", nameFa: "لیر ترکیه", symbol: "₺" }, |
||||
|
{ code: "GBP", nameEn: "British Pound", nameFa: "پوند بریتانیا", symbol: "£" }, |
||||
|
{ code: "CAD", nameEn: "Canadian Dollar", nameFa: "دلار کانادا", symbol: "CA$" }, |
||||
|
{ code: "AUD", nameEn: "Australian Dollar", nameFa: "دلار استرالیا", symbol: "AU$" }, |
||||
|
{ code: "IQD", nameEn: "Iraqi Dinar", nameFa: "دینار عراق", symbol: "ع.د" }, |
||||
|
{ code: "SAR", nameEn: "Saudi Riyal", nameFa: "ریال سعودی", symbol: "﷼" }, |
||||
|
{ code: "QAR", nameEn: "Qatari Riyal", nameFa: "ریال قطر", symbol: "ر.ق" }, |
||||
|
{ code: "KWD", nameEn: "Kuwaiti Dinar", nameFa: "دینار کویت", symbol: "د.ك" }, |
||||
|
{ code: "OMR", nameEn: "Omani Rial", nameFa: "ریال عمان", symbol: "ر.ع." }, |
||||
|
{ code: "BHD", nameEn: "Bahraini Dinar", nameFa: "دینار بحرین", symbol: ".د.ب" }, |
||||
|
{ code: "AFN", nameEn: "Afghan Afghani", nameFa: "افغانی افغانستان", symbol: "؋" }, |
||||
|
{ code: "PKR", nameEn: "Pakistani Rupee", nameFa: "روپیه پاکستان", symbol: "₨" }, |
||||
|
{ code: "TJS", nameEn: "Tajikistani Somoni", nameFa: "سامانی تاجیکستان", symbol: "смн" }, |
||||
|
{ code: "AZN", nameEn: "Azerbaijani Manat", nameFa: "منات آذربایجان", symbol: "₼" }, |
||||
|
{ code: "RUB", nameEn: "Russian Ruble", nameFa: "روبل روسیه", symbol: "₽" }, |
||||
|
{ code: "CNY", nameEn: "Chinese Yuan", nameFa: "یوان چین", symbol: "¥" }, |
||||
|
{ code: "JPY", nameEn: "Japanese Yen", nameFa: "ین ژاپن", symbol: "¥" }, |
||||
|
{ code: "CHF", nameEn: "Swiss Franc", nameFa: "فرانک سوئیس", symbol: "CHF" }, |
||||
|
{ code: "SEK", nameEn: "Swedish Krona", nameFa: "کرون سوئد", symbol: "kr" }, |
||||
|
{ code: "NOK", nameEn: "Norwegian Krone", nameFa: "کرون نروژ", symbol: "kr" }, |
||||
|
{ code: "DKK", nameEn: "Danish Krone", nameFa: "کرون دانمارک", symbol: "kr" }, |
||||
|
{ code: "INR", nameEn: "Indian Rupee", nameFa: "روپیه هند", symbol: "₹" }, |
||||
|
{ code: "MYR", nameEn: "Malaysian Ringgit", nameFa: "رینگیت مالزی", symbol: "RM" }, |
||||
|
{ code: "SGD", nameEn: "Singapore Dollar", nameFa: "دلار سنگاپور", symbol: "S$" }, |
||||
|
{ code: "NZD", nameEn: "New Zealand Dollar", nameFa: "دلار نیوزیلند", symbol: "NZ$" }, |
||||
|
{ code: "BRL", nameEn: "Brazilian Real", nameFa: "رئال برزیل", symbol: "R$" }, |
||||
|
{ code: "MXN", nameEn: "Mexican Peso", nameFa: "پزو مکزیک", symbol: "Mex$" }, |
||||
|
{ code: "ARS", nameEn: "Argentine Peso", nameFa: "پزو آرژانتین", symbol: "ARS$" }, |
||||
|
{ code: "EGP", nameEn: "Egyptian Pound", nameFa: "پوند مصر", symbol: "E£" }, |
||||
|
{ code: "ZAR", nameEn: "South African Rand", nameFa: "راند آفریقای جنوبی", symbol: "R" }, |
||||
|
{ code: "IDR", nameEn: "Indonesian Rupiah", nameFa: "روپیه اندونزی", symbol: "Rp" }, |
||||
|
{ code: "THB", nameEn: "Thai Baht", nameFa: "بات تایلند", symbol: "฿" }, |
||||
|
{ code: "KRW", nameEn: "South Korean Won", nameFa: "وون کره جنوبی", symbol: "₩" }, |
||||
|
{ code: "UZS", nameEn: "Uzbekistani Som", nameFa: "سوم ازبکستان", symbol: "so'm" }, |
||||
|
{ code: "TMT", nameEn: "Turkmenistani Manat", nameFa: "منات ترکمنستان", symbol: "TMT" }, |
||||
|
{ code: "KZT", nameEn: "Kazakhstani Tenge", nameFa: "تنگه قزاقستان", symbol: "₸" }, |
||||
|
{ code: "KGS", nameEn: "Kyrgystani Som", nameFa: "سوم قرقیزستان", symbol: "сом" }, |
||||
|
{ code: "GEL", nameEn: "Georgian Lari", nameFa: "لاری گرجستان", symbol: "₾" }, |
||||
|
{ code: "AMD", nameEn: "Armenian Dram", nameFa: "درام ارمنستان", symbol: "֏" }, |
||||
|
{ code: "LBP", nameEn: "Lebanese Pound", nameFa: "لیره لبنان", symbol: "ل.ل" }, |
||||
|
{ code: "SYP", nameEn: "Syrian Pound", nameFa: "لیره سوریه", symbol: "ل.س" }, |
||||
|
{ code: "JOD", nameEn: "Jordanian Dinar", nameFa: "دینار اردن", symbol: "د.ا" }, |
||||
|
{ code: "YER", nameEn: "Yemeni Rial", nameFa: "ریال یمن", symbol: "﷼" }, |
||||
|
{ code: "MAD", nameEn: "Moroccan Dirham", nameFa: "درهم مراکش", symbol: "د.م." }, |
||||
|
{ code: "DZD", nameEn: "Algerian Dinar", nameFa: "دینار الجزایر", symbol: "د.ج" }, |
||||
|
{ code: "TND", nameEn: "Tunisian Dinar", nameFa: "دینار تونس", symbol: "د.ت" }, |
||||
|
{ code: "LYD", nameEn: "Libyan Dinar", nameFa: "دینار لیبی", symbol: "ل.د" }, |
||||
|
{ code: "SDG", nameEn: "Sudanese Pound", nameFa: "پوند سودان", symbol: "ج.س." }, |
||||
|
{ code: "BDT", nameEn: "Bangladeshi Taka", nameFa: "تاکای بنگلادش", symbol: "৳" }, |
||||
|
{ code: "BGN", nameEn: "Bulgarian Lev", nameFa: "لو بلغارستان", symbol: "лв" }, |
||||
|
{ code: "PLN", nameEn: "Polish Zloty", nameFa: "زلوتی لهستان", symbol: "zł" }, |
||||
|
{ code: "CZK", nameEn: "Czech Koruna", nameFa: "کرون چک", symbol: "Kč" }, |
||||
|
{ code: "HUF", nameEn: "Hungarian Forint", nameFa: "فورینت مجارستان", symbol: "Ft" }, |
||||
|
{ code: "RON", nameEn: "Romanian Leu", nameFa: "لئو رومانی", symbol: "lei" }, |
||||
|
{ code: "RSD", nameEn: "Serbian Dinar", nameFa: "دینار صربستان", symbol: "дин." }, |
||||
|
{ code: "BAM", nameEn: "Bosnia Mark", nameFa: "مارک بوسنی", symbol: "KM" }, |
||||
|
{ code: "ALL", nameEn: "Albanian Lek", nameFa: "لک آلبانی", symbol: "Lek" }, |
||||
|
{ code: "UAH", nameEn: "Ukrainian Hryvnia", nameFa: "گریونا اوکراین", symbol: "₴" }, |
||||
|
{ code: "BYN", nameEn: "Belarusian Ruble", nameFa: "روبل بلاروس", symbol: "Br" }, |
||||
|
{ code: "ISK", nameEn: "Icelandic Króna", nameFa: "کرون ایسلند", symbol: "kr" }, |
||||
|
{ code: "CLP", nameEn: "Chilean Peso", nameFa: "پزو شیلی", symbol: "CLP$" }, |
||||
|
{ code: "COP", nameEn: "Colombian Peso", nameFa: "پزو کلمبیا", symbol: "COL$" }, |
||||
|
{ code: "PEN", nameEn: "Peruvian Sol", nameFa: "سول پرو", symbol: "S/." }, |
||||
|
{ code: "CRC", nameEn: "Costa Rican Colón", nameFa: "کولون کاستاریکا", symbol: "₡" }, |
||||
|
{ code: "DOP", nameEn: "Dominican Peso", nameFa: "پزو دومینیکن", symbol: "RD$" }, |
||||
|
{ code: "GTQ", nameEn: "Guatemalan Quetzal", nameFa: "کوتزال گواتمالا", symbol: "Q" }, |
||||
|
{ code: "NGN", nameEn: "Nigerian Naira", nameFa: "نایرا نیجریه", symbol: "₦" }, |
||||
|
{ code: "KES", nameEn: "Kenyan Shilling", nameFa: "شیلینگ کنیا", symbol: "KSh" }, |
||||
|
{ code: "GHS", nameEn: "Ghanaian Cedi", nameFa: "سدی غنا", symbol: "GH₵" }, |
||||
|
{ code: "ETB", nameEn: "Ethiopian Birr", nameFa: "بیر اتیوپی", symbol: "Br" }, |
||||
|
{ code: "IRR", nameEn: "Iranian Rial", nameFa: "ریال ایران", symbol: "﷼" }, |
||||
|
]; |
||||
|
|
||||
|
export function resolveDefaultCurrency({ |
||||
|
countryCode, |
||||
|
countryName, |
||||
|
fallbackLocale, |
||||
|
}: { |
||||
|
countryCode?: string | null; |
||||
|
countryName?: string | null; |
||||
|
fallbackLocale?: string | null; |
||||
|
}): string { |
||||
|
if (countryCode && typeof countryCode === "string") { |
||||
|
const cleanCode = countryCode.trim().toUpperCase(); |
||||
|
if (COUNTRY_CODE_TO_CURRENCY[cleanCode]) { |
||||
|
return COUNTRY_CODE_TO_CURRENCY[cleanCode]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (countryName && typeof countryName === "string") { |
||||
|
const cleanName = countryName.trim(); |
||||
|
if (COUNTRY_NAME_TO_CURRENCY[cleanName]) { |
||||
|
return COUNTRY_NAME_TO_CURRENCY[cleanName]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (fallbackLocale === "fa" || fallbackLocale === "fa-ir") { |
||||
|
return "TOMAN"; |
||||
|
} |
||||
|
|
||||
|
return "USD"; |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue