diff --git a/src/pages/AdminCouponDetail.tsx b/src/pages/AdminCouponDetail.tsx index ff14e71..06ebaa4 100644 --- a/src/pages/AdminCouponDetail.tsx +++ b/src/pages/AdminCouponDetail.tsx @@ -6,7 +6,7 @@ import i18n from '../i18n'; import { couponsApi } from '../api/coupons'; import { usePlatform } from '../platform/hooks/usePlatform'; import { copyToClipboard } from '../utils/clipboard'; -import { formatPrice } from '../utils/format'; +import { formatPrice, formatShortDate } from '../utils/format'; import { getApiErrorMessage } from '../utils/api-error'; import { useToast } from '../components/Toast'; import { PermissionGate } from '../components/auth/PermissionGate'; @@ -21,17 +21,6 @@ import { } from '@/components/icons'; import { StatCard } from '../components/stats'; -const formatDate = (date: string | null): string => { - if (!date) return '-'; - const localeMap: Record = { ru: 'ru-RU', en: 'en-US', zh: 'zh-CN', fa: 'fa-IR' }; - const locale = localeMap[i18n.language] || 'ru-RU'; - return new Date(date).toLocaleDateString(locale, { - day: '2-digit', - month: '2-digit', - year: 'numeric', - }); -}; - export default function AdminCouponDetail() { const { t } = useTranslation(); const navigate = useNavigate(); @@ -184,13 +173,13 @@ export default function AdminCouponDetail() { {t('admin.coupons.detail.validUntil')} {batch.valid_until - ? formatDate(batch.valid_until) + ? formatShortDate(batch.valid_until) : t('admin.coupons.detail.perpetual')}
{t('admin.coupons.detail.createdAt')} - {formatDate(batch.created_at)} + {formatShortDate(batch.created_at)}
diff --git a/src/pages/AdminCoupons.tsx b/src/pages/AdminCoupons.tsx index a25eb77..221a45a 100644 --- a/src/pages/AdminCoupons.tsx +++ b/src/pages/AdminCoupons.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import i18n from '../i18n'; import { couponsApi, CouponBatch } from '../api/coupons'; import { usePlatform } from '../platform/hooks/usePlatform'; -import { formatPrice } from '../utils/format'; +import { formatPrice, formatShortDate } from '../utils/format'; import { BackIcon, PlusIcon, @@ -18,17 +18,6 @@ import { StatCard } from '../components/stats'; const PAGE_SIZE = 50; -const formatDate = (date: string | null): string => { - if (!date) return '-'; - const localeMap: Record = { ru: 'ru-RU', en: 'en-US', zh: 'zh-CN', fa: 'fa-IR' }; - const locale = localeMap[i18n.language] || 'ru-RU'; - return new Date(date).toLocaleDateString(locale, { - day: '2-digit', - month: '2-digit', - year: 'numeric', - }); -}; - export default function AdminCoupons() { const { t } = useTranslation(); const navigate = useNavigate(); @@ -74,8 +63,10 @@ export default function AdminCoupons() { - {/* Stats Overview */} - {batches.length > 0 && ( + {/* Stats Overview — the Active/Redeemed/Revoked cards sum only the loaded + page, so show them only when the whole set fits one page; otherwise + they would contradict the global "Batches" total. */} + {batches.length > 0 && total <= PAGE_SIZE && (
- {t('admin.coupons.list.until')}: {formatDate(batch.valid_until)} + {t('admin.coupons.list.until')}: {formatShortDate(batch.valid_until)} )}
diff --git a/src/pages/CouponStatus.tsx b/src/pages/CouponStatus.tsx index d371360..767a3a7 100644 --- a/src/pages/CouponStatus.tsx +++ b/src/pages/CouponStatus.tsx @@ -5,22 +5,11 @@ import { useTranslation } from 'react-i18next'; import axios from 'axios'; import { couponsApi, CouponRedeemResponse } from '../api/coupons'; import { useAuthStore } from '../store/auth'; -import i18n from '../i18n'; +import { formatShortDate } from '../utils/format'; import { Spinner } from '@/components/ui/Spinner'; import { AnimatedCheckmark } from '@/components/ui/AnimatedCheckmark'; import { TicketIcon } from '@/components/icons'; -const formatDate = (date: string | null): string => { - if (!date) return '-'; - const localeMap: Record = { ru: 'ru-RU', en: 'en-US', zh: 'zh-CN', fa: 'fa-IR' }; - const locale = localeMap[i18n.language] || 'ru-RU'; - return new Date(date).toLocaleDateString(locale, { - day: '2-digit', - month: '2-digit', - year: 'numeric', - }); -}; - function Shell({ children }: { children: React.ReactNode }) { return (
@@ -60,7 +49,10 @@ export default function CouponStatus() { queryKey: ['coupon-status', token], queryFn: () => couponsApi.getCouponStatus(token!), enabled: !!token, - retry: 1, + // 404 is the expected answer for an invalid/consumed/expired token — the + // common case for a shared link — so retrying only wastes a request against + // the rate-limited public endpoint. + retry: false, }); const redeemMutation = useMutation({ @@ -110,7 +102,7 @@ export default function CouponStatus() { {redeemed.end_date && ( <>
- {t('coupon.success.until')} {formatDate(redeemed.end_date)} + {t('coupon.success.until')} {formatShortDate(redeemed.end_date)} )}

@@ -142,7 +134,9 @@ export default function CouponStatus() { {coupon.valid_until && (
{t('coupon.validUntil')} - {formatDate(coupon.valid_until)} + + {formatShortDate(coupon.valid_until)} +
)}
diff --git a/src/utils/format.ts b/src/utils/format.ts index 459f264..796a020 100644 --- a/src/utils/format.ts +++ b/src/utils/format.ts @@ -59,3 +59,21 @@ export function formatPrice(kopeks: number, lang?: string): string { return `${rounded} ${config.symbol}`; } } + +const SHORT_DATE_LOCALE_MAP: Record = { + ru: 'ru-RU', + en: 'en-US', + zh: 'zh-CN', + fa: 'fa-IR', +}; + +/** Date-only (dd.mm.yyyy) in the active UI locale; '-' for a null date. */ +export function formatShortDate(date: string | null): string { + if (!date) return '-'; + const locale = SHORT_DATE_LOCALE_MAP[i18next.language] || 'ru-RU'; + return new Date(date).toLocaleDateString(locale, { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }); +}