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

@@ -235,7 +235,7 @@ export default function PromoOffersSection({ className = '' }: PromoOffersSectio
</button>
<button
onClick={handleDeactivateClick}
className="flex items-center justify-center gap-1.5 rounded-xl border border-dark-600/50 bg-dark-900/50 px-4 py-2.5 text-sm text-dark-400 transition-colors hover:border-red-500/30 hover:bg-red-500/10 hover:text-red-400"
className="flex items-center justify-center gap-1.5 rounded-xl border border-dark-600/50 bg-dark-900/50 px-4 py-2.5 text-sm text-dark-400 transition-colors hover:border-error-500/30 hover:bg-error-500/10 hover:text-error-400"
>
<XCircleIcon />
<span>{t('promo.deactivate.button')}</span>

View File

@@ -499,7 +499,7 @@ export default function TelegramLoginButton({ referralCode }: TelegramLoginButto
</>
) : deepLinkError ? (
<div className="flex flex-col items-center space-y-2">
<p className="text-xs text-red-500">{deepLinkError}</p>
<p className="text-xs text-error-500">{deepLinkError}</p>
<button
type="button"
onClick={startDeepLinkAuth}
@@ -542,7 +542,7 @@ export default function TelegramLoginButton({ referralCode }: TelegramLoginButto
</svg>
{oidcLoading ? t('common.loading') : t('auth.loginWithTelegram')}
</button>
{oidcError && <p className="text-xs text-red-500">{oidcError}</p>}
{oidcError && <p className="text-xs text-error-500">{oidcError}</p>}
</div>
) : (
<div ref={containerRef} className="flex justify-center" />

View File

@@ -73,7 +73,7 @@ export function AnalyticsTab() {
<div className="rounded-2xl border border-dark-700/50 bg-dark-800/50 p-6">
<div className="mb-1 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-yellow-500/20 to-red-500/20">
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-yellow-500/20 to-error-500/20">
<svg className="h-5 w-5 text-yellow-400" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" />
</svg>

View File

@@ -19,7 +19,7 @@ const STYLE_OPTIONS: { value: StyleValue; colorClass: string }[] = [
{ value: 'default', colorClass: 'bg-dark-500' },
{ value: 'primary', colorClass: 'bg-blue-500' },
{ value: 'success', colorClass: 'bg-success-500' },
{ value: 'danger', colorClass: 'bg-red-500' },
{ value: 'danger', colorClass: 'bg-error-500' },
];
function labelsEqual(a: Record<string, string>, b: Record<string, string>): boolean {
@@ -226,7 +226,7 @@ export function ButtonsTab() {
: cfg.style === 'success'
? 'bg-success-500 text-white'
: cfg.style === 'danger'
? 'bg-red-500 text-white'
? 'bg-error-500 text-white'
: 'bg-blue-500 text-white'
}`}
>

View File

@@ -292,11 +292,11 @@ export function ColoredItemCombobox({
type="button"
onClick={(e) => handleDelete(e, item)}
disabled={deletingId === item.id}
className="shrink-0 rounded p-1 text-dark-600 transition-colors hover:bg-red-500/10 hover:text-red-400 disabled:opacity-50"
className="shrink-0 rounded p-1 text-dark-600 transition-colors hover:bg-error-500/10 hover:text-error-400 disabled:opacity-50"
aria-label={t('news.admin.combobox.delete', { name: item.name })}
>
{deletingId === item.id ? (
<div className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-red-400 border-t-transparent" />
<div className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-error-400 border-t-transparent" />
) : (
<svg className="h-3.5 w-3.5" viewBox="0 0 24 24" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />

View File

@@ -234,7 +234,7 @@ function ButtonChip({
{!isBuiltin && (
<button
onClick={onRemove}
className="rounded-lg p-1 text-dark-500 transition-colors hover:bg-red-500/10 hover:text-red-400"
className="rounded-lg p-1 text-dark-500 transition-colors hover:bg-error-500/10 hover:text-error-400"
>
<TrashIcon />
</button>
@@ -423,7 +423,7 @@ function SortableRow({
{!allBuiltin && (
<button
onClick={() => onRemoveRow(row.id)}
className="rounded-lg p-1.5 text-dark-500 transition-colors hover:bg-red-500/10 hover:text-red-400"
className="rounded-lg p-1.5 text-dark-500 transition-colors hover:bg-error-500/10 hover:text-error-400"
>
<TrashIcon />
</button>
@@ -796,7 +796,7 @@ export function MenuEditorTab() {
if (isError) {
return (
<div className="rounded-xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-400">
<div className="rounded-xl border border-error-500/30 bg-error-500/10 px-4 py-3 text-sm text-error-400">
{t('common.error')}
</div>
);

View File

@@ -21,7 +21,7 @@ export default function BlacklistedScreen() {
<div className="mb-8">
<div className="mx-auto flex h-24 w-24 items-center justify-center rounded-full bg-dark-800">
<svg
className="h-12 w-12 text-red-500"
className="h-12 w-12 text-error-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"

View File

@@ -118,7 +118,7 @@ export default function ChannelSubscriptionScreen() {
{channels.map((ch) => (
<div
key={ch.channel_id}
className="flex items-center justify-between rounded-xl border border-red-500/30 bg-red-500/10 p-3"
className="flex items-center justify-between rounded-xl border border-error-500/30 bg-error-500/10 p-3"
>
<span className="text-sm font-medium text-white">{ch.title || ch.channel_id}</span>
{ch.channel_link && (
@@ -146,8 +146,8 @@ export default function ChannelSubscriptionScreen() {
{/* Error message */}
{error && (
<div className="mb-4 rounded-xl border border-red-500/30 bg-red-500/10 p-3">
<p className="text-sm text-red-400">{error}</p>
<div className="mb-4 rounded-xl border border-error-500/30 bg-error-500/10 p-3">
<p className="text-sm text-error-400">{error}</p>
</div>
)}

View File

@@ -323,7 +323,7 @@ export default function TvQuickConnect({ subscriptionUrl, isLight }: Props) {
{toast && (
<div
className={`fixed bottom-6 left-1/2 z-50 -translate-x-1/2 rounded-xl px-5 py-3 text-sm font-medium shadow-lg transition-all ${
toast.type === 'success' ? 'bg-emerald-500/90 text-white' : 'bg-red-500/90 text-white'
toast.type === 'success' ? 'bg-emerald-500/90 text-white' : 'bg-error-500/90 text-white'
}`}
>
{toast.text}

View File

@@ -48,7 +48,7 @@ export function AddonsTab({ params }: AddonsTabProps) {
}
if (isError || !data) {
return <div className="py-8 text-center text-red-400">{t('admin.salesStats.loadError')}</div>;
return <div className="py-8 text-center text-error-400">{t('admin.salesStats.loadError')}</div>;
}
const packageBarData = data.by_package.map((item) => ({

View File

@@ -68,7 +68,7 @@ export function DepositsTab({ params }: DepositsTabProps) {
}
if (isError || !data) {
return <div className="py-8 text-center text-red-400">{t('admin.salesStats.loadError')}</div>;
return <div className="py-8 text-center text-error-400">{t('admin.salesStats.loadError')}</div>;
}
return (

View File

@@ -35,7 +35,7 @@ export function RenewalsTab({ params }: RenewalsTabProps) {
}
if (isError || !data) {
return <div className="py-8 text-center text-red-400">{t('admin.salesStats.loadError')}</div>;
return <div className="py-8 text-center text-error-400">{t('admin.salesStats.loadError')}</div>;
}
const dailyData = data.daily.map((item) => ({

View File

@@ -45,7 +45,7 @@ export function SalesTab({ params }: SalesTabProps) {
}
if (isError || !data) {
return <div className="py-8 text-center text-red-400">{t('admin.salesStats.loadError')}</div>;
return <div className="py-8 text-center text-error-400">{t('admin.salesStats.loadError')}</div>;
}
const tariffBarData = data.by_tariff.map((item) => ({

View File

@@ -42,7 +42,7 @@ export function TrialsTab({ params }: TrialsTabProps) {
}
if (isError || !data) {
return <div className="py-8 text-center text-red-400">{t('admin.salesStats.loadError')}</div>;
return <div className="py-8 text-center text-error-400">{t('admin.salesStats.loadError')}</div>;
}
const pieData = data.by_provider.map((item) => ({

View File

@@ -45,7 +45,7 @@ function StatusBadge({
? 'bg-emerald-400/15 text-emerald-400 border-emerald-400/20'
: isLimited
? 'bg-amber-400/15 text-amber-400 border-amber-400/20'
: 'bg-red-400/15 text-red-400 border-red-400/20';
: 'bg-error-400/15 text-error-400 border-error-400/20';
const label = isActive
? t('subscription.statusActive', 'Активна')
@@ -96,7 +96,11 @@ export default function SubscriptionListCard({
? Math.min(100, (trafficUsed / trafficLimit) * 100)
: 0;
const trafficColor =
trafficPercent >= 90 ? 'bg-red-400' : trafficPercent >= 70 ? 'bg-amber-400' : 'bg-emerald-400';
trafficPercent >= 90
? 'bg-error-400'
: trafficPercent >= 70
? 'bg-amber-400'
: 'bg-emerald-400';
const isLimitedStatus = subscription.status === 'limited';
@@ -207,7 +211,7 @@ export default function SubscriptionListCard({
: t('subscription.autopay', 'Автопродление');
return (
<span
className={`flex items-center gap-1 ${enabled ? 'text-emerald-400/70' : 'text-red-400/50'}`}
className={`flex items-center gap-1 ${enabled ? 'text-emerald-400/70' : 'text-error-400/50'}`}
>
<svg
className="h-3 w-3"