mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
feat: add web campaign links — capture, auth integration, bonus UI
- Add campaign.ts utility (capture from URL, localStorage with TTL, validation) - Pass campaign_slug in all auth API methods (telegram, widget, email, oauth) - Consume slug in auth store login methods, set pendingCampaignBonus state - Add CampaignBonusNotifier component (toast on bonus, mounted in AppShell) - Show web_link alongside bot deep_link in AdminCampaignStats - Add CampaignBonusInfo type with union bonus_type - Fix VerifyEmail: ref guard against StrictMode double-fire, setTimeout cleanup - Fix AdminCampaignStats: setTimeout refs with cleanup, i18n-aware locale - Wrap all localStorage access in try/catch (Safari private browsing safety) - Add campaignBonus translations (ru/en/fa/zh)
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useSearchParams, Link, useNavigate } from 'react-router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { authApi } from '../api/auth';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
import { consumeCampaignSlug } from '../utils/campaign';
|
||||
import { tokenStorage } from '../utils/token';
|
||||
import LanguageSwitcher from '../components/LanguageSwitcher';
|
||||
|
||||
@@ -13,8 +14,11 @@ export default function VerifyEmail() {
|
||||
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
|
||||
const [error, setError] = useState('');
|
||||
const { setTokens, setUser, checkAdminStatus } = useAuthStore();
|
||||
const hasVerified = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasVerified.current) return;
|
||||
|
||||
const token = searchParams.get('token');
|
||||
|
||||
if (!token) {
|
||||
@@ -23,17 +27,24 @@ export default function VerifyEmail() {
|
||||
return;
|
||||
}
|
||||
|
||||
hasVerified.current = true;
|
||||
let redirectTimer: ReturnType<typeof setTimeout>;
|
||||
|
||||
const verify = async () => {
|
||||
try {
|
||||
const response = await authApi.verifyEmail(token);
|
||||
const campaignSlug = consumeCampaignSlug();
|
||||
const response = await authApi.verifyEmail(token, campaignSlug);
|
||||
// Save tokens and log user in
|
||||
tokenStorage.setTokens(response.access_token, response.refresh_token);
|
||||
setTokens(response.access_token, response.refresh_token);
|
||||
setUser(response.user);
|
||||
if (response.campaign_bonus) {
|
||||
useAuthStore.setState({ pendingCampaignBonus: response.campaign_bonus });
|
||||
}
|
||||
checkAdminStatus();
|
||||
setStatus('success');
|
||||
// Redirect to dashboard after short delay
|
||||
setTimeout(() => navigate('/', { replace: true }), 1500);
|
||||
redirectTimer = setTimeout(() => navigate('/', { replace: true }), 1500);
|
||||
} catch (err: unknown) {
|
||||
setStatus('error');
|
||||
const error = err as { response?: { data?: { detail?: string } } };
|
||||
@@ -42,6 +53,8 @@ export default function VerifyEmail() {
|
||||
};
|
||||
|
||||
verify();
|
||||
|
||||
return () => clearTimeout(redirectTimer);
|
||||
}, [searchParams, t, navigate, setTokens, setUser, checkAdminStatus]);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user