mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
Контраст-аудит (WCAG-обход всех текстовых узлов, 4 палитры × юзер+админ страницы) показал системные провалы читаемости: вторичный текст 1.5-2.5 на светлой/кастомных темах, статусные цвета 1.3-1.6, белый текст на светлых акцентах 1.9. Корневые фиксы: - useThemeColors: контраст-кламп производных серых токенов — dark-400/ champagne-600 (вторичный текст) держат >=5.0 к поверхности, dark-500/ champagne-500 (подсказки) >=3.8; палитры с достаточным контрастом рендерятся байт-в-байт как раньше - --color-on-accent/-success/-warning/-error: чёрный или белый текст по фактической яркости цвета оператора; text-white на accent-заливках заменён на text-on-accent (135 мест + variant примитива Button + .btn-primary) - .light: статусные текстовые оттенки *-300/400 ремапятся на тёмные *-700 (жёлтая «Админка» 1.26 -> 3.8+, зелёные суммы 1.4 -> 3.4+, бейджи состояний) Замер после: операторская тёмная палитра — 0 нарушений (всё >=4.5), худшие точки остальных подняты с 1.26-1.6 до 3.2+. Консистентность: - заголовки админ-страниц приведены к text-xl font-bold text-dark-100 (были 18/20/24/30px и 600/700 вперемешку) - «Создать FAQ»: warning-500/80+белый -> warning-500+text-on-warning - formatTraffic: единицы из i18n (убран микс «GB»/«ГБ» на одном экране) - categoryCanvas -> categoryCANVAS в локалях (битый ключ на настройках) - «Управление сквадами» -> «Управление локациями» (юзерский экран) - мин-макс сумм на выборе метода: dark-600 -> dark-400 - неактивные пункты нижнего нава: dark-500 -> dark-400
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
import { Link } from 'react-router';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { motion } from 'framer-motion';
|
|
import { GiftIcon } from '@/components/icons';
|
|
import type { PendingGift } from '../../api/gift';
|
|
|
|
interface PendingGiftCardProps {
|
|
gifts: PendingGift[];
|
|
className?: string;
|
|
}
|
|
|
|
export default function PendingGiftCard({ gifts, className }: PendingGiftCardProps) {
|
|
const { t } = useTranslation();
|
|
|
|
if (gifts.length === 0) return null;
|
|
|
|
return (
|
|
<div className={className ?? 'space-y-3'}>
|
|
{gifts.map((gift) => (
|
|
<motion.div
|
|
key={gift.token}
|
|
initial={{ opacity: 0, y: -10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="relative overflow-hidden rounded-2xl border border-accent-500/30 bg-gradient-to-r from-accent-500/10 via-purple-500/10 to-accent-500/10 p-5"
|
|
>
|
|
{/* Subtle glow effect */}
|
|
<div className="absolute -right-8 -top-8 h-24 w-24 rounded-full bg-accent-500/10 blur-2xl" />
|
|
|
|
<div className="relative flex items-start gap-4">
|
|
{/* Gift icon */}
|
|
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-accent-500/20">
|
|
<GiftIcon className="h-6 w-6 text-accent-400" />
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="min-w-0 flex-1">
|
|
<h3 className="text-sm font-semibold text-dark-50">{t('gift.pending.title')}</h3>
|
|
<p className="mt-0.5 text-xs text-dark-300">
|
|
{gift.tariff_name && (
|
|
<span>
|
|
{gift.tariff_name} — {gift.period_days} {t('gift.days')}
|
|
</span>
|
|
)}
|
|
{gift.sender_display && (
|
|
<span className="ml-1 text-dark-400">
|
|
{t('gift.pending.from', { sender: gift.sender_display })}
|
|
</span>
|
|
)}
|
|
</p>
|
|
{gift.gift_message && (
|
|
<p className="mt-1.5 line-clamp-2 text-xs italic text-dark-400">
|
|
“{gift.gift_message}”
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Activate button */}
|
|
<Link
|
|
to={`/gift?tab=activate&code=${gift.token}`}
|
|
className="shrink-0 rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
|
>
|
|
{t('gift.pending.activate')}
|
|
</Link>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|