feat(auth): чекбоксы согласия с офертой и политикой при первой авторизации

Новый пользователь попадал в кабинет молча — никаких документов ему не показывали.
Теперь при создании аккаунта он подтверждает, что ознакомился с офертой и политикой.

Набор документов и сам факт включения гейта приходят с бэка
(GET /cabinet/info/legal-consent): там же учитывается, заполнены ли документы и
видны ли они в вебе. Фронт знает только ключи и адреса публичных страниц /offer
и /privacy — тех же, что уже линкует футер логина.

Два пути входа устроены по-разному, поэтому и UI разный:
- регистрация по email — чекбоксы прямо в форме, кнопка заблокирована до галочек;
- Telegram-вход происходит сам собой, спрашивать заранее не у чего. Поэтому ловим
  428 от бэка и показываем отдельный экран согласия, а замыкание помнит, какой
  именно вход повторить после простановки галочек. Тот же приём страхует и
  email-форму: конфиг мог протухнуть, если админ включил гейт между загрузкой
  страницы и отправкой.

Неизвестный ключ документа всё равно рисуем (без ссылки): бэк его требует, и молча
спрятанный чекбокс превратился бы в «кнопка не работает и непонятно почему».

Локали ru/en/zh/fa.
This commit is contained in:
Fringg
2026-07-30 00:31:47 +03:00
parent b615927283
commit 0f21629ba5
10 changed files with 319 additions and 16 deletions

View File

@@ -48,9 +48,12 @@ interface AuthState {
initialize: () => Promise<void>;
refreshUser: () => Promise<void>;
checkAdminStatus: () => Promise<void>;
loginWithTelegram: (initData: string) => Promise<void>;
loginWithTelegramWidget: (data: TelegramWidgetData) => Promise<void>;
loginWithTelegramOIDC: (idToken: string) => Promise<void>;
loginWithTelegram: (initData: string, acceptedLegalDocuments?: string[]) => Promise<void>;
loginWithTelegramWidget: (
data: TelegramWidgetData,
acceptedLegalDocuments?: string[],
) => Promise<void>;
loginWithTelegramOIDC: (idToken: string, acceptedLegalDocuments?: string[]) => Promise<void>;
loginWithEmail: (email: string, password: string) => Promise<void>;
loginWithOAuth: (
provider: string,
@@ -64,6 +67,7 @@ interface AuthState {
password: string,
firstName?: string,
referralCode?: string,
acceptedLegalDocuments?: string[],
) => Promise<RegisterResponse>;
}
@@ -261,10 +265,15 @@ export const useAuthStore = create<AuthState>()(
return initState.promise;
},
loginWithTelegram: async (initData) => {
loginWithTelegram: async (initData, acceptedLegalDocuments) => {
const campaignSlug = getPendingCampaignSlug();
const referralCode = getPendingReferralCode();
const response = await authApi.loginTelegram(initData, campaignSlug, referralCode);
const response = await authApi.loginTelegram(
initData,
campaignSlug,
referralCode,
acceptedLegalDocuments,
);
// Clear only after successful auth — retry keeps the slugs
consumeCampaignSlug();
consumeReferralCode();
@@ -279,10 +288,15 @@ export const useAuthStore = create<AuthState>()(
await get().checkAdminStatus();
},
loginWithTelegramWidget: async (data) => {
loginWithTelegramWidget: async (data, acceptedLegalDocuments) => {
const campaignSlug = getPendingCampaignSlug();
const referralCode = getPendingReferralCode();
const response = await authApi.loginTelegramWidget(data, campaignSlug, referralCode);
const response = await authApi.loginTelegramWidget(
data,
campaignSlug,
referralCode,
acceptedLegalDocuments,
);
consumeCampaignSlug();
consumeReferralCode();
tokenStorage.setTokens(response.access_token, response.refresh_token);
@@ -296,10 +310,15 @@ export const useAuthStore = create<AuthState>()(
await get().checkAdminStatus();
},
loginWithTelegramOIDC: async (idToken) => {
loginWithTelegramOIDC: async (idToken, acceptedLegalDocuments) => {
const campaignSlug = getPendingCampaignSlug();
const referralCode = getPendingReferralCode();
const response = await authApi.loginTelegramOIDC(idToken, campaignSlug, referralCode);
const response = await authApi.loginTelegramOIDC(
idToken,
campaignSlug,
referralCode,
acceptedLegalDocuments,
);
consumeCampaignSlug();
consumeReferralCode();
tokenStorage.setTokens(response.access_token, response.refresh_token);
@@ -370,7 +389,13 @@ export const useAuthStore = create<AuthState>()(
await get().checkAdminStatus();
},
registerWithEmail: async (email, password, firstName, referralCode) => {
registerWithEmail: async (
email,
password,
firstName,
referralCode,
acceptedLegalDocuments,
) => {
const code = referralCode || getPendingReferralCode() || undefined;
const campaignSlug = getPendingCampaignSlug() || undefined;
const response = await authApi.registerEmailStandalone({
@@ -380,6 +405,7 @@ export const useAuthStore = create<AuthState>()(
language: navigator.language.split('-')[0] || 'ru',
referral_code: code,
campaign_slug: campaignSlug,
accepted_legal_documents: acceptedLegalDocuments,
});
consumeReferralCode();
return response;