feat(cabinet): premium redesign of all 5 full-screen blocking states

The status screens (service-unavailable, maintenance, channel-subscription,
blacklist, account-deleted) all shared a generic flat look — an icon in a grey
circle, a title, a subtitle, three raw pulsing dots — that didn't match the
app's premium dark-glass aesthetic.

Introduce one shared BlockingShell that all five compose: an opaque themed
canvas with a self-contained accent glow, a centered glass card
(rounded-[--bento-radius] border bg-dark-900/80 backdrop-blur + inset
highlight), a gradient-ringed icon medallion (warning/error/info accent)
instead of a flat grey circle, font-display typography, the canonical Button
for every action, and a subtle framer-motion scale/slide entrance. Accent per
screen: warning (maintenance, account-deleted, service-unavailable), error
(blacklist), info (channel).

Behavior is preserved 1:1 — every i18n key, the channel list + open buttons,
the telegram deep-link, retry/check/close, the recovery poll, focus trap and
aria all carried over verbatim; only the visuals change.
This commit is contained in:
c0mrade
2026-06-04 13:38:35 +03:00
parent 7413837faf
commit 782568e091
6 changed files with 373 additions and 325 deletions

View File

@@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next';
import { useBlockingStore } from '../../store/blocking';
import { useFocusTrap } from '../../hooks/useFocusTrap';
import { WrenchIcon } from '@/components/icons';
import BlockingShell from './BlockingShell';
export default function MaintenanceScreen() {
const { t } = useTranslation();
@@ -9,58 +10,24 @@ export default function MaintenanceScreen() {
const screenRef = useFocusTrap<HTMLDivElement>(true, { lockScroll: false });
return (
<div
ref={screenRef}
role="alertdialog"
aria-modal="true"
aria-labelledby="maintenance-title"
tabIndex={-1}
className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6"
<BlockingShell
screenRef={screenRef}
titleId="maintenance-title"
accent="warning"
icon={<WrenchIcon className="h-9 w-9" />}
title={t('blocking.maintenance.title')}
description={maintenanceInfo?.message || t('blocking.maintenance.defaultMessage')}
pulse
footer={t('blocking.maintenance.waitMessage')}
>
<div className="w-full max-w-md text-center">
{/* Icon */}
<div className="mb-8">
<div className="mx-auto flex h-24 w-24 items-center justify-center rounded-full bg-dark-800">
<WrenchIcon className="h-12 w-12 text-warning-500" />
</div>
{maintenanceInfo?.reason && (
<div className="rounded-xl border border-dark-700/30 bg-dark-800/50 p-4">
<p className="mb-1 text-xs font-medium uppercase tracking-wide text-dark-500">
{t('blocking.maintenance.reason')}:
</p>
<p className="text-sm text-dark-300">{maintenanceInfo.reason}</p>
</div>
{/* Title */}
<h1 id="maintenance-title" className="mb-4 text-2xl font-bold text-white">
{t('blocking.maintenance.title')}
</h1>
{/* Message */}
<p className="mb-6 text-lg text-dark-400">
{maintenanceInfo?.message || t('blocking.maintenance.defaultMessage')}
</p>
{/* Reason */}
{maintenanceInfo?.reason && (
<div className="mb-6 rounded-xl bg-dark-800/50 p-4">
<p className="mb-1 text-sm text-dark-500">{t('blocking.maintenance.reason')}:</p>
<p className="text-dark-300">{maintenanceInfo.reason}</p>
</div>
)}
{/* Decorative dots */}
<div className="mt-8 flex items-center justify-center gap-2">
<div
className="h-2 w-2 animate-pulse rounded-full bg-warning-500"
style={{ animationDelay: '0ms' }}
/>
<div
className="h-2 w-2 animate-pulse rounded-full bg-warning-500"
style={{ animationDelay: '300ms' }}
/>
<div
className="h-2 w-2 animate-pulse rounded-full bg-warning-500"
style={{ animationDelay: '600ms' }}
/>
</div>
<p className="mt-4 text-sm text-dark-500">{t('blocking.maintenance.waitMessage')}</p>
</div>
</div>
)}
</BlockingShell>
);
}