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
-
445src/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