mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
feat(blocking): account-deleted recovery screen with bot deep-link
Pairs with backend feat(deleted-users) — when the cabinet returns
403 {detail: {code: 'account_deleted', message, bot_username,
telegram_deep_link}} the SPA shows a dedicated screen instead of the
generic auth-error toast.
* Adds AccountDeletedScreen with localized title/description, an
Open-bot button using the backend-provided telegram_deep_link, and a
Retry button that clears the block + reloads (so the next request
observes the now-revived row).
* useBlockingStore gains accountDeleted slice mirroring the existing
maintenance/channel-subscription/blacklisted slots — consistent with
the precedent for cross-cutting 403 codes.
* apiClient: AccountDeletedError type + isAccountDeletedError guard +
response interceptor routes the 403 into the blocking store.
* i18n: ru/en/zh/fa parity for title, description, openBot, retry, hint.
No behavior change for ACTIVE users. BLOCKED users still get the
generic 403 (the backend keeps the message opaque for admin actions).
This commit is contained in:
@@ -152,6 +152,13 @@ export interface BlacklistedError {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AccountDeletedError {
|
||||
code: 'account_deleted';
|
||||
message: string;
|
||||
bot_username?: string;
|
||||
telegram_deep_link?: string;
|
||||
}
|
||||
|
||||
export function isMaintenanceError(
|
||||
error: unknown,
|
||||
): error is { response: { status: 503; data: { detail: MaintenanceError } } } {
|
||||
@@ -179,6 +186,14 @@ export function isBlacklistedError(
|
||||
return err.response?.status === 403 && err.response?.data?.detail?.code === 'blacklisted';
|
||||
}
|
||||
|
||||
export function isAccountDeletedError(
|
||||
error: unknown,
|
||||
): error is { response: { status: 403; data: { detail: AccountDeletedError } } } {
|
||||
if (!error || typeof error !== 'object') return false;
|
||||
const err = error as AxiosError<{ detail: AccountDeletedError }>;
|
||||
return err.response?.status === 403 && err.response?.data?.detail?.code === 'account_deleted';
|
||||
}
|
||||
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
@@ -211,6 +226,21 @@ apiClient.interceptors.response.use(
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (isAccountDeletedError(error)) {
|
||||
const detail = (error.response?.data as { detail: AccountDeletedError }).detail;
|
||||
// Surface the deleted-account screen. The auth flow (initData login)
|
||||
// is allowed to auto-revive; this branch is for token-bearing
|
||||
// sessions where the user is already in the cabinet but their row
|
||||
// got marked DELETED out-of-band, and for password-only logins
|
||||
// that can't be silently revived.
|
||||
useBlockingStore.getState().setAccountDeleted({
|
||||
message: detail.message,
|
||||
bot_username: detail.bot_username,
|
||||
telegram_deep_link: detail.telegram_deep_link,
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
const requestUrl = originalRequest.url || '';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user