mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43: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:
46
src/components/CampaignBonusNotifier.tsx
Normal file
46
src/components/CampaignBonusNotifier.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
import { useToast } from './Toast';
|
||||
|
||||
export default function CampaignBonusNotifier() {
|
||||
const { t } = useTranslation();
|
||||
const { showToast } = useToast();
|
||||
const bonus = useAuthStore((s) => s.pendingCampaignBonus);
|
||||
const clearBonus = useAuthStore((s) => s.clearCampaignBonus);
|
||||
|
||||
useEffect(() => {
|
||||
if (!bonus) return;
|
||||
|
||||
let message: string | null = null;
|
||||
if (bonus.bonus_type === 'balance' && bonus.balance_kopeks > 0) {
|
||||
message = t('campaignBonus.balance', {
|
||||
amount: (bonus.balance_kopeks / 100).toFixed(0),
|
||||
name: bonus.campaign_name,
|
||||
});
|
||||
} else if (bonus.bonus_type === 'subscription' && bonus.subscription_days) {
|
||||
message = t('campaignBonus.subscription', {
|
||||
days: bonus.subscription_days,
|
||||
name: bonus.campaign_name,
|
||||
});
|
||||
} else if (bonus.bonus_type === 'tariff' && bonus.tariff_name) {
|
||||
message = t('campaignBonus.tariff', {
|
||||
tariff: bonus.tariff_name,
|
||||
name: bonus.campaign_name,
|
||||
});
|
||||
}
|
||||
|
||||
if (message) {
|
||||
showToast({
|
||||
type: 'success',
|
||||
title: t('campaignBonus.title'),
|
||||
message,
|
||||
duration: 8000,
|
||||
});
|
||||
}
|
||||
|
||||
clearBonus();
|
||||
}, [bonus, clearBonus, showToast, t]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import { cn } from '@/lib/utils';
|
||||
import { UI } from '@/config/constants';
|
||||
|
||||
import WebSocketNotifications from '@/components/WebSocketNotifications';
|
||||
import CampaignBonusNotifier from '@/components/CampaignBonusNotifier';
|
||||
import SuccessNotificationModal from '@/components/SuccessNotificationModal';
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher';
|
||||
import TicketNotificationBell from '@/components/TicketNotificationBell';
|
||||
@@ -281,6 +282,7 @@ export function AppShell({ children }: AppShellProps) {
|
||||
|
||||
{/* Global components */}
|
||||
<WebSocketNotifications />
|
||||
<CampaignBonusNotifier />
|
||||
<SuccessNotificationModal />
|
||||
|
||||
{/* Desktop Header */}
|
||||
|
||||
Reference in New Issue
Block a user