barsa 0f6bae840f feat: add eligibility check flow with form, OTP, and success steps
- Implemented FormStep component for user input (name, email, address).
- Created OtpStep component for OTP verification.
- Developed SuccessStep component to display success messages based on account creation.
- Introduced eligibility-check.store for managing state throughout the eligibility check process.
- Added commitlint configuration for standardized commit messages.
- Configured knip for workspace management and project structure.
2026-01-15 11:28:25 +09:00

98 lines
2.6 KiB
TypeScript

/**
* Toolkit - Currency Formatting
*
* Our product currently operates in Japanese Yen only, but we keep a single
* helper so the portal and BFF share the same formatting behaviour.
*
* The function still accepts legacy signatures (`formatCurrency(amount, "JPY", "¥")`)
* so existing call sites remain compatible.
*/
export type SupportedCurrency = "JPY";
type LegacyOptions = {
/**
* Optional locale override. Defaults to "ja-JP".
*/
locale?: string | undefined;
/**
* Set to false if you ever want to hide the symbol. Defaults to true.
*/
showSymbol?: boolean | undefined;
/**
* Optional custom symbol. Defaults to "¥".
*/
currencySymbol?: string | undefined;
};
const DEFAULT_CURRENCY: SupportedCurrency = "JPY";
const DEFAULT_SYMBOL = "¥";
const DEFAULT_LOCALE = "ja-JP";
export const getCurrencyLocale = (_currency?: string): string => DEFAULT_LOCALE;
const normalizeOptions = (
currencyOrOptions?: string | LegacyOptions,
symbolOrOptions?: string | LegacyOptions
) => {
const result: {
currency: string;
symbol: string;
locale: string;
showSymbol: boolean;
} = {
currency: DEFAULT_CURRENCY,
symbol: DEFAULT_SYMBOL,
locale: DEFAULT_LOCALE,
showSymbol: true,
};
const applyOptions = (opts?: LegacyOptions) => {
if (!opts) return;
if (opts.locale) result.locale = opts.locale;
if (typeof opts.showSymbol === "boolean") result.showSymbol = opts.showSymbol;
if (opts.currencySymbol) result.symbol = opts.currencySymbol;
};
if (typeof currencyOrOptions === "string") {
result.currency = currencyOrOptions;
} else {
applyOptions(currencyOrOptions);
}
if (typeof symbolOrOptions === "string") {
result.symbol = symbolOrOptions;
} else {
applyOptions(symbolOrOptions);
}
// Even if a different currency code is provided, we treat it like JPY for now.
const fractionDigits = result.currency.toUpperCase() === "JPY" ? 0 : 2;
return { ...result, fractionDigits };
};
export function formatCurrency(
amount: number,
currencyOrOptions?: string | LegacyOptions,
symbolOrOptions?: string | LegacyOptions
): string {
const { locale, symbol, showSymbol, fractionDigits } = normalizeOptions(
currencyOrOptions,
symbolOrOptions
);
const formatted = amount.toLocaleString(locale, {
minimumFractionDigits: fractionDigits,
maximumFractionDigits: fractionDigits,
});
return showSymbol ? `${symbol}${formatted}` : formatted;
}
export function parseCurrency(value: string): number | null {
const cleaned = value.replace(/[¥$€,\s]/g, "");
const parsed = Number.parseFloat(cleaned);
return Number.isFinite(parsed) ? parsed : null;
}