mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
feat(auth,a11y): Telegram CloudStorage token recovery + blocking-screen a11y
Telegram CloudStorage token recovery (resilience against WebView localStorage wipes): - mirror the refresh token to per-user CloudStorage on login, remove it on logout - restoreRefreshTokenFromCloud() recovers the refresh token in initialize() when localStorage is empty, then the normal refresh flow re-establishes the session - move the initialize() bootstrap call from auth.ts module-load into main.tsx after the Telegram SDK init(), since launch params + CloudStorage are unavailable before init - best-effort: no-ops outside Telegram and on any error -> falls back to prior behavior Accessibility: - role=alertdialog + aria-modal + aria-labelledby + focus management (useFocusTrap) on Maintenance, Blacklisted, ChannelSubscription and AccountDeleted blocking screens - aria-label on ColorPicker Hue/Saturation/Lightness range inputs
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePlatform } from '@/platform';
|
||||
import { useBlockingStore } from '../../store/blocking';
|
||||
import { useFocusTrap } from '../../hooks/useFocusTrap';
|
||||
|
||||
/**
|
||||
* Full-screen block shown when the backend returns
|
||||
@@ -21,6 +22,7 @@ export default function AccountDeletedScreen() {
|
||||
const { t } = useTranslation();
|
||||
const { openTelegramLink } = usePlatform();
|
||||
const info = useBlockingStore((state) => state.accountDeletedInfo);
|
||||
const screenRef = useFocusTrap<HTMLDivElement>(true, { lockScroll: false });
|
||||
|
||||
const deepLink = info?.telegram_deep_link?.trim() || null;
|
||||
// Route through the platform adapter, not raw window.open. Inside the
|
||||
@@ -42,7 +44,14 @@ export default function AccountDeletedScreen() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6">
|
||||
<div
|
||||
ref={screenRef}
|
||||
role="alertdialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="account-deleted-title"
|
||||
tabIndex={-1}
|
||||
className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6"
|
||||
>
|
||||
<div className="w-full max-w-md text-center">
|
||||
<div className="mb-8">
|
||||
<div className="mx-auto flex h-24 w-24 items-center justify-center rounded-full bg-dark-800">
|
||||
@@ -63,7 +72,9 @@ export default function AccountDeletedScreen() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="mb-4 text-2xl font-bold text-white">{t('blocking.accountDeleted.title')}</h1>
|
||||
<h1 id="account-deleted-title" className="mb-4 text-2xl font-bold text-white">
|
||||
{t('blocking.accountDeleted.title')}
|
||||
</h1>
|
||||
|
||||
<p className="mb-6 text-lg text-gray-400">{t('blocking.accountDeleted.description')}</p>
|
||||
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBlockingStore } from '../../store/blocking';
|
||||
import { useFocusTrap } from '../../hooks/useFocusTrap';
|
||||
|
||||
export default function BlacklistedScreen() {
|
||||
const { t } = useTranslation();
|
||||
const blacklistedInfo = useBlockingStore((state) => state.blacklistedInfo);
|
||||
const screenRef = useFocusTrap<HTMLDivElement>(true, { lockScroll: false });
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6">
|
||||
<div
|
||||
ref={screenRef}
|
||||
role="alertdialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="blacklisted-title"
|
||||
tabIndex={-1}
|
||||
className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6"
|
||||
>
|
||||
<div className="w-full max-w-md text-center">
|
||||
{/* Icon */}
|
||||
<div className="mb-8">
|
||||
@@ -28,7 +37,9 @@ export default function BlacklistedScreen() {
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h1 className="mb-4 text-2xl font-bold text-white">{t('blocking.blacklisted.title')}</h1>
|
||||
<h1 id="blacklisted-title" className="mb-4 text-2xl font-bold text-white">
|
||||
{t('blocking.blacklisted.title')}
|
||||
</h1>
|
||||
|
||||
{/* Message */}
|
||||
<p className="mb-6 text-lg text-gray-400">{t('blocking.blacklisted.defaultMessage')}</p>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useBlockingStore } from '../../store/blocking';
|
||||
import { apiClient, isChannelSubscriptionError } from '../../api/client';
|
||||
import { usePlatform } from '../../platform';
|
||||
import { useFocusTrap } from '../../hooks/useFocusTrap';
|
||||
|
||||
const CHECK_COOLDOWN_SECONDS = 5;
|
||||
|
||||
@@ -80,8 +81,17 @@ export default function ChannelSubscriptionScreen() {
|
||||
}
|
||||
}, [clearBlocking, t]);
|
||||
|
||||
const screenRef = useFocusTrap<HTMLDivElement>(true, { lockScroll: false });
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6">
|
||||
<div
|
||||
ref={screenRef}
|
||||
role="alertdialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="channel-sub-title"
|
||||
tabIndex={-1}
|
||||
className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6"
|
||||
>
|
||||
<div className="w-full max-w-md text-center">
|
||||
{/* Icon */}
|
||||
<div className="mb-8">
|
||||
@@ -93,7 +103,9 @@ export default function ChannelSubscriptionScreen() {
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h1 className="mb-4 text-2xl font-bold text-white">{t('blocking.channel.title')}</h1>
|
||||
<h1 id="channel-sub-title" className="mb-4 text-2xl font-bold text-white">
|
||||
{t('blocking.channel.title')}
|
||||
</h1>
|
||||
|
||||
{/* Message */}
|
||||
<p className="mb-6 text-lg text-gray-400">
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useBlockingStore } from '../../store/blocking';
|
||||
import { useFocusTrap } from '../../hooks/useFocusTrap';
|
||||
|
||||
export default function MaintenanceScreen() {
|
||||
const { t } = useTranslation();
|
||||
const maintenanceInfo = useBlockingStore((state) => state.maintenanceInfo);
|
||||
const screenRef = useFocusTrap<HTMLDivElement>(true, { lockScroll: false });
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex flex-col items-center justify-center bg-dark-950 p-6">
|
||||
<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"
|
||||
>
|
||||
<div className="w-full max-w-md text-center">
|
||||
{/* Icon */}
|
||||
<div className="mb-8">
|
||||
@@ -28,7 +37,9 @@ export default function MaintenanceScreen() {
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h1 className="mb-4 text-2xl font-bold text-white">{t('blocking.maintenance.title')}</h1>
|
||||
<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-gray-400">
|
||||
|
||||
Reference in New Issue
Block a user