mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix(theme): readable text on any operator palette + admin consistency pass
Контраст-аудит (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
This commit is contained in:
@@ -74,7 +74,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-3 font-medium text-white transition-colors hover:bg-accent-600"
|
className="rounded-xl bg-accent-500 px-6 py-3 font-medium text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
Reload page
|
Reload page
|
||||||
</button>
|
</button>
|
||||||
@@ -110,7 +110,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-3 font-medium text-white transition-colors hover:bg-accent-600"
|
className="rounded-xl bg-accent-500 px-6 py-3 font-medium text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{isChunk ? 'Reload' : 'Try again'}
|
{isChunk ? 'Reload' : 'Try again'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ export default function SuccessNotificationModal() {
|
|||||||
{isSubscription && (
|
{isSubscription && (
|
||||||
<button
|
<button
|
||||||
onClick={handleGoToSubscription}
|
onClick={handleGoToSubscription}
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-white shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
|
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-on-accent shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
|
||||||
>
|
>
|
||||||
<RocketIcon className="h-8 w-8" />
|
<RocketIcon className="h-8 w-8" />
|
||||||
<span>{t('successNotification.goToSubscription', 'Go to Subscription')}</span>
|
<span>{t('successNotification.goToSubscription', 'Go to Subscription')}</span>
|
||||||
@@ -301,7 +301,7 @@ export default function SuccessNotificationModal() {
|
|||||||
{isDevicesPurchased && (
|
{isDevicesPurchased && (
|
||||||
<button
|
<button
|
||||||
onClick={handleGoToSubscription}
|
onClick={handleGoToSubscription}
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-white shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
|
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-on-accent shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
|
||||||
>
|
>
|
||||||
<DevicesIcon className="h-8 w-8" />
|
<DevicesIcon className="h-8 w-8" />
|
||||||
<span>{t('successNotification.goToSubscription', 'Go to Subscription')}</span>
|
<span>{t('successNotification.goToSubscription', 'Go to Subscription')}</span>
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ export function AnalyticsTab() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSaveYandex}
|
onClick={handleSaveYandex}
|
||||||
disabled={updateMutation.isPending}
|
disabled={updateMutation.isPending}
|
||||||
className="rounded-xl bg-accent-500 px-4 py-2.5 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="rounded-xl bg-accent-500 px-4 py-2.5 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</button>
|
</button>
|
||||||
@@ -253,7 +253,7 @@ export function AnalyticsTab() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSaveGoogleId}
|
onClick={handleSaveGoogleId}
|
||||||
disabled={updateMutation.isPending}
|
disabled={updateMutation.isPending}
|
||||||
className="rounded-xl bg-accent-500 px-4 py-2.5 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="rounded-xl bg-accent-500 px-4 py-2.5 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</button>
|
</button>
|
||||||
@@ -307,7 +307,7 @@ export function AnalyticsTab() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSaveGoogleLabel}
|
onClick={handleSaveGoogleLabel}
|
||||||
disabled={updateMutation.isPending}
|
disabled={updateMutation.isPending}
|
||||||
className="rounded-xl bg-accent-500 px-4 py-2.5 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="rounded-xl bg-accent-500 px-4 py-2.5 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export function BackgroundEditor() {
|
|||||||
'w-full rounded-xl py-3 text-sm font-medium transition-colors',
|
'w-full rounded-xl py-3 text-sm font-medium transition-colors',
|
||||||
saveStatus === 'saved'
|
saveStatus === 'saved'
|
||||||
? 'bg-success-500/20 text-success-400'
|
? 'bg-success-500/20 text-success-400'
|
||||||
: 'bg-accent-500 text-white hover:bg-accent-600 disabled:opacity-50',
|
: 'bg-accent-500 text-on-accent hover:bg-accent-600 disabled:opacity-50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{saveStatus === 'saving'
|
{saveStatus === 'saving'
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export function BrandingTab({ accentColor = '#3b82f6' }: BrandingTabProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => updateBrandingMutation.mutate(newName)}
|
onClick={() => updateBrandingMutation.mutate(newName)}
|
||||||
disabled={updateBrandingMutation.isPending}
|
disabled={updateBrandingMutation.isPending}
|
||||||
className="rounded-xl bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="rounded-xl bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export function ButtonsTab() {
|
|||||||
? 'bg-success-500 text-white'
|
? 'bg-success-500 text-white'
|
||||||
: cfg.style === 'danger'
|
: cfg.style === 'danger'
|
||||||
? 'bg-error-500 text-white'
|
? 'bg-error-500 text-white'
|
||||||
: 'bg-accent-500 text-white'
|
: 'bg-accent-500 text-on-accent'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t(`admin.buttons.styles.${cfg.style}`)}
|
{t(`admin.buttons.styles.${cfg.style}`)}
|
||||||
@@ -321,7 +321,7 @@ export function ButtonsTab() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={updateMutation.isPending}
|
disabled={updateMutation.isPending}
|
||||||
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{updateMutation.isPending ? t('common.saving') : t('common.save')}
|
{updateMutation.isPending ? t('common.saving') : t('common.save')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ export function ColoredItemCombobox({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
disabled={isCreating}
|
disabled={isCreating}
|
||||||
className="flex min-h-[44px] w-full items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
className="flex min-h-[44px] w-full items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isCreating ? (
|
{isCreating ? (
|
||||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" />
|
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" />
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ function MaxPerRowSelector({ value, onChange }: MaxPerRowSelectorProps) {
|
|||||||
onClick={() => onChange(n)}
|
onClick={() => onChange(n)}
|
||||||
className={`flex h-7 w-7 items-center justify-center rounded-lg text-xs font-semibold transition-all ${
|
className={`flex h-7 w-7 items-center justify-center rounded-lg text-xs font-semibold transition-all ${
|
||||||
value === n
|
value === n
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-700/50 text-dark-400 hover:bg-dark-600 hover:text-dark-300'
|
: 'bg-dark-700/50 text-dark-400 hover:bg-dark-600 hover:text-dark-300'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -788,7 +788,7 @@ export function MenuEditorTab() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={updateMutation.isPending}
|
disabled={updateMutation.isPending}
|
||||||
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{updateMutation.isPending ? t('common.saving') : t('common.save')}
|
{updateMutation.isPending ? t('common.saving') : t('common.save')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps)
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="flex items-center gap-1.5 rounded-lg bg-accent-500 px-3 py-1.5 text-sm text-white transition-colors hover:bg-accent-600"
|
className="flex items-center gap-1.5 rounded-lg bg-accent-500 px-3 py-1.5 text-sm text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
{t('admin.settings.saveButton')}
|
{t('admin.settings.saveButton')}
|
||||||
@@ -159,7 +159,7 @@ export function SettingInput({ setting, onUpdate, disabled }: SettingInputProps)
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="rounded-lg bg-accent-500 p-2 text-white transition-colors hover:bg-accent-600"
|
className="rounded-lg bg-accent-500 p-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
title={t('admin.settings.saveHint')}
|
title={t('admin.settings.saveHint')}
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export function SortableSelectedMethodCard({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex h-3.5 w-3.5 items-center justify-center rounded',
|
'flex h-3.5 w-3.5 items-center justify-center rounded',
|
||||||
enabled
|
enabled
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'border border-dark-600 bg-dark-700',
|
: 'border border-dark-600 bg-dark-700',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ export function ThemeTab() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => updateColorsMutation.mutate(draftColors)}
|
onClick={() => updateColorsMutation.mutate(draftColors)}
|
||||||
disabled={updateColorsMutation.isPending}
|
disabled={updateColorsMutation.isPending}
|
||||||
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{updateColorsMutation.isPending
|
{updateColorsMutation.isPending
|
||||||
? t('common.saving', t('common.save'))
|
? t('common.saving', t('common.save'))
|
||||||
|
|||||||
@@ -621,7 +621,7 @@ export function ActionModal({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="w-full rounded-xl bg-accent-500 px-4 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-accent-600"
|
className="w-full rounded-xl bg-accent-500 px-4 py-2.5 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('common.close')}
|
{t('common.close')}
|
||||||
</button>
|
</button>
|
||||||
@@ -647,7 +647,7 @@ export function ActionModal({
|
|||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isConfirmDisabled}
|
disabled={isConfirmDisabled}
|
||||||
className="flex min-h-[44px] flex-1 items-center justify-center gap-2 rounded-xl bg-accent-500 px-4 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="flex min-h-[44px] flex-1 items-center justify-center gap-2 rounded-xl bg-accent-500 px-4 py-2.5 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{t('admin.bulkActions.confirm')}
|
{t('admin.bulkActions.confirm')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export function FloatingActionBar({
|
|||||||
<div className="relative ml-auto">
|
<div className="relative ml-auto">
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen(!open)}
|
onClick={() => setOpen(!open)}
|
||||||
className="flex items-center gap-2 rounded-xl bg-accent-500 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-accent-600"
|
className="flex items-center gap-2 rounded-xl bg-accent-500 px-4 py-2 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('common.actions')}
|
{t('common.actions')}
|
||||||
<ChevronDownIcon />
|
<ChevronDownIcon />
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export function CountryFilter({
|
|||||||
>
|
>
|
||||||
<GlobeIcon className="h-4 w-4" />
|
<GlobeIcon className="h-4 w-4" />
|
||||||
{activeCount > 0 && (
|
{activeCount > 0 && (
|
||||||
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
|
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-on-accent">
|
||||||
{activeCount}
|
{activeCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export function NodeFilter({
|
|||||||
<ServerIcon className="h-4 w-4" />
|
<ServerIcon className="h-4 w-4" />
|
||||||
{t('admin.trafficUsage.nodes')}
|
{t('admin.trafficUsage.nodes')}
|
||||||
{activeCount > 0 && (
|
{activeCount > 0 && (
|
||||||
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
|
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-on-accent">
|
||||||
{activeCount}
|
{activeCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export function PeriodSelector({
|
|||||||
onClick={() => onChange(p)}
|
onClick={() => onChange(p)}
|
||||||
className={`rounded-lg px-2.5 py-1 text-xs font-medium transition-colors ${
|
className={`rounded-lg px-2.5 py-1 text-xs font-medium transition-colors ${
|
||||||
value === p
|
value === p
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-800 text-dark-400 hover:bg-dark-700 hover:text-dark-200'
|
: 'bg-dark-800 text-dark-400 hover:bg-dark-700 hover:text-dark-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function StatusFilter({
|
|||||||
<StatusIcon className="h-4 w-4" />
|
<StatusIcon className="h-4 w-4" />
|
||||||
{t('admin.trafficUsage.status')}
|
{t('admin.trafficUsage.status')}
|
||||||
{activeCount > 0 && (
|
{activeCount > 0 && (
|
||||||
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
|
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-on-accent">
|
||||||
{activeCount}
|
{activeCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function TariffFilter({
|
|||||||
<FilterIcon className="h-4 w-4" />
|
<FilterIcon className="h-4 w-4" />
|
||||||
{t('admin.trafficUsage.tariff')}
|
{t('admin.trafficUsage.tariff')}
|
||||||
{activeCount > 0 && (
|
{activeCount > 0 && (
|
||||||
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
|
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-on-accent">
|
||||||
{activeCount}
|
{activeCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ export function InfoTab(props: InfoTabProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={onUpdateReferralCommission}
|
onClick={onUpdateReferralCommission}
|
||||||
disabled={actionLoading}
|
disabled={actionLoading}
|
||||||
className="w-full rounded-lg bg-accent-500 px-2 py-1 text-xs text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="w-full rounded-lg bg-accent-500 px-2 py-1 text-xs text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{actionLoading ? t('common.loading') : t('common.save')}
|
{actionLoading ? t('common.loading') : t('common.save')}
|
||||||
</button>
|
</button>
|
||||||
@@ -475,7 +475,7 @@ export function InfoTab(props: InfoTabProps) {
|
|||||||
disabled={actionLoading}
|
disabled={actionLoading}
|
||||||
className={`rounded-lg px-3 py-2 text-sm font-medium transition-all disabled:opacity-50 ${
|
className={`rounded-lg px-3 py-2 text-sm font-medium transition-all disabled:opacity-50 ${
|
||||||
confirmingAction === 'resetTrial'
|
confirmingAction === 'resetTrial'
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-accent-500/15 text-accent-400 hover:bg-accent-500/25'
|
: 'bg-accent-500/15 text-accent-400 hover:bg-accent-500/25'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -464,7 +464,7 @@ export function SubscriptionTab(props: SubscriptionTabProps) {
|
|||||||
<button
|
<button
|
||||||
onClick={() => selectedTrafficGb && onAddTraffic(Number(selectedTrafficGb))}
|
onClick={() => selectedTrafficGb && onAddTraffic(Number(selectedTrafficGb))}
|
||||||
disabled={actionLoading || !selectedTrafficGb}
|
disabled={actionLoading || !selectedTrafficGb}
|
||||||
className="shrink-0 rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="shrink-0 rounded-lg bg-accent-500 px-4 py-2 text-sm text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{t('admin.users.detail.subscription.addButton')}
|
{t('admin.users.detail.subscription.addButton')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ function ChatView({
|
|||||||
onClick={onReply}
|
onClick={onReply}
|
||||||
disabled={!replyText.trim() || replySending}
|
disabled={!replyText.trim() || replySending}
|
||||||
aria-label={t('admin.tickets.sendReply', 'Send reply')}
|
aria-label={t('admin.tickets.sendReply', 'Send reply')}
|
||||||
className="min-h-[44px] min-w-[44px] shrink-0 self-end rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:opacity-50 sm:min-h-0 sm:min-w-0"
|
className="min-h-[44px] min-w-[44px] shrink-0 self-end rounded-lg bg-accent-500 px-4 py-2 text-sm text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50 sm:min-h-0 sm:min-w-0"
|
||||||
>
|
>
|
||||||
{replySending ? (
|
{replySending ? (
|
||||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function PendingGiftCard({ gifts, className }: PendingGiftCardPro
|
|||||||
{/* Activate button */}
|
{/* Activate button */}
|
||||||
<Link
|
<Link
|
||||||
to={`/gift?tab=activate&code=${gift.token}`}
|
to={`/gift?tab=activate&code=${gift.token}`}
|
||||||
className="shrink-0 rounded-xl bg-accent-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
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')}
|
{t('gift.pending.activate')}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export function MobileBottomNav({
|
|||||||
onClick={handleNavClick}
|
onClick={handleNavClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex min-w-[56px] flex-1 shrink-0 flex-col items-center justify-center rounded-2xl px-3 py-2.5 transition-all duration-200',
|
'relative flex min-w-[56px] flex-1 shrink-0 flex-col items-center justify-center rounded-2xl px-3 py-2.5 transition-all duration-200',
|
||||||
isActive(item.path) ? 'text-accent-400' : 'text-dark-500 hover:text-dark-300',
|
isActive(item.path) ? 'text-accent-400' : 'text-dark-400 hover:text-dark-200',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isActive(item.path) && (
|
{isActive(item.path) && (
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const buttonVariants = cva(
|
|||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
primary: [
|
primary: [
|
||||||
'bg-accent-500 text-white',
|
'bg-accent-500 text-on-accent',
|
||||||
'hover:bg-accent-600',
|
'hover:bg-accent-600',
|
||||||
'active:bg-accent-700',
|
'active:bg-accent-700',
|
||||||
'shadow-linear-sm hover:shadow-linear',
|
'shadow-linear-sm hover:shadow-linear',
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export function TariffPickerGrid({
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/subscriptions')}
|
onClick={() => navigate('/subscriptions')}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600"
|
className="rounded-xl bg-accent-500 px-6 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('subscription.backToList', 'Мои подписки')}
|
{t('subscription.backToList', 'Мои подписки')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -54,6 +54,61 @@ function interpolateRgb(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Rgb = { r: number; g: number; b: number };
|
||||||
|
|
||||||
|
function mixRgb(rgb1: Rgb, rgb2: Rgb, factor: number): Rgb {
|
||||||
|
return {
|
||||||
|
r: Math.round(rgb1.r + (rgb2.r - rgb1.r) * factor),
|
||||||
|
g: Math.round(rgb1.g + (rgb2.g - rgb1.g) * factor),
|
||||||
|
b: Math.round(rgb1.b + (rgb2.b - rgb1.b) * factor),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// WCAG relative luminance
|
||||||
|
function relativeLuminance({ r, g, b }: Rgb): number {
|
||||||
|
const srgb = (v: number) => {
|
||||||
|
const c = v / 255;
|
||||||
|
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
||||||
|
};
|
||||||
|
return 0.2126 * srgb(r) + 0.7152 * srgb(g) + 0.0722 * srgb(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// WCAG contrast ratio between two colors
|
||||||
|
function contrastRatio(a: Rgb, b: Rgb): number {
|
||||||
|
const l1 = relativeLuminance(a);
|
||||||
|
const l2 = relativeLuminance(b);
|
||||||
|
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guarantee a minimum contrast for a TEXT color against its background.
|
||||||
|
*
|
||||||
|
* Operators pick arbitrary palette colors in the theme editor, and several
|
||||||
|
* tokens are additionally blended toward the surface (dark-500 = secondary
|
||||||
|
* text mixed 40% into the card color). Without a floor, hint/meta text
|
||||||
|
* regularly lands at a 1.5-2.5 contrast ratio and becomes unreadable.
|
||||||
|
* When the color already passes, it is returned untouched, so well-tuned
|
||||||
|
* palettes render byte-for-byte the same as before.
|
||||||
|
*/
|
||||||
|
function ensureReadable(fg: Rgb, towards: Rgb, bg: Rgb, minRatio: number): Rgb {
|
||||||
|
if (contrastRatio(fg, bg) >= minRatio) return fg;
|
||||||
|
for (let t = 0.1; t <= 1; t += 0.1) {
|
||||||
|
const mixed = mixRgb(fg, towards, t);
|
||||||
|
if (contrastRatio(mixed, bg) >= minRatio) return mixed;
|
||||||
|
}
|
||||||
|
return towards;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Black-or-white text for a given button/badge background: pick whichever
|
||||||
|
// side actually reads on top of it (operators may choose a light accent).
|
||||||
|
function onColorFor(bgTriplet: string): string {
|
||||||
|
const [r, g, b] = bgTriplet.split(',').map((x) => Number(x.trim()));
|
||||||
|
const bg = { r, g, b };
|
||||||
|
const white = { r: 255, g: 255, b: 255 };
|
||||||
|
const ink = { r: 15, g: 23, b: 42 };
|
||||||
|
return contrastRatio(white, bg) >= contrastRatio(ink, bg) ? '255, 255, 255' : '15, 23, 42';
|
||||||
|
}
|
||||||
|
|
||||||
// Apply theme colors as CSS variables (RGB format for Tailwind opacity support)
|
// Apply theme colors as CSS variables (RGB format for Tailwind opacity support)
|
||||||
export function applyThemeColors(colors: ThemeColors): void {
|
export function applyThemeColors(colors: ThemeColors): void {
|
||||||
const root = document.documentElement;
|
const root = document.documentElement;
|
||||||
@@ -70,6 +125,17 @@ export function applyThemeColors(colors: ThemeColors): void {
|
|||||||
const darkTextRgb = hexToRgb(colors.darkText);
|
const darkTextRgb = hexToRgb(colors.darkText);
|
||||||
const darkTextSecRgb = hexToRgb(colors.darkTextSecondary);
|
const darkTextSecRgb = hexToRgb(colors.darkTextSecondary);
|
||||||
|
|
||||||
|
// Contrast floors: secondary text must stay readable on the card surface
|
||||||
|
// regardless of the operator-chosen palette (AA 4.5 for dark-400, a softer
|
||||||
|
// 3.5 floor for the blended hint token dark-500).
|
||||||
|
const darkTextSecReadable = ensureReadable(darkTextSecRgb, darkTextRgb, darkSurfaceRgb, 5.0);
|
||||||
|
const darkHintReadable = ensureReadable(
|
||||||
|
mixRgb(darkTextSecRgb, darkSurfaceRgb, 0.4),
|
||||||
|
darkTextRgb,
|
||||||
|
darkSurfaceRgb,
|
||||||
|
3.8,
|
||||||
|
);
|
||||||
|
|
||||||
// Apply dark palette with actual user colors:
|
// Apply dark palette with actual user colors:
|
||||||
// Text colors (light shades): 50-100 = primary text, 200-300 = mixed, 400 = secondary text
|
// Text colors (light shades): 50-100 = primary text, 200-300 = mixed, 400 = secondary text
|
||||||
root.style.setProperty(
|
root.style.setProperty(
|
||||||
@@ -84,11 +150,14 @@ export function applyThemeColors(colors: ThemeColors): void {
|
|||||||
root.style.setProperty('--color-dark-300', interpolateRgb(darkTextRgb, darkTextSecRgb, 0.66));
|
root.style.setProperty('--color-dark-300', interpolateRgb(darkTextRgb, darkTextSecRgb, 0.66));
|
||||||
root.style.setProperty(
|
root.style.setProperty(
|
||||||
'--color-dark-400',
|
'--color-dark-400',
|
||||||
rgbToString(darkTextSecRgb.r, darkTextSecRgb.g, darkTextSecRgb.b),
|
rgbToString(darkTextSecReadable.r, darkTextSecReadable.g, darkTextSecReadable.b),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Transition colors (500-700): interpolate between secondary text and surface
|
// Transition colors (500-700): interpolate between secondary text and surface
|
||||||
root.style.setProperty('--color-dark-500', interpolateRgb(darkTextSecRgb, darkSurfaceRgb, 0.4));
|
root.style.setProperty(
|
||||||
|
'--color-dark-500',
|
||||||
|
rgbToString(darkHintReadable.r, darkHintReadable.g, darkHintReadable.b),
|
||||||
|
);
|
||||||
root.style.setProperty('--color-dark-600', interpolateRgb(darkTextSecRgb, darkSurfaceRgb, 0.6));
|
root.style.setProperty('--color-dark-600', interpolateRgb(darkTextSecRgb, darkSurfaceRgb, 0.6));
|
||||||
root.style.setProperty('--color-dark-700', interpolateRgb(darkTextSecRgb, darkSurfaceRgb, 0.8));
|
root.style.setProperty('--color-dark-700', interpolateRgb(darkTextSecRgb, darkSurfaceRgb, 0.8));
|
||||||
|
|
||||||
@@ -122,11 +191,23 @@ export function applyThemeColors(colors: ThemeColors): void {
|
|||||||
root.style.setProperty('--color-champagne-300', interpolateRgb(lightBgRgb, lightTextSecRgb, 0.2));
|
root.style.setProperty('--color-champagne-300', interpolateRgb(lightBgRgb, lightTextSecRgb, 0.2));
|
||||||
root.style.setProperty('--color-champagne-400', interpolateRgb(lightBgRgb, lightTextSecRgb, 0.4));
|
root.style.setProperty('--color-champagne-400', interpolateRgb(lightBgRgb, lightTextSecRgb, 0.4));
|
||||||
|
|
||||||
// Transition colors (500-600): between bg and text
|
// Transition colors (500-600): between bg and text.
|
||||||
root.style.setProperty('--color-champagne-500', interpolateRgb(lightBgRgb, lightTextSecRgb, 0.6));
|
// Same contrast floors as the dark palette: champagne-600 backs dark-400
|
||||||
|
// (secondary text) in the light theme, champagne-500 backs dark-500 (hints).
|
||||||
|
const lightHintReadable = ensureReadable(
|
||||||
|
mixRgb(lightBgRgb, lightTextSecRgb, 0.6),
|
||||||
|
lightTextRgb,
|
||||||
|
lightSurfaceRgb,
|
||||||
|
3.8,
|
||||||
|
);
|
||||||
|
const lightTextSecReadable = ensureReadable(lightTextSecRgb, lightTextRgb, lightSurfaceRgb, 5.0);
|
||||||
|
root.style.setProperty(
|
||||||
|
'--color-champagne-500',
|
||||||
|
rgbToString(lightHintReadable.r, lightHintReadable.g, lightHintReadable.b),
|
||||||
|
);
|
||||||
root.style.setProperty(
|
root.style.setProperty(
|
||||||
'--color-champagne-600',
|
'--color-champagne-600',
|
||||||
rgbToString(lightTextSecRgb.r, lightTextSecRgb.g, lightTextSecRgb.b),
|
rgbToString(lightTextSecReadable.r, lightTextSecReadable.g, lightTextSecReadable.b),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Text colors (700-950): secondary to primary text
|
// Text colors (700-950): secondary to primary text
|
||||||
@@ -154,6 +235,13 @@ export function applyThemeColors(colors: ThemeColors): void {
|
|||||||
root.style.setProperty(`--color-error-${shade}`, errorPalette[shade]);
|
root.style.setProperty(`--color-error-${shade}`, errorPalette[shade]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Readable text color on top of each status color (buttons, filled badges).
|
||||||
|
// Hardcoded white breaks the moment an operator picks a light accent.
|
||||||
|
root.style.setProperty('--color-on-accent', onColorFor(accentPalette[500]));
|
||||||
|
root.style.setProperty('--color-on-success', onColorFor(successPalette[500]));
|
||||||
|
root.style.setProperty('--color-on-warning', onColorFor(warningPalette[500]));
|
||||||
|
root.style.setProperty('--color-on-error', onColorFor(errorPalette[500]));
|
||||||
|
|
||||||
// Apply semantic colors (hex for direct use)
|
// Apply semantic colors (hex for direct use)
|
||||||
root.style.setProperty('--color-dark-bg', colors.darkBackground);
|
root.style.setProperty('--color-dark-bg', colors.darkBackground);
|
||||||
root.style.setProperty('--color-dark-surface', colors.darkSurface);
|
root.style.setProperty('--color-dark-surface', colors.darkSurface);
|
||||||
|
|||||||
@@ -25,10 +25,9 @@
|
|||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"all": "All",
|
"all": "All",
|
||||||
"units": {
|
"units": {
|
||||||
|
"tb": "TB",
|
||||||
"gb": "GB",
|
"gb": "GB",
|
||||||
"perGb": "/GB",
|
"mb": "MB"
|
||||||
"days": "days",
|
|
||||||
"mo": "mo"
|
|
||||||
},
|
},
|
||||||
"retry": "Retry",
|
"retry": "Retry",
|
||||||
"saving": "Saving...",
|
"saving": "Saving...",
|
||||||
@@ -1119,7 +1118,7 @@
|
|||||||
"globalOpacity": "Opacity",
|
"globalOpacity": "Opacity",
|
||||||
"globalBlur": "Blur",
|
"globalBlur": "Blur",
|
||||||
"categoryCSS": "CSS",
|
"categoryCSS": "CSS",
|
||||||
"categoryCanvas": "Canvas",
|
"categoryCANVAS": "Canvas",
|
||||||
"categorySVG": "SVG",
|
"categorySVG": "SVG",
|
||||||
"aurora": "Aurora",
|
"aurora": "Aurora",
|
||||||
"auroraDesc": "Northern lights with smooth gradients",
|
"auroraDesc": "Northern lights with smooth gradients",
|
||||||
|
|||||||
@@ -22,10 +22,9 @@
|
|||||||
"currency": "تومان",
|
"currency": "تومان",
|
||||||
"copy": "کپی",
|
"copy": "کپی",
|
||||||
"units": {
|
"units": {
|
||||||
|
"tb": "TB",
|
||||||
"gb": "GB",
|
"gb": "GB",
|
||||||
"perGb": "/GB",
|
"mb": "MB"
|
||||||
"days": "روز",
|
|
||||||
"mo": "ماه"
|
|
||||||
},
|
},
|
||||||
"add": "افزودن",
|
"add": "افزودن",
|
||||||
"refresh": "بازخوانی",
|
"refresh": "بازخوانی",
|
||||||
@@ -1001,7 +1000,7 @@
|
|||||||
"globalOpacity": "شفافیت",
|
"globalOpacity": "شفافیت",
|
||||||
"globalBlur": "تاری",
|
"globalBlur": "تاری",
|
||||||
"categoryCSS": "CSS",
|
"categoryCSS": "CSS",
|
||||||
"categoryCanvas": "Canvas",
|
"categoryCANVAS": "Canvas",
|
||||||
"categorySVG": "SVG",
|
"categorySVG": "SVG",
|
||||||
"aurora": "شفق قطبی",
|
"aurora": "شفق قطبی",
|
||||||
"auroraDesc": "شفق شمالی با گرادیانهای نرم",
|
"auroraDesc": "شفق شمالی با گرادیانهای نرم",
|
||||||
|
|||||||
@@ -28,10 +28,9 @@
|
|||||||
"saving": "Сохранение...",
|
"saving": "Сохранение...",
|
||||||
"understand": "Понятно",
|
"understand": "Понятно",
|
||||||
"units": {
|
"units": {
|
||||||
|
"tb": "ТБ",
|
||||||
"gb": "ГБ",
|
"gb": "ГБ",
|
||||||
"perGb": "/ГБ",
|
"mb": "МБ"
|
||||||
"days": "дн",
|
|
||||||
"mo": "мес"
|
|
||||||
},
|
},
|
||||||
"balance": "Баланс",
|
"balance": "Баланс",
|
||||||
"copied": "Скопировано",
|
"copied": "Скопировано",
|
||||||
@@ -613,8 +612,8 @@
|
|||||||
"unlimited": "Безлимит",
|
"unlimited": "Безлимит",
|
||||||
"buyTrafficGb": "Купить {{gb}} ГБ",
|
"buyTrafficGb": "Купить {{gb}} ГБ",
|
||||||
"buyUnlimited": "Купить Безлимит",
|
"buyUnlimited": "Купить Безлимит",
|
||||||
"manageServers": "Управление сквадами",
|
"manageServers": "Управление локациями",
|
||||||
"manageServersTitle": "Управление сквадами",
|
"manageServersTitle": "Управление локациями",
|
||||||
"connectedDevices": "Подключённые устройства",
|
"connectedDevices": "Подключённые устройства",
|
||||||
"currentDeviceLimit": "Текущий лимит устройств",
|
"currentDeviceLimit": "Текущий лимит устройств",
|
||||||
"decrementDevices": "Уменьшить кол-во устройств",
|
"decrementDevices": "Уменьшить кол-во устройств",
|
||||||
@@ -1149,7 +1148,7 @@
|
|||||||
"globalOpacity": "Прозрачность",
|
"globalOpacity": "Прозрачность",
|
||||||
"globalBlur": "Размытие",
|
"globalBlur": "Размытие",
|
||||||
"categoryCSS": "CSS",
|
"categoryCSS": "CSS",
|
||||||
"categoryCanvas": "Canvas",
|
"categoryCANVAS": "Canvas",
|
||||||
"categorySVG": "SVG",
|
"categorySVG": "SVG",
|
||||||
"aurora": "Aurora",
|
"aurora": "Aurora",
|
||||||
"auroraDesc": "Северное сияние с плавными переливами",
|
"auroraDesc": "Северное сияние с плавными переливами",
|
||||||
|
|||||||
@@ -28,10 +28,9 @@
|
|||||||
"understand": "明白了",
|
"understand": "明白了",
|
||||||
"collapse": "收起",
|
"collapse": "收起",
|
||||||
"units": {
|
"units": {
|
||||||
|
"tb": "TB",
|
||||||
"gb": "GB",
|
"gb": "GB",
|
||||||
"perGb": "/GB",
|
"mb": "MB"
|
||||||
"days": "天",
|
|
||||||
"mo": "月"
|
|
||||||
},
|
},
|
||||||
"balance": "余额",
|
"balance": "余额",
|
||||||
"copied": "已复制",
|
"copied": "已复制",
|
||||||
@@ -1001,7 +1000,7 @@
|
|||||||
"globalOpacity": "透明度",
|
"globalOpacity": "透明度",
|
||||||
"globalBlur": "模糊",
|
"globalBlur": "模糊",
|
||||||
"categoryCSS": "CSS",
|
"categoryCSS": "CSS",
|
||||||
"categoryCanvas": "Canvas",
|
"categoryCANVAS": "Canvas",
|
||||||
"categorySVG": "SVG",
|
"categorySVG": "SVG",
|
||||||
"aurora": "极光",
|
"aurora": "极光",
|
||||||
"auroraDesc": "柔和渐变的北极光",
|
"auroraDesc": "柔和渐变的北极光",
|
||||||
|
|||||||
@@ -545,7 +545,7 @@ export default function AdminAuditLog() {
|
|||||||
handleExport();
|
handleExport();
|
||||||
}}
|
}}
|
||||||
disabled={exporting}
|
disabled={exporting}
|
||||||
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
<span className="hidden sm:inline">
|
<span className="hidden sm:inline">
|
||||||
@@ -614,7 +614,7 @@ export default function AdminAuditLog() {
|
|||||||
<span
|
<span
|
||||||
className={`flex h-4 w-4 items-center justify-center rounded border text-xs ${
|
className={`flex h-4 w-4 items-center justify-center rounded border text-xs ${
|
||||||
isSelected
|
isSelected
|
||||||
? 'border-accent-500 bg-accent-500 text-white'
|
? 'border-accent-500 bg-accent-500 text-on-accent'
|
||||||
: 'border-dark-500 bg-dark-800'
|
: 'border-dark-500 bg-dark-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -734,7 +734,7 @@ export default function AdminAuditLog() {
|
|||||||
<div className="mt-4 flex items-center gap-3">
|
<div className="mt-4 flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={handleApplyFilters}
|
onClick={handleApplyFilters}
|
||||||
className="rounded-lg bg-accent-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-600"
|
className="rounded-lg bg-accent-500 px-4 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('admin.auditLog.filters.apply')}
|
{t('admin.auditLog.filters.apply')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -702,7 +702,7 @@ export default function AdminBroadcastCreate() {
|
|||||||
onClick={() => toggleButton(button.key)}
|
onClick={() => toggleButton(button.key)}
|
||||||
className={`rounded-lg px-3 py-2 text-sm transition-colors ${
|
className={`rounded-lg px-3 py-2 text-sm transition-colors ${
|
||||||
selectedButtons.includes(button.key)
|
selectedButtons.includes(button.key)
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'border border-dark-700 bg-dark-800 text-dark-300 hover:bg-dark-700'
|
: 'border border-dark-700 bg-dark-800 text-dark-300 hover:bg-dark-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -770,7 +770,7 @@ export default function AdminBroadcastCreate() {
|
|||||||
onClick={() => setNewButtonActionType('callback')}
|
onClick={() => setNewButtonActionType('callback')}
|
||||||
className={`flex-1 rounded-lg px-3 py-2 text-sm transition-colors ${
|
className={`flex-1 rounded-lg px-3 py-2 text-sm transition-colors ${
|
||||||
newButtonActionType === 'callback'
|
newButtonActionType === 'callback'
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'border border-dark-700 bg-dark-800 text-dark-300 hover:bg-dark-700'
|
: 'border border-dark-700 bg-dark-800 text-dark-300 hover:bg-dark-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -781,7 +781,7 @@ export default function AdminBroadcastCreate() {
|
|||||||
onClick={() => setNewButtonActionType('url')}
|
onClick={() => setNewButtonActionType('url')}
|
||||||
className={`flex-1 rounded-lg px-3 py-2 text-sm transition-colors ${
|
className={`flex-1 rounded-lg px-3 py-2 text-sm transition-colors ${
|
||||||
newButtonActionType === 'url'
|
newButtonActionType === 'url'
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'border border-dark-700 bg-dark-800 text-dark-300 hover:bg-dark-700'
|
: 'border border-dark-700 bg-dark-800 text-dark-300 hover:bg-dark-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export default function AdminBroadcastDetail() {
|
|||||||
<p className="text-dark-400">{t('admin.broadcasts.notFound')}</p>
|
<p className="text-dark-400">{t('admin.broadcasts.notFound')}</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/broadcasts')}
|
onClick={() => navigate('/admin/broadcasts')}
|
||||||
className="rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('common.back')}
|
{t('common.back')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export default function AdminBroadcasts() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/broadcasts/create')}
|
onClick={() => navigate('/admin/broadcasts/create')}
|
||||||
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
<span className="hidden sm:inline">{t('admin.broadcasts.create')}</span>
|
<span className="hidden sm:inline">{t('admin.broadcasts.create')}</span>
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ function ServerSelector({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex h-5 w-5 items-center justify-center rounded ${
|
className={`flex h-5 w-5 items-center justify-center rounded ${
|
||||||
selected.includes(server.squad_uuid) ? 'bg-accent-500 text-white' : 'bg-dark-600'
|
selected.includes(server.squad_uuid)
|
||||||
|
? 'bg-accent-500 text-on-accent'
|
||||||
|
: 'bg-dark-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{selected.includes(server.squad_uuid) && <CheckIcon />}
|
{selected.includes(server.squad_uuid) && <CheckIcon />}
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ function ServerSelector({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex h-5 w-5 items-center justify-center rounded ${
|
className={`flex h-5 w-5 items-center justify-center rounded ${
|
||||||
selected.includes(server.squad_uuid) ? 'bg-accent-500 text-white' : 'bg-dark-600'
|
selected.includes(server.squad_uuid)
|
||||||
|
? 'bg-accent-500 text-on-accent'
|
||||||
|
: 'bg-dark-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{selected.includes(server.squad_uuid) && <CheckIcon />}
|
{selected.includes(server.squad_uuid) && <CheckIcon />}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export default function AdminCampaigns() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/campaigns/create')}
|
onClick={() => navigate('/admin/campaigns/create')}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
{t('admin.campaigns.createButton')}
|
{t('admin.campaigns.createButton')}
|
||||||
|
|||||||
@@ -424,7 +424,7 @@ function AddChannelForm({
|
|||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!channelId.trim() || isLoading}
|
disabled={!channelId.trim() || isLoading}
|
||||||
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-sm text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
{t('admin.channelSubscriptions.form.submit')}
|
{t('admin.channelSubscriptions.form.submit')}
|
||||||
@@ -499,7 +499,7 @@ function EditChannelForm({
|
|||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-sm text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
{t('admin.channelSubscriptions.form.save')}
|
{t('admin.channelSubscriptions.form.save')}
|
||||||
@@ -641,7 +641,7 @@ export default function AdminChannelSubscriptions() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => setShowAddForm(true)}
|
onClick={() => setShowAddForm(true)}
|
||||||
aria-label={t('admin.channelSubscriptions.addChannel')}
|
aria-label={t('admin.channelSubscriptions.addChannel')}
|
||||||
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
<span className="hidden sm:inline">{t('admin.channelSubscriptions.addChannel')}</span>
|
<span className="hidden sm:inline">{t('admin.channelSubscriptions.addChannel')}</span>
|
||||||
|
|||||||
@@ -442,7 +442,7 @@ function TemplateEditor({
|
|||||||
<button
|
<button
|
||||||
onClick={() => saveMutation.mutate()}
|
onClick={() => saveMutation.mutate()}
|
||||||
disabled={!isDirty || saveMutation.isPending}
|
disabled={!isDirty || saveMutation.isPending}
|
||||||
className="inline-flex items-center justify-center gap-1.5 rounded-lg bg-accent-500 px-3 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-40 sm:px-4 sm:py-2"
|
className="inline-flex items-center justify-center gap-1.5 rounded-lg bg-accent-500 px-3 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-40 sm:px-4 sm:py-2"
|
||||||
>
|
>
|
||||||
<SaveIcon className="h-4 w-4" />
|
<SaveIcon className="h-4 w-4" />
|
||||||
{saveMutation.isPending ? t('common.loading') : t('common.save')}
|
{saveMutation.isPending ? t('common.loading') : t('common.save')}
|
||||||
|
|||||||
@@ -1027,7 +1027,7 @@ export default function AdminInfoPageEditor() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saveMutation.isPending || !slug.trim()}
|
disabled={saveMutation.isPending || !slug.trim()}
|
||||||
className="min-h-[44px] rounded-lg bg-accent-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
className="min-h-[44px] rounded-lg bg-accent-500 px-6 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saveMutation.isPending ? t('admin.infoPages.saving') : t('admin.infoPages.save')}
|
{saveMutation.isPending ? t('admin.infoPages.saving') : t('admin.infoPages.save')}
|
||||||
</button>
|
</button>
|
||||||
@@ -1108,7 +1108,7 @@ export default function AdminInfoPageEditor() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
||||||
displayMode === mode
|
displayMode === mode
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -1136,7 +1136,7 @@ export default function AdminInfoPageEditor() {
|
|||||||
pageType === pt
|
pageType === pt
|
||||||
? pt === 'faq'
|
? pt === 'faq'
|
||||||
? 'bg-warning-500 text-white'
|
? 'bg-warning-500 text-white'
|
||||||
: 'bg-accent-500 text-white'
|
: 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -1194,7 +1194,7 @@ export default function AdminInfoPageEditor() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
||||||
activeLocale === loc
|
activeLocale === loc
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -1415,7 +1415,7 @@ export default function AdminInfoPageEditor() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saveMutation.isPending || !slug.trim()}
|
disabled={saveMutation.isPending || !slug.trim()}
|
||||||
className="min-h-[44px] w-full rounded-lg bg-accent-500 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
className="min-h-[44px] w-full rounded-lg bg-accent-500 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saveMutation.isPending ? t('admin.infoPages.saving') : t('admin.infoPages.save')}
|
{saveMutation.isPending ? t('admin.infoPages.saving') : t('admin.infoPages.save')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ export default function AdminInfoPages() {
|
|||||||
haptic.buttonPress();
|
haptic.buttonPress();
|
||||||
navigate('/admin/info-pages/create?type=faq');
|
navigate('/admin/info-pages/create?type=faq');
|
||||||
}}
|
}}
|
||||||
className="flex min-h-[44px] items-center gap-2 rounded-lg bg-warning-500/80 px-4 py-2.5 text-white transition-colors hover:bg-warning-500"
|
className="flex min-h-[44px] items-center gap-2 rounded-lg bg-warning-500 px-4 py-2.5 text-on-warning transition-colors hover:bg-warning-400"
|
||||||
aria-label={t('admin.infoPages.createFaq')}
|
aria-label={t('admin.infoPages.createFaq')}
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
@@ -249,7 +249,7 @@ export default function AdminInfoPages() {
|
|||||||
haptic.buttonPress();
|
haptic.buttonPress();
|
||||||
navigate('/admin/info-pages/create');
|
navigate('/admin/info-pages/create');
|
||||||
}}
|
}}
|
||||||
className="flex min-h-[44px] items-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-white transition-colors hover:bg-accent-600"
|
className="flex min-h-[44px] items-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
aria-label={t('admin.infoPages.create')}
|
aria-label={t('admin.infoPages.create')}
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
@@ -268,7 +268,7 @@ export default function AdminInfoPages() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
||||||
activeFilter === tab
|
activeFilter === tab
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -588,7 +588,7 @@ export default function AdminLandingEditor() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={isPending || !slug || !Object.values(title).some((v) => v.trim())}
|
disabled={isPending || !slug || !Object.values(title).some((v) => v.trim())}
|
||||||
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-sm text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isPending && (
|
{isPending && (
|
||||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ export default function AdminLandings() {
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/landings/create')}
|
onClick={() => navigate('/admin/landings/create')}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
{t('admin.landings.create')}
|
{t('admin.landings.create')}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function DisplayModeSelector({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
||||||
value === mode
|
value === mode
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
||||||
disabled && 'cursor-not-allowed opacity-50',
|
disabled && 'cursor-not-allowed opacity-50',
|
||||||
)}
|
)}
|
||||||
@@ -87,7 +87,7 @@ function LanguageTabs({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium uppercase transition-colors',
|
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium uppercase transition-colors',
|
||||||
active === lang
|
active === lang
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -221,7 +221,7 @@ function DocumentEditor({
|
|||||||
<button
|
<button
|
||||||
onClick={() => saveMutation.mutate()}
|
onClick={() => saveMutation.mutate()}
|
||||||
disabled={saveMutation.isPending}
|
disabled={saveMutation.isPending}
|
||||||
className="min-h-[44px] rounded-lg bg-accent-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="min-h-[44px] rounded-lg bg-accent-500 px-6 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saveMutation.isPending ? t('admin.legalPages.saving') : t('admin.legalPages.save')}
|
{saveMutation.isPending ? t('admin.legalPages.saving') : t('admin.legalPages.save')}
|
||||||
</button>
|
</button>
|
||||||
@@ -321,7 +321,7 @@ function RulesEditor({ onDirtyChange }: { onDirtyChange: (dirty: boolean) => voi
|
|||||||
<button
|
<button
|
||||||
onClick={() => saveMutation.mutate()}
|
onClick={() => saveMutation.mutate()}
|
||||||
disabled={saveMutation.isPending}
|
disabled={saveMutation.isPending}
|
||||||
className="min-h-[44px] rounded-lg bg-accent-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="min-h-[44px] rounded-lg bg-accent-500 px-6 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saveMutation.isPending ? t('admin.legalPages.saving') : t('admin.legalPages.save')}
|
{saveMutation.isPending ? t('admin.legalPages.saving') : t('admin.legalPages.save')}
|
||||||
</button>
|
</button>
|
||||||
@@ -604,7 +604,7 @@ function FaqEditor({ onDirtyChange }: { onDirtyChange: (dirty: boolean) => void
|
|||||||
<button
|
<button
|
||||||
onClick={() => createMutation.mutate()}
|
onClick={() => createMutation.mutate()}
|
||||||
disabled={createMutation.isPending}
|
disabled={createMutation.isPending}
|
||||||
className="flex min-h-[44px] items-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="flex min-h-[44px] items-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
<span className="hidden sm:inline">{t('admin.legalPages.addQuestion')}</span>
|
<span className="hidden sm:inline">{t('admin.legalPages.addQuestion')}</span>
|
||||||
@@ -669,7 +669,7 @@ export default function AdminLegalPages() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
'min-h-[44px] rounded-lg px-4 py-2.5 text-sm font-medium transition-colors',
|
||||||
activeTab === tab
|
activeTab === tab
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
: 'bg-dark-700 text-dark-300 hover:bg-dark-600 hover:text-dark-100',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ export default function AdminNews() {
|
|||||||
haptic.buttonPress();
|
haptic.buttonPress();
|
||||||
navigate('/admin/news/create');
|
navigate('/admin/news/create');
|
||||||
}}
|
}}
|
||||||
className="flex min-h-[44px] items-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-white transition-colors hover:bg-accent-600"
|
className="flex min-h-[44px] items-center gap-2 rounded-lg bg-accent-500 px-4 py-2.5 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
aria-label={t('news.admin.create')}
|
aria-label={t('news.admin.create')}
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
|
|||||||
@@ -558,7 +558,7 @@ export default function AdminNewsCreate() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saveMutation.isPending || !title.trim() || !slug.trim() || !selectedCategory}
|
disabled={saveMutation.isPending || !title.trim() || !slug.trim() || !selectedCategory}
|
||||||
className="min-h-[44px] rounded-lg bg-accent-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
className="min-h-[44px] rounded-lg bg-accent-500 px-6 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saveMutation.isPending ? t('news.admin.saving') : t('news.admin.save')}
|
{saveMutation.isPending ? t('news.admin.saving') : t('news.admin.save')}
|
||||||
</button>
|
</button>
|
||||||
@@ -912,7 +912,7 @@ export default function AdminNewsCreate() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saveMutation.isPending || !title.trim() || !slug.trim() || !selectedCategory}
|
disabled={saveMutation.isPending || !title.trim() || !slug.trim() || !selectedCategory}
|
||||||
className="min-h-[44px] w-full rounded-lg bg-accent-500 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
className="min-h-[44px] w-full rounded-lg bg-accent-500 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{saveMutation.isPending ? t('news.admin.saving') : t('news.admin.save')}
|
{saveMutation.isPending ? t('news.admin.saving') : t('news.admin.save')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export default function AdminPartnerCampaignAssign() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/admin/campaigns/create?partnerId=${userId}`)}
|
onClick={() => navigate(`/admin/campaigns/create?partnerId=${userId}`)}
|
||||||
className="mt-2 w-full rounded-lg bg-accent-500 px-4 py-3 font-medium text-white transition-colors hover:bg-accent-600"
|
className="mt-2 w-full rounded-lg bg-accent-500 px-4 py-3 font-medium text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('admin.partnerDetail.campaigns.createNew')}
|
{t('admin.partnerDetail.campaigns.createNew')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export default function AdminPartnerCommission() {
|
|||||||
Number(commissionValue) < 1 ||
|
Number(commissionValue) < 1 ||
|
||||||
Number(commissionValue) > 100
|
Number(commissionValue) > 100
|
||||||
}
|
}
|
||||||
className="flex-1 rounded-lg bg-accent-500 px-4 py-3 font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="flex-1 rounded-lg bg-accent-500 px-4 py-3 font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{updateMutation.isPending ? t('common.saving') : t('common.save')}
|
{updateMutation.isPending ? t('common.saving') : t('common.save')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -301,7 +301,9 @@ export default function AdminPaymentMethodEdit() {
|
|||||||
<span className="text-sm">{opt.name}</span>
|
<span className="text-sm">{opt.name}</span>
|
||||||
<div
|
<div
|
||||||
className={`flex h-5 w-5 items-center justify-center rounded ${
|
className={`flex h-5 w-5 items-center justify-center rounded ${
|
||||||
enabled ? 'bg-accent-500 text-white' : 'border border-dark-600 bg-dark-700'
|
enabled
|
||||||
|
? 'bg-accent-500 text-on-accent'
|
||||||
|
: 'border border-dark-600 bg-dark-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{enabled && <CheckIcon />}
|
{enabled && <CheckIcon />}
|
||||||
@@ -493,7 +495,7 @@ export default function AdminPaymentMethodEdit() {
|
|||||||
<span>{group.name}</span>
|
<span>{group.name}</span>
|
||||||
<div
|
<div
|
||||||
className={`flex h-4 w-4 items-center justify-center rounded ${
|
className={`flex h-4 w-4 items-center justify-center rounded ${
|
||||||
selected ? 'bg-accent-500 text-white' : 'border border-dark-600'
|
selected ? 'bg-accent-500 text-on-accent' : 'border border-dark-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{selected && <CheckIcon />}
|
{selected && <CheckIcon />}
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ export default function AdminPaymentMethods() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-dark-50">{t('admin.paymentMethods.title')}</h1>
|
<h1 className="text-xl font-bold text-dark-100">{t('admin.paymentMethods.title')}</h1>
|
||||||
<p className="text-sm text-dark-400">{t('admin.paymentMethods.description')}</p>
|
<p className="text-sm text-dark-400">{t('admin.paymentMethods.description')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export default function AdminPayments() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-semibold text-dark-100">{t('admin.payments.title')}</h1>
|
<h1 className="text-xl font-bold text-dark-100">{t('admin.payments.title')}</h1>
|
||||||
<p className="text-sm text-dark-400">{t('admin.payments.description')}</p>
|
<p className="text-sm text-dark-400">{t('admin.payments.description')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -239,7 +239,7 @@ export default function AdminPayments() {
|
|||||||
onClick={() => setStatusFilter(option.value)}
|
onClick={() => setStatusFilter(option.value)}
|
||||||
className={`rounded-lg px-3 py-1.5 text-sm transition-all ${
|
className={`rounded-lg px-3 py-1.5 text-sm transition-all ${
|
||||||
statusFilter === option.value
|
statusFilter === option.value
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-800 text-dark-300 hover:bg-dark-700'
|
: 'bg-dark-800 text-dark-300 hover:bg-dark-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -256,7 +256,7 @@ export default function AdminPayments() {
|
|||||||
onClick={() => handlePeriodChange(option.value)}
|
onClick={() => handlePeriodChange(option.value)}
|
||||||
className={`rounded-lg px-3 py-1.5 text-sm transition-all ${
|
className={`rounded-lg px-3 py-1.5 text-sm transition-all ${
|
||||||
periodFilter === option.value
|
periodFilter === option.value
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-800 text-dark-300 hover:bg-dark-700'
|
: 'bg-dark-800 text-dark-300 hover:bg-dark-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -267,7 +267,7 @@ export default function AdminPayments() {
|
|||||||
onClick={() => handlePeriodChange('custom')}
|
onClick={() => handlePeriodChange('custom')}
|
||||||
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm transition-all ${
|
className={`flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm transition-all ${
|
||||||
periodFilter === 'custom'
|
periodFilter === 'custom'
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-800 text-dark-300 hover:bg-dark-700'
|
: 'bg-dark-800 text-dark-300 hover:bg-dark-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ export default function AdminPinnedMessages() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/pinned-messages/create')}
|
onClick={() => navigate('/admin/pinned-messages/create')}
|
||||||
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
<span className="hidden sm:inline">{t('admin.pinnedMessages.create')}</span>
|
<span className="hidden sm:inline">{t('admin.pinnedMessages.create')}</span>
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export default function AdminPolicies() {
|
|||||||
<PermissionGate permission="roles:create">
|
<PermissionGate permission="roles:create">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/policies/create')}
|
onClick={() => navigate('/admin/policies/create')}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
{t('admin.policies.createPolicy')}
|
{t('admin.policies.createPolicy')}
|
||||||
|
|||||||
@@ -757,7 +757,7 @@ export default function AdminPolicyEdit() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className="rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isSaving ? t('admin.policies.form.saving') : t('admin.policies.form.save')}
|
{isSaving ? t('admin.policies.form.saving') : t('admin.policies.form.save')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default function AdminPromoGroups() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/promo-groups/create')}
|
onClick={() => navigate('/admin/promo-groups/create')}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
{t('admin.promoGroups.addGroup')}
|
{t('admin.promoGroups.addGroup')}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ export default function AdminPromoOfferSend() {
|
|||||||
<div className="flex justify-center gap-3">
|
<div className="flex justify-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/promo-offers')}
|
onClick={() => navigate('/admin/promo-offers')}
|
||||||
className="rounded-lg bg-accent-500 px-6 py-2 text-white transition-colors hover:bg-accent-600"
|
className="rounded-lg bg-accent-500 px-6 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('admin.promoOffers.backToList')}
|
{t('admin.promoOffers.backToList')}
|
||||||
</button>
|
</button>
|
||||||
@@ -441,7 +441,7 @@ export default function AdminPromoOfferSend() {
|
|||||||
{selectedTemplate.message_text}
|
{selectedTemplate.message_text}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<span className="inline-block rounded-lg bg-accent-500 px-4 py-2 text-sm text-white">
|
<span className="inline-block rounded-lg bg-accent-500 px-4 py-2 text-sm text-on-accent">
|
||||||
{selectedTemplate.button_text}
|
{selectedTemplate.button_text}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export default function AdminPromoOffers() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/promo-offers/send')}
|
onClick={() => navigate('/admin/promo-offers/send')}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<SendIcon />
|
<SendIcon />
|
||||||
{t('admin.promoOffers.sendButton')}
|
{t('admin.promoOffers.sendButton')}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export default function AdminPromocodeStats() {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/admin/promocodes/${id}/edit`)}
|
onClick={() => navigate(`/admin/promocodes/${id}/edit`)}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
{t('admin.promocodes.modal.edit')}
|
{t('admin.promocodes.modal.edit')}
|
||||||
|
|||||||
@@ -102,13 +102,13 @@ export default function AdminPromocodes() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-semibold text-dark-100">{t('admin.promocodes.title')}</h1>
|
<h1 className="text-xl font-bold text-dark-100">{t('admin.promocodes.title')}</h1>
|
||||||
<p className="text-sm text-dark-400">{t('admin.promocodes.subtitle')}</p>
|
<p className="text-sm text-dark-400">{t('admin.promocodes.subtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/promocodes/create')}
|
onClick={() => navigate('/admin/promocodes/create')}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
{t('admin.promocodes.addPromocode')}
|
{t('admin.promocodes.addPromocode')}
|
||||||
|
|||||||
@@ -432,7 +432,7 @@ export default function AdminRoleAssign() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={assignMutation.isPending || !selectedUser || !selectedRoleId}
|
disabled={assignMutation.isPending || !selectedUser || !selectedRoleId}
|
||||||
className="flex items-center gap-2 rounded-lg bg-accent-500 px-5 py-2 text-white transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
className="flex items-center gap-2 rounded-lg bg-accent-500 px-5 py-2 text-on-accent transition-colors hover:bg-accent-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{assignMutation.isPending
|
{assignMutation.isPending
|
||||||
? t('admin.roleAssign.assigning')
|
? t('admin.roleAssign.assigning')
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ export default function AdminRoleEdit() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
className="rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{isSaving ? t('admin.roles.form.saving') : t('admin.roles.form.save')}
|
{isSaving ? t('admin.roles.form.saving') : t('admin.roles.form.save')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export default function AdminRoles() {
|
|||||||
<PermissionGate permission="roles:create">
|
<PermissionGate permission="roles:create">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/roles/create')}
|
onClick={() => navigate('/admin/roles/create')}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
{t('admin.roles.createRole')}
|
{t('admin.roles.createRole')}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default function AdminServers() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => syncMutation.mutate()}
|
onClick={() => syncMutation.mutate()}
|
||||||
disabled={syncMutation.isPending}
|
disabled={syncMutation.isPending}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<SyncIcon />
|
<SyncIcon />
|
||||||
{syncMutation.isPending ? t('admin.servers.syncing') : t('admin.servers.sync')}
|
{syncMutation.isPending ? t('admin.servers.syncing') : t('admin.servers.sync')}
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export default function AdminSettings() {
|
|||||||
<BackIcon />
|
<BackIcon />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<h1 className="text-lg font-bold text-dark-100">{t('admin.settings.title')}</h1>
|
<h1 className="text-xl font-bold text-dark-100">{t('admin.settings.title')}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SettingsTreeSidebar
|
<SettingsTreeSidebar
|
||||||
|
|||||||
@@ -661,7 +661,7 @@ export default function AdminTariffCreate() {
|
|||||||
!selectedExternalSquad
|
!selectedExternalSquad
|
||||||
? isDaily
|
? isDaily
|
||||||
? 'bg-warning-500 text-white'
|
? 'bg-warning-500 text-white'
|
||||||
: 'bg-accent-500 text-white'
|
: 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-600'
|
: 'bg-dark-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -691,7 +691,7 @@ export default function AdminTariffCreate() {
|
|||||||
isSelected
|
isSelected
|
||||||
? isDaily
|
? isDaily
|
||||||
? 'bg-warning-500 text-white'
|
? 'bg-warning-500 text-white'
|
||||||
: 'bg-accent-500 text-white'
|
: 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-600'
|
: 'bg-dark-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -740,7 +740,7 @@ export default function AdminTariffCreate() {
|
|||||||
isSelected
|
isSelected
|
||||||
? isDaily
|
? isDaily
|
||||||
? 'bg-warning-500 text-white'
|
? 'bg-warning-500 text-white'
|
||||||
: 'bg-accent-500 text-white'
|
: 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-600'
|
: 'bg-dark-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -1055,7 +1055,7 @@ export default function AdminTariffCreate() {
|
|||||||
isSelected
|
isSelected
|
||||||
? isDaily
|
? isDaily
|
||||||
? 'bg-warning-500 text-white'
|
? 'bg-warning-500 text-white'
|
||||||
: 'bg-accent-500 text-white'
|
: 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-600'
|
: 'bg-dark-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ export default function AdminTariffs() {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-semibold text-dark-100">{t('admin.tariffs.title')}</h1>
|
<h1 className="text-xl font-bold text-dark-100">{t('admin.tariffs.title')}</h1>
|
||||||
<p className="text-sm text-dark-400">{t('admin.tariffs.subtitle')}</p>
|
<p className="text-sm text-dark-400">{t('admin.tariffs.subtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,7 +328,7 @@ export default function AdminTariffs() {
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/tariffs/create')}
|
onClick={() => navigate('/admin/tariffs/create')}
|
||||||
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="flex items-center justify-center gap-2 rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
{t('admin.tariffs.create')}
|
{t('admin.tariffs.create')}
|
||||||
|
|||||||
@@ -272,9 +272,7 @@ export default function AdminTickets() {
|
|||||||
<BackIcon />
|
<BackIcon />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<h1 className="text-2xl font-bold text-dark-50 sm:text-3xl">
|
<h1 className="text-xl font-bold text-dark-100">{t('admin.tickets.title')}</h1>
|
||||||
{t('admin.tickets.title')}
|
|
||||||
</h1>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/tickets/settings')}
|
onClick={() => navigate('/admin/tickets/settings')}
|
||||||
|
|||||||
@@ -740,7 +740,7 @@ export default function AdminUserDetail() {
|
|||||||
<p className="text-dark-400">{t('admin.users.notFound')}</p>
|
<p className="text-dark-400">{t('admin.users.notFound')}</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/admin/users')}
|
onClick={() => navigate('/admin/users')}
|
||||||
className="rounded-lg bg-accent-500 px-4 py-2 text-white transition-colors hover:bg-accent-600"
|
className="rounded-lg bg-accent-500 px-4 py-2 text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('common.back')}
|
{t('common.back')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ export default function AdminWithdrawalDetail() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => completeMutation.mutate()}
|
onClick={() => completeMutation.mutate()}
|
||||||
disabled={completeMutation.isPending}
|
disabled={completeMutation.isPending}
|
||||||
className="w-full rounded-lg bg-accent-500 px-4 py-3 font-medium text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="w-full rounded-lg bg-accent-500 px-4 py-3 font-medium text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{completeMutation.isPending
|
{completeMutation.isPending
|
||||||
? t('admin.withdrawals.detail.completing')
|
? t('admin.withdrawals.detail.completing')
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export default function AdminWithdrawals() {
|
|||||||
onClick={() => setStatusFilter(filter)}
|
onClick={() => setStatusFilter(filter)}
|
||||||
className={`whitespace-nowrap rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
|
className={`whitespace-nowrap rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
|
||||||
statusFilter === filter
|
statusFilter === filter
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-800/40 text-dark-400 hover:bg-dark-700/50 hover:text-dark-200'
|
: 'bg-dark-800/40 text-dark-400 hover:bg-dark-700/50 hover:text-dark-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function AutoLogin() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate('/login', { replace: true })}
|
onClick={() => navigate('/login', { replace: true })}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="rounded-xl bg-accent-500 px-6 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('auth.login', 'Login')}
|
{t('auth.login', 'Login')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ export default function Balance() {
|
|||||||
{translatedDesc || method.description}
|
{translatedDesc || method.description}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mt-3 text-xs text-dark-600">
|
<div className="mt-3 text-xs text-dark-400">
|
||||||
{formatAmount(method.min_amount_kopeks / 100, 0)} {t('common.rangeTo', 'to')}{' '}
|
{formatAmount(method.min_amount_kopeks / 100, 0)} {t('common.rangeTo', 'to')}{' '}
|
||||||
{formatAmount(method.max_amount_kopeks / 100, 0)} {currencySymbol}
|
{formatAmount(method.max_amount_kopeks / 100, 0)} {currencySymbol}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ export default function Dashboard() {
|
|||||||
) : (
|
) : (
|
||||||
<Link
|
<Link
|
||||||
to="/subscription/purchase"
|
to="/subscription/purchase"
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-500 p-3.5 text-sm font-semibold text-white transition-colors hover:bg-accent-600"
|
className="flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-500 p-3.5 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<span className="text-base">+</span>{' '}
|
<span className="text-base">+</span>{' '}
|
||||||
{t('subscriptions.browsePlans', 'Посмотреть тарифы и купить подписку')}
|
{t('subscriptions.browsePlans', 'Посмотреть тарифы и купить подписку')}
|
||||||
@@ -381,7 +381,7 @@ export default function Dashboard() {
|
|||||||
)}
|
)}
|
||||||
<Link
|
<Link
|
||||||
to="/subscription/purchase"
|
to="/subscription/purchase"
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-500 p-3.5 text-sm font-semibold text-white transition-colors hover:bg-accent-600"
|
className="flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-500 p-3.5 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<span className="text-base">+</span>{' '}
|
<span className="text-base">+</span>{' '}
|
||||||
{t('subscriptions.browsePlans', 'Посмотреть тарифы и купить подписку')}
|
{t('subscriptions.browsePlans', 'Посмотреть тарифы и купить подписку')}
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ export default function GiftClaim() {
|
|||||||
'flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3 text-sm font-bold transition-all active:scale-[0.98]',
|
'flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3 text-sm font-bold transition-all active:scale-[0.98]',
|
||||||
copied
|
copied
|
||||||
? 'bg-success-500/20 text-success-400'
|
? 'bg-success-500/20 text-success-400'
|
||||||
: 'bg-accent-500 text-white hover:bg-accent-400',
|
: 'bg-accent-500 text-on-accent hover:bg-accent-400',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{copied ? <CheckIcon className="h-4 w-4" /> : <CopyIcon className="h-4 w-4" />}
|
{copied ? <CheckIcon className="h-4 w-4" /> : <CopyIcon className="h-4 w-4" />}
|
||||||
@@ -264,7 +264,7 @@ export default function GiftClaim() {
|
|||||||
{gift.bot_claim_link && (
|
{gift.bot_claim_link && (
|
||||||
<a
|
<a
|
||||||
href={gift.bot_claim_link}
|
href={gift.bot_claim_link}
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3.5 text-sm font-bold text-white shadow-lg shadow-accent-500/25 transition-all hover:bg-accent-400 active:scale-[0.98]"
|
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3.5 text-sm font-bold text-on-accent shadow-lg shadow-accent-500/25 transition-all hover:bg-accent-400 active:scale-[0.98]"
|
||||||
>
|
>
|
||||||
{t('landing.giftClaim.activateTelegram', 'Activate in Telegram')}
|
{t('landing.giftClaim.activateTelegram', 'Activate in Telegram')}
|
||||||
</a>
|
</a>
|
||||||
@@ -303,7 +303,7 @@ export default function GiftClaim() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center justify-center gap-2 rounded-xl px-6 py-3.5 text-sm font-bold transition-all',
|
'flex w-full items-center justify-center gap-2 rounded-xl px-6 py-3.5 text-sm font-bold transition-all',
|
||||||
isValidEmail(email) && !claimMutation.isPending
|
isValidEmail(email) && !claimMutation.isPending
|
||||||
? 'bg-accent-500 text-white hover:bg-accent-400 active:scale-[0.98]'
|
? 'bg-accent-500 text-on-accent hover:bg-accent-400 active:scale-[0.98]'
|
||||||
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ function CodeOnlySuccessState({
|
|||||||
'flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3.5 text-sm font-bold transition-all duration-200 active:scale-[0.98]',
|
'flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3.5 text-sm font-bold transition-all duration-200 active:scale-[0.98]',
|
||||||
copied
|
copied
|
||||||
? 'bg-success-500/20 text-success-400'
|
? 'bg-success-500/20 text-success-400'
|
||||||
: 'bg-accent-500 text-white shadow-lg shadow-accent-500/25 hover:bg-accent-400',
|
: 'bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25 hover:bg-accent-400',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
@@ -236,7 +236,7 @@ function DeliveredState({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('gift.backToDashboard', 'Back to dashboard')}
|
{t('gift.backToDashboard', 'Back to dashboard')}
|
||||||
</button>
|
</button>
|
||||||
@@ -303,7 +303,7 @@ function PendingActivationState({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('gift.backToDashboard', 'Back to dashboard')}
|
{t('gift.backToDashboard', 'Back to dashboard')}
|
||||||
</button>
|
</button>
|
||||||
@@ -335,7 +335,7 @@ function FailedState() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate('/gift')}
|
onClick={() => navigate('/gift')}
|
||||||
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('gift.tryAgain', 'Try again')}
|
{t('gift.tryAgain', 'Try again')}
|
||||||
</button>
|
</button>
|
||||||
@@ -372,7 +372,7 @@ function PollErrorState() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="flex w-full items-center justify-center rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('gift.backToDashboard', 'Back to dashboard')}
|
{t('gift.backToDashboard', 'Back to dashboard')}
|
||||||
</button>
|
</button>
|
||||||
@@ -406,7 +406,7 @@ function PollTimedOutState({ onRetry }: { onRetry: () => void }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onRetry}
|
onClick={onRetry}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('gift.retry', 'Retry')}
|
{t('gift.retry', 'Retry')}
|
||||||
</button>
|
</button>
|
||||||
@@ -436,7 +436,7 @@ function NoTokenState() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate('/gift')}
|
onClick={() => navigate('/gift')}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('gift.backToGift', 'Go back')}
|
{t('gift.backToGift', 'Go back')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ function PeriodCard({
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-md px-2 py-0.5 text-xs font-bold',
|
'rounded-md px-2 py-0.5 text-xs font-bold',
|
||||||
isSelected ? 'bg-white/20 text-white' : 'bg-accent-500/20 text-accent-400',
|
isSelected ? 'bg-white/20 text-on-accent' : 'bg-accent-500/20 text-accent-400',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
-{period.discount_percent}%
|
-{period.discount_percent}%
|
||||||
@@ -366,7 +366,7 @@ function PaymentMethodCard({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded-xl px-4 py-2 text-sm font-medium transition-all duration-200',
|
'rounded-xl px-4 py-2 text-sm font-medium transition-all duration-200',
|
||||||
selectedSubOption === opt.id
|
selectedSubOption === opt.id
|
||||||
? 'bg-accent-500 text-white shadow-sm shadow-accent-500/25'
|
? 'bg-accent-500 text-on-accent shadow-sm shadow-accent-500/25'
|
||||||
: 'bg-dark-800/50 text-dark-300 hover:bg-dark-700/50',
|
: 'bg-dark-800/50 text-dark-300 hover:bg-dark-700/50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -716,7 +716,7 @@ function BuyTabContent({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center justify-center gap-2 rounded-2xl px-6 py-4 text-base font-semibold transition-all duration-200',
|
'flex w-full items-center justify-center gap-2 rounded-2xl px-6 py-4 text-base font-semibold transition-all duration-200',
|
||||||
canSubmit && !purchaseMutation.isPending
|
canSubmit && !purchaseMutation.isPending
|
||||||
? 'bg-accent-500 text-white shadow-lg shadow-accent-500/25 hover:bg-accent-400 hover:shadow-accent-500/40 active:scale-[0.98]'
|
? 'bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25 hover:bg-accent-400 hover:shadow-accent-500/40 active:scale-[0.98]'
|
||||||
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -833,7 +833,7 @@ function ActivateTabContent({ initialCode }: { initialCode?: string | null }) {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'w-full max-w-sm rounded-2xl px-6 py-4 text-base font-semibold transition-all duration-200',
|
'w-full max-w-sm rounded-2xl px-6 py-4 text-base font-semibold transition-all duration-200',
|
||||||
code.trim() && !activateMutation.isPending
|
code.trim() && !activateMutation.isPending
|
||||||
? 'bg-accent-500 text-white shadow-lg shadow-accent-500/25 hover:bg-accent-400 active:scale-[0.98]'
|
? 'bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25 hover:bg-accent-400 active:scale-[0.98]'
|
||||||
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -962,7 +962,7 @@ function SentGiftCard({ gift }: { gift: SentGift }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleShare}
|
onClick={handleShare}
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-4 py-3 text-sm font-bold uppercase tracking-wider text-white transition-colors hover:bg-accent-400 active:scale-[0.98]"
|
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-4 py-3 text-sm font-bold uppercase tracking-wider text-on-accent transition-colors hover:bg-accent-400 active:scale-[0.98]"
|
||||||
>
|
>
|
||||||
<ExportIcon className="h-4 w-4" />
|
<ExportIcon className="h-4 w-4" />
|
||||||
{t('gift.shareGift')}
|
{t('gift.shareGift')}
|
||||||
@@ -1243,7 +1243,7 @@ export default function GiftSubscription() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex-1 rounded-xl px-3 py-2.5 text-sm font-medium transition-all duration-200',
|
'flex-1 rounded-xl px-3 py-2.5 text-sm font-medium transition-all duration-200',
|
||||||
activeTab === tab.id
|
activeTab === tab.id
|
||||||
? 'bg-accent-500 text-white shadow-sm'
|
? 'bg-accent-500 text-on-accent shadow-sm'
|
||||||
: 'text-dark-400 hover:text-dark-200',
|
: 'text-dark-400 hover:text-dark-200',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -838,7 +838,7 @@ export default function Info() {
|
|||||||
onClick={() => setActiveTab(tab.id)}
|
onClick={() => setActiveTab(tab.id)}
|
||||||
className={`flex min-h-[44px] shrink-0 items-center gap-2 rounded-lg px-4 py-2.5 text-sm font-medium transition-colors ${
|
className={`flex min-h-[44px] shrink-0 items-center gap-2 rounded-lg px-4 py-2.5 text-sm font-medium transition-colors ${
|
||||||
activeTab === tab.id
|
activeTab === tab.id
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'bg-dark-800 text-dark-300 hover:bg-dark-700'
|
: 'bg-dark-800 text-dark-300 hover:bg-dark-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -583,7 +583,7 @@ export default function Login() {
|
|||||||
type="button"
|
type="button"
|
||||||
className={`flex-1 rounded-md py-2 text-sm font-medium transition-all ${
|
className={`flex-1 rounded-md py-2 text-sm font-medium transition-all ${
|
||||||
authMode === 'login'
|
authMode === 'login'
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'text-dark-400 hover:text-dark-200'
|
: 'text-dark-400 hover:text-dark-200'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setAuthMode('login')}
|
onClick={() => setAuthMode('login')}
|
||||||
@@ -594,7 +594,7 @@ export default function Login() {
|
|||||||
type="button"
|
type="button"
|
||||||
className={`flex-1 rounded-md py-2 text-sm font-medium transition-all ${
|
className={`flex-1 rounded-md py-2 text-sm font-medium transition-all ${
|
||||||
authMode === 'register'
|
authMode === 'register'
|
||||||
? 'bg-accent-500 text-white'
|
? 'bg-accent-500 text-on-accent'
|
||||||
: 'text-dark-400 hover:text-dark-200'
|
: 'text-dark-400 hover:text-dark-200'
|
||||||
}`}
|
}`}
|
||||||
onClick={() => setAuthMode('register')}
|
onClick={() => setAuthMode('register')}
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ function SuccessState({
|
|||||||
href={botLink}
|
href={botLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('landing.openBot')}
|
{t('landing.openBot')}
|
||||||
</a>
|
</a>
|
||||||
@@ -492,7 +492,7 @@ function GiftPendingActivationState({
|
|||||||
href={botLink}
|
href={botLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('landing.openBot')}
|
{t('landing.openBot')}
|
||||||
</a>
|
</a>
|
||||||
@@ -545,7 +545,7 @@ function PollTimedOutState({ onRetry }: { onRetry: () => void }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onRetry}
|
onClick={onRetry}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('common.retry', 'Retry')}
|
{t('common.retry', 'Retry')}
|
||||||
</button>
|
</button>
|
||||||
@@ -641,7 +641,7 @@ function GiftLinkShareState({
|
|||||||
'flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3.5 text-sm font-bold transition-all duration-200 active:scale-[0.98]',
|
'flex w-full items-center justify-center gap-2 rounded-xl px-4 py-3.5 text-sm font-bold transition-all duration-200 active:scale-[0.98]',
|
||||||
copied
|
copied
|
||||||
? 'bg-success-500/20 text-success-400'
|
? 'bg-success-500/20 text-success-400'
|
||||||
: 'bg-accent-500 text-white shadow-lg shadow-accent-500/25 hover:bg-accent-400',
|
: 'bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25 hover:bg-accent-400',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ function PeriodTabs({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'whitespace-nowrap rounded-full px-4 py-2 text-sm font-medium transition-all duration-200',
|
'whitespace-nowrap rounded-full px-4 py-2 text-sm font-medium transition-all duration-200',
|
||||||
selectedDays === period.days
|
selectedDays === period.days
|
||||||
? 'bg-accent-500 text-white shadow-lg shadow-accent-500/25'
|
? 'bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25'
|
||||||
: 'bg-dark-800/50 text-dark-300 hover:bg-dark-700/50 hover:text-dark-100',
|
: 'bg-dark-800/50 text-dark-300 hover:bg-dark-700/50 hover:text-dark-100',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -409,7 +409,7 @@ function PaymentMethodCard({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'rounded-xl px-4 py-2 text-sm font-medium transition-all duration-200',
|
'rounded-xl px-4 py-2 text-sm font-medium transition-all duration-200',
|
||||||
selectedSubOption === opt.id
|
selectedSubOption === opt.id
|
||||||
? 'bg-accent-500 text-white shadow-sm shadow-accent-500/25'
|
? 'bg-accent-500 text-on-accent shadow-sm shadow-accent-500/25'
|
||||||
: 'bg-dark-800/50 text-dark-300 hover:bg-dark-700/50 hover:text-dark-100',
|
: 'bg-dark-800/50 text-dark-300 hover:bg-dark-700/50 hover:text-dark-100',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -583,7 +583,7 @@ function SummaryCard({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center justify-center gap-2 rounded-2xl px-6 py-4 text-base font-semibold transition-all duration-200',
|
'flex w-full items-center justify-center gap-2 rounded-2xl px-6 py-4 text-base font-semibold transition-all duration-200',
|
||||||
canSubmit && !isSubmitting
|
canSubmit && !isSubmitting
|
||||||
? 'bg-accent-500 text-white shadow-lg shadow-accent-500/25 hover:bg-accent-400 hover:shadow-accent-500/40 active:scale-[0.98]'
|
? 'bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25 hover:bg-accent-400 hover:shadow-accent-500/40 active:scale-[0.98]'
|
||||||
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -629,7 +629,7 @@ function SummaryCard({
|
|||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full items-center justify-center gap-2 rounded-2xl px-6 py-4 text-base font-semibold transition-all duration-200',
|
'flex w-full items-center justify-center gap-2 rounded-2xl px-6 py-4 text-base font-semibold transition-all duration-200',
|
||||||
canSubmit && !isSubmitting
|
canSubmit && !isSubmitting
|
||||||
? 'bg-accent-500 text-white shadow-lg shadow-accent-500/25 hover:bg-accent-400 hover:shadow-accent-500/40 active:scale-[0.98]'
|
? 'bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25 hover:bg-accent-400 hover:shadow-accent-500/40 active:scale-[0.98]'
|
||||||
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
: 'cursor-not-allowed bg-dark-800 text-dark-500',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -734,7 +734,7 @@ function DiscountBanner({
|
|||||||
<div className="flex flex-col items-center gap-4 px-5 py-5 sm:flex-row sm:justify-between">
|
<div className="flex flex-col items-center gap-4 px-5 py-5 sm:flex-row sm:justify-between">
|
||||||
{/* Left: badge + text */}
|
{/* Left: badge + text */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="shrink-0 rounded-full bg-accent-500 px-3 py-1 text-sm font-bold text-white shadow-lg shadow-accent-500/25">
|
<span className="shrink-0 rounded-full bg-accent-500 px-3 py-1 text-sm font-bold text-on-accent shadow-lg shadow-accent-500/25">
|
||||||
-{discount.percent}%
|
-{discount.percent}%
|
||||||
</span>
|
</span>
|
||||||
{discount.badge_text && (
|
{discount.badge_text && (
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ export default function RenewSubscription() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleRenew(selectedPeriod)}
|
onClick={() => handleRenew(selectedPeriod)}
|
||||||
disabled={renewMutation.isPending}
|
disabled={renewMutation.isPending}
|
||||||
className="w-full rounded-2xl bg-accent-500 py-3.5 text-base font-semibold text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
|
className="w-full rounded-2xl bg-accent-500 py-3.5 text-base font-semibold text-on-accent transition-colors hover:bg-accent-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{renewMutation.isPending
|
{renewMutation.isPending
|
||||||
? t('common.processing', 'Обработка...')
|
? t('common.processing', 'Обработка...')
|
||||||
|
|||||||
@@ -515,7 +515,7 @@ export default function Subscription() {
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/subscriptions')}
|
onClick={() => navigate('/subscriptions')}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-2.5 text-sm font-medium text-white"
|
className="rounded-xl bg-accent-500 px-6 py-2.5 text-sm font-medium text-on-accent"
|
||||||
>
|
>
|
||||||
{t('subscription.backToList', 'Мои подписки')}
|
{t('subscription.backToList', 'Мои подписки')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export default function SubscriptionPurchase() {
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => refetchOptions()}
|
onClick={() => refetchOptions()}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-600"
|
className="rounded-xl bg-accent-500 px-6 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('common.retry')}
|
{t('common.retry')}
|
||||||
</button>
|
</button>
|
||||||
@@ -322,7 +322,7 @@ export default function SubscriptionPurchase() {
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => refetchOptions()}
|
onClick={() => refetchOptions()}
|
||||||
className="rounded-xl bg-accent-500 px-6 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-600"
|
className="rounded-xl bg-accent-500 px-6 py-2 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('common.retry')}
|
{t('common.retry')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function EmptyState({ onBuy }: { onBuy: () => void }) {
|
|||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={onBuy}
|
onClick={onBuy}
|
||||||
className="rounded-xl bg-accent-500 px-8 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-600"
|
className="rounded-xl bg-accent-500 px-8 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('subscriptions.buy', 'Купить подписку')}
|
{t('subscriptions.buy', 'Купить подписку')}
|
||||||
</button>
|
</button>
|
||||||
@@ -135,7 +135,7 @@ export default function Subscriptions() {
|
|||||||
{!isLoading && subscriptions.length > 0 && !hasActivePaid && (
|
{!isLoading && subscriptions.length > 0 && !hasActivePaid && (
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/subscription/purchase')}
|
onClick={() => navigate('/subscription/purchase')}
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-500 p-3.5 text-sm font-semibold text-white transition-colors hover:bg-accent-600"
|
className="flex w-full items-center justify-center gap-2 rounded-2xl bg-accent-500 p-3.5 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-5 w-5" />
|
<PlusIcon className="h-5 w-5" />
|
||||||
{t('subscriptions.browsePlans', 'Посмотреть тарифы и купить подписку')}
|
{t('subscriptions.browsePlans', 'Посмотреть тарифы и купить подписку')}
|
||||||
@@ -171,7 +171,7 @@ export default function Subscriptions() {
|
|||||||
(Telegram-баг #605056/#605063). */}
|
(Telegram-баг #605056/#605063). */}
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/subscription/purchase')}
|
onClick={() => navigate('/subscription/purchase')}
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3 text-sm font-semibold text-white transition-colors hover:bg-accent-600"
|
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3 text-sm font-semibold text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
<PlusIcon className="h-5 w-5" />
|
<PlusIcon className="h-5 w-5" />
|
||||||
{t('subscriptions.browsePlans', 'Посмотреть тарифы и купить подписку')}
|
{t('subscriptions.browsePlans', 'Посмотреть тарифы и купить подписку')}
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ export default function TopUpAmount() {
|
|||||||
? 'cursor-not-allowed bg-dark-700 text-dark-500'
|
? 'cursor-not-allowed bg-dark-700 text-dark-500'
|
||||||
: isStarsMethod
|
: isStarsMethod
|
||||||
? 'bg-gradient-to-r from-yellow-500 to-orange-500 text-white shadow-lg shadow-yellow-500/25 hover:from-yellow-400 hover:to-orange-400 active:from-yellow-600 active:to-orange-600'
|
? 'bg-gradient-to-r from-yellow-500 to-orange-500 text-white shadow-lg shadow-yellow-500/25 hover:from-yellow-400 hover:to-orange-400 active:from-yellow-600 active:to-orange-600'
|
||||||
: 'bg-accent-500 text-white shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600'
|
: 'bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default function TopUpMethodSelect() {
|
|||||||
{translatedDesc || method.description}
|
{translatedDesc || method.description}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="mt-3 text-xs text-dark-600">
|
<div className="mt-3 text-xs text-dark-400">
|
||||||
{formatAmount(method.min_amount_kopeks / 100, 0)} –{' '}
|
{formatAmount(method.min_amount_kopeks / 100, 0)} –{' '}
|
||||||
{formatAmount(method.max_amount_kopeks / 100, 0)} {currencySymbol}
|
{formatAmount(method.max_amount_kopeks / 100, 0)} {currencySymbol}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function SuccessState({ amountKopeks }: { amountKopeks: number | null }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleGoToBalance}
|
onClick={handleGoToBalance}
|
||||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('balance.topUpResult.goToBalance')}
|
{t('balance.topUpResult.goToBalance')}
|
||||||
</button>
|
</button>
|
||||||
@@ -162,7 +162,7 @@ function TimeoutState({ onRetry, onGoBack }: { onRetry: () => void; onGoBack: ()
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onRetry}
|
onClick={onRetry}
|
||||||
className="w-full rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-white transition-colors hover:bg-accent-400"
|
className="w-full rounded-xl bg-accent-500 px-6 py-3 text-sm font-medium text-on-accent transition-colors hover:bg-accent-400"
|
||||||
>
|
>
|
||||||
{t('common.retry')}
|
{t('common.retry')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -632,7 +632,7 @@ export default function Wheel() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleDirectStarsPay}
|
onClick={handleDirectStarsPay}
|
||||||
className="rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-600"
|
className="rounded-lg bg-accent-500 px-4 py-2.5 text-sm font-medium text-on-accent transition-colors hover:bg-accent-600"
|
||||||
>
|
>
|
||||||
{t('wheel.payStars', { count: config.spin_cost_stars ?? 0 })}
|
{t('wheel.payStars', { count: config.spin_cost_stars ?? 0 })}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -342,6 +342,20 @@
|
|||||||
--color-dark-850: var(--color-champagne-100) !important;
|
--color-dark-850: var(--color-champagne-100) !important;
|
||||||
--color-dark-900: var(--color-champagne-100) !important;
|
--color-dark-900: var(--color-champagne-100) !important;
|
||||||
--color-dark-950: var(--color-champagne-200) !important;
|
--color-dark-950: var(--color-champagne-200) !important;
|
||||||
|
|
||||||
|
/* Статусные палитры генерируются под тёмную тему: текстовые оттенки
|
||||||
|
300/400 (зелёные суммы, жёлтая «Админка», бейджи) на светлом фоне
|
||||||
|
дают контраст 1.3–1.6. В светлой теме подменяем их тёмными шейдами —
|
||||||
|
зеркально ремапу серых выше. Фоновые употребления (bg-*-500/15 и т.п.)
|
||||||
|
используют 500+ и не затрагиваются. */
|
||||||
|
--color-accent-300: var(--color-accent-700) !important;
|
||||||
|
--color-accent-400: var(--color-accent-700) !important;
|
||||||
|
--color-success-300: var(--color-success-700) !important;
|
||||||
|
--color-success-400: var(--color-success-700) !important;
|
||||||
|
--color-warning-300: var(--color-warning-700) !important;
|
||||||
|
--color-warning-400: var(--color-warning-700) !important;
|
||||||
|
--color-error-300: var(--color-error-700) !important;
|
||||||
|
--color-error-400: var(--color-error-700) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom scrollbar - Dark */
|
/* Custom scrollbar - Dark */
|
||||||
@@ -578,7 +592,7 @@ img.twemoji {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
@apply btn bg-accent-500 text-white shadow-lg shadow-accent-500/25 hover:bg-accent-400 hover:shadow-xl hover:shadow-accent-500/30 focus-visible:ring-accent-500 active:bg-accent-600;
|
@apply btn bg-accent-500 text-on-accent shadow-lg shadow-accent-500/25 hover:bg-accent-400 hover:shadow-xl hover:shadow-accent-500/30 focus-visible:ring-accent-500 active:bg-accent-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import i18n from '../i18n';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format traffic amount with appropriate unit (MB/GB/TB).
|
* Format traffic amount with appropriate unit (MB/GB/TB).
|
||||||
* Matches the prototype's formatBytes logic.
|
* Units come from i18n so RU shows «ГБ» consistently with the rest of the UI
|
||||||
|
* (templates previously mixed latin "GB" with localized «ГБ» on one screen).
|
||||||
*/
|
*/
|
||||||
export function formatTraffic(gb: number): string {
|
export function formatTraffic(gb: number): string {
|
||||||
if (gb >= 1000) return `${(gb / 1000).toFixed(1)} TB`;
|
if (gb >= 1000) return `${(gb / 1000).toFixed(1)} ${i18n.t('common.units.tb', 'TB')}`;
|
||||||
if (gb >= 1) return `${gb.toFixed(1)} GB`;
|
if (gb >= 1) return `${gb.toFixed(1)} ${i18n.t('common.units.gb', 'GB')}`;
|
||||||
return `${(gb * 1024).toFixed(0)} MB`;
|
return `${(gb * 1024).toFixed(0)} ${i18n.t('common.units.mb', 'MB')}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ export default {
|
|||||||
900: withOpacity('--color-champagne-900'),
|
900: withOpacity('--color-champagne-900'),
|
||||||
950: withOpacity('--color-champagne-950'),
|
950: withOpacity('--color-champagne-950'),
|
||||||
},
|
},
|
||||||
|
// Readable text on top of status-colored fills (computed from the
|
||||||
|
// operator palette in useThemeColors — black or white, whichever reads)
|
||||||
|
'on-accent': withOpacity('--color-on-accent'),
|
||||||
|
'on-success': withOpacity('--color-on-success'),
|
||||||
|
'on-warning': withOpacity('--color-on-warning'),
|
||||||
|
'on-error': withOpacity('--color-on-error'),
|
||||||
// Accent - dynamic color scheme
|
// Accent - dynamic color scheme
|
||||||
accent: {
|
accent: {
|
||||||
50: withOpacity('--color-accent-50'),
|
50: withOpacity('--color-accent-50'),
|
||||||
|
|||||||
Reference in New Issue
Block a user