refactor(theming): replace red-* palette with semantic error-* tokens

Sweep 30 files: text/bg/border/ring/from/to/via/fill/stroke/shadow/divide/decoration/
outline/placeholder-red-N → -error-N. All red usages were semantically error/danger
(no brand red), so tokens now flow through the design-system CSS variables and respond
to palette overrides.
This commit is contained in:
c0mrade
2026-05-26 11:08:22 +03:00
parent e1b48c1ccb
commit 9c5e38b594
30 changed files with 66 additions and 60 deletions

View File

@@ -189,7 +189,7 @@ interface StatusBadgeProps {
function StatusBadge({ status, label }: StatusBadgeProps) {
const colorMap: Record<string, string> = {
success: 'bg-success-500/20 text-success-400',
denied: 'bg-red-500/20 text-red-400',
denied: 'bg-error-500/20 text-error-400',
error: 'bg-amber-500/20 text-amber-400',
};
@@ -212,7 +212,7 @@ function MethodBadge({ method }: MethodBadgeProps) {
POST: 'bg-success-500/20 text-success-400',
PUT: 'bg-amber-500/20 text-amber-400',
PATCH: 'bg-amber-500/20 text-amber-400',
DELETE: 'bg-red-500/20 text-red-400',
DELETE: 'bg-error-500/20 text-error-400',
};
return (

View File

@@ -50,7 +50,7 @@ function StatusBadge({ status }: StatusBadgeProps) {
const styles: Record<string, string> = {
paid: 'bg-green-500/20 text-green-400',
pending: 'bg-amber-500/20 text-amber-400',
cancelled: 'bg-red-500/20 text-red-400',
cancelled: 'bg-error-500/20 text-error-400',
};
const normalized = status.toLowerCase();
@@ -78,7 +78,7 @@ function StatCard({ label, value, color, isActive, onClick }: StatCardProps) {
blue: 'border-accent-500/30 bg-accent-500/20 text-accent-400',
amber: 'border-amber-500/30 bg-amber-500/20 text-amber-400',
green: 'border-green-500/30 bg-green-500/20 text-green-400',
red: 'border-red-500/30 bg-red-500/20 text-red-400',
red: 'border-error-500/30 bg-error-500/20 text-error-400',
};
return (
@@ -597,7 +597,7 @@ export default function AdminPayments() {
{checkPaymentMutation.isError &&
checkPaymentMutation.variables?.paymentId === payment.id &&
checkPaymentMutation.variables?.method === payment.method && (
<div className="mt-3 rounded-lg border border-red-500/30 bg-red-500/10 p-2 text-sm text-red-400">
<div className="mt-3 rounded-lg border border-error-500/30 bg-error-500/10 p-2 text-sm text-error-400">
{t('admin.payments.checkError')}
</div>
)}

View File

@@ -141,7 +141,7 @@ function EffectBadge({ effect, className }: EffectBadgeProps) {
className={`inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-semibold ${
isAllow
? 'border-success-500/30 bg-success-500/10 text-success-400'
: 'border-red-500/30 bg-red-500/10 text-red-400'
: 'border-error-500/30 bg-error-500/10 text-error-400'
} ${className ?? ''}`}
>
{isAllow ? t('admin.policies.effectAllow') : t('admin.policies.effectDeny')}
@@ -331,7 +331,7 @@ export default function AdminPolicies() {
<div className="text-xs text-dark-400">{t('admin.policies.stats.allow')}</div>
</div>
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4">
<div className="text-2xl font-bold text-red-400">
<div className="text-2xl font-bold text-error-400">
{sortedPolicies.filter((p) => p.effect === 'deny').length}
</div>
<div className="text-xs text-dark-400">{t('admin.policies.stats.deny')}</div>

View File

@@ -455,7 +455,7 @@ export default function AdminPolicyEdit() {
onClick={() => setFormData((prev) => ({ ...prev, effect: 'deny' }))}
className={`flex-1 rounded-lg border px-3 py-2 text-sm font-medium transition-colors ${
formData.effect === 'deny'
? 'border-red-500/50 bg-red-500/10 text-red-400'
? 'border-error-500/50 bg-error-500/10 text-error-400'
: 'border-dark-600 bg-dark-900 text-dark-400 hover:border-dark-500'
}`}
>

View File

@@ -595,14 +595,14 @@ export default function AdminTickets() {
</div>
)}
{att.error && (
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-red-500/30">
<span className="text-xs text-red-300">!</span>
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-error-500/30">
<span className="text-xs text-error-300">!</span>
</div>
)}
<button
type="button"
onClick={() => removeAttachment(idx)}
className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-dark-600 text-dark-300 hover:bg-red-500 hover:text-white"
className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-dark-600 text-dark-300 hover:bg-error-500 hover:text-white"
>
<svg
className="h-3 w-3"
@@ -633,7 +633,7 @@ export default function AdminTickets() {
/>
{replyError && (
<div className="mt-2 rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-300">
<div className="mt-2 rounded-lg border border-error-500/30 bg-error-500/10 px-3 py-2 text-sm text-error-300">
{replyError}
</div>
)}

View File

@@ -83,8 +83,8 @@ export default function Contests() {
if (error) {
return (
<div className="card border-red-500/20 bg-red-500/10">
<p className="text-red-400">{t('contests.error')}</p>
<div className="card border-error-500/20 bg-error-500/10">
<p className="text-error-400">{t('contests.error')}</p>
</div>
);
}
@@ -125,7 +125,7 @@ export default function Contests() {
{result && (
<div
className={`mb-4 rounded-lg p-4 ${result.is_winner ? 'bg-success-500/20 text-success-400' : 'bg-red-500/20 text-red-400'}`}
className={`mb-4 rounded-lg p-4 ${result.is_winner ? 'bg-success-500/20 text-success-400' : 'bg-error-500/20 text-error-400'}`}
>
<p className="font-medium">{result.message}</p>
</div>

View File

@@ -122,8 +122,8 @@ export default function Polls() {
if (error) {
return (
<div className="card border-red-500/20 bg-red-500/10">
<p className="text-red-400">{t('polls.error')}</p>
<div className="card border-error-500/20 bg-error-500/10">
<p className="text-error-400">{t('polls.error')}</p>
</div>
);
}

View File

@@ -600,9 +600,9 @@ function SummaryCard({
const el = document.getElementById('contact-input');
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('!border-red-500', '!ring-2', '!ring-red-500/50');
el.classList.add('!border-error-500', '!ring-2', '!ring-error-500/50');
setTimeout(() => {
el.classList.remove('!border-red-500', '!ring-2', '!ring-red-500/50');
el.classList.remove('!border-error-500', '!ring-2', '!ring-error-500/50');
}, 2000);
}
return;
@@ -643,12 +643,12 @@ function SummaryCard({
const el = document.getElementById('contact-input');
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('!border-red-500', '!ring-2', '!ring-red-500/50');
el.classList.add('!border-error-500', '!ring-2', '!ring-error-500/50');
setTimeout(() => {
el.focus();
}, 300);
setTimeout(() => {
el.classList.remove('!border-red-500', '!ring-2', '!ring-red-500/50');
el.classList.remove('!border-error-500', '!ring-2', '!ring-error-500/50');
}, 2000);
}
return;

View File

@@ -195,7 +195,7 @@ export default function RenewSubscription() {
</div>
</div>
{!canAfford && (
<div className="mt-1 text-[11px] text-red-400">
<div className="mt-1 text-[11px] text-error-400">
{t(
'subscription.insufficientBalanceAmount',
'Недостаточно средств. Не хватает {{missing}}',
@@ -216,7 +216,9 @@ export default function RenewSubscription() {
{/* Error */}
{error && !missingAmount && (
<div className="rounded-xl bg-red-400/10 p-3 text-center text-sm text-red-400">{error}</div>
<div className="rounded-xl bg-error-400/10 p-3 text-center text-sm text-error-400">
{error}
</div>
)}
{/* Renew button */}

View File

@@ -1428,7 +1428,7 @@ export default function Subscription() {
}
}}
disabled={deleteLoading}
className="flex w-full items-center justify-center gap-2 rounded-2xl border border-red-400/20 bg-red-400/5 p-3.5 text-sm font-medium text-red-400 transition-colors hover:bg-red-400/10 disabled:opacity-50"
className="flex w-full items-center justify-center gap-2 rounded-2xl border border-error-400/20 bg-error-400/5 p-3.5 text-sm font-medium text-error-400 transition-colors hover:bg-error-400/10 disabled:opacity-50"
>
<svg
className="h-4 w-4"
@@ -1447,10 +1447,10 @@ export default function Subscription() {
</button>
) : (
<div
className="rounded-2xl border border-red-400/20 p-4"
className="rounded-2xl border border-error-400/20 p-4"
style={{ background: 'rgba(255,59,92,0.04)' }}
>
<div className="mb-3 text-sm font-semibold text-red-400">
<div className="mb-3 text-sm font-semibold text-error-400">
{t('subscription.deleteTitle', 'Удалить подписку?')}
</div>
<div className="mb-4 text-xs" style={{ color: g.textSecondary }}>
@@ -1473,7 +1473,7 @@ export default function Subscription() {
}
}}
disabled={deleteLoading}
className="flex-1 rounded-xl bg-red-500 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-red-600 disabled:opacity-50"
className="flex-1 rounded-xl bg-error-500 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-error-600 disabled:opacity-50"
>
{deleteLoading
? t('common.processing', 'Удаление...')
@@ -2382,7 +2382,7 @@ export default function Subscription() {
</div>
</button>
{revokeMutation.error && (
<p className="mt-2 text-sm text-red-400">{getErrorMessage(revokeMutation.error)}</p>
<p className="mt-2 text-sm text-error-400">{getErrorMessage(revokeMutation.error)}</p>
)}
</div>
)}

View File

@@ -350,14 +350,14 @@ export default function Support() {
</div>
)}
{att.error && (
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-red-500/30">
<span className="text-xs text-red-300">!</span>
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-error-500/30">
<span className="text-xs text-error-300">!</span>
</div>
)}
<button
type="button"
onClick={() => onRemove(idx)}
className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-dark-600 text-dark-300 hover:bg-red-500 hover:text-white"
className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full bg-dark-600 text-dark-300 hover:bg-error-500 hover:text-white"
>
<CloseIcon />
</button>

View File

@@ -66,7 +66,7 @@ export default function TelegramCallback() {
return (
<div className="min-h-viewport flex items-center justify-center bg-gray-50 px-4 py-8">
<div className="w-full max-w-md text-center">
<div className="mb-4 text-5xl text-red-500"></div>
<div className="mb-4 text-5xl text-error-500"></div>
<h2 className="mb-2 text-lg font-semibold text-gray-900">{t('auth.loginFailed')}</h2>
<p className="mb-6 text-sm text-gray-500">{error}</p>
<button onClick={() => navigate('/login')} className="btn-primary">

View File

@@ -102,7 +102,7 @@ export default function VerifyEmail() {
{status === 'error' && (
<div>
<div className="mb-4 text-5xl text-red-500 sm:text-6xl"></div>
<div className="mb-4 text-5xl text-error-500 sm:text-6xl"></div>
<h2 className="text-lg font-semibold text-gray-900 sm:text-xl">
{t('emailVerification.failed')}
</h2>

View File

@@ -487,7 +487,7 @@ export default function Wheel() {
if (error || !config) {
return (
<div className="flex min-h-[60vh] flex-col items-center justify-center gap-4">
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-red-500/10">
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-error-500/10">
<span className="text-4xl">😔</span>
</div>
<p className="text-lg text-dark-400">{t('wheel.errors.loadFailed')}</p>
@@ -709,7 +709,7 @@ export default function Wheel() {
className={`animate-fade-in rounded-linear border p-4 ${
spinResult.success
? 'border-accent-500/30 bg-accent-500/10'
: 'border-red-500/30 bg-red-500/10'
: 'border-error-500/30 bg-error-500/10'
}`}
>
<div className="flex items-center gap-3">