mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix: referral system — stop cabinet redirect to Telegram, fix deep link code handling
- Remove redirect to Telegram bot when email auth disabled + referral code present (cabinet links should stay on cabinet, bot links should go to bot) - Remove referral_code from deep link auth (existing users can't get referrals) - Don't consume referralCode in deep link path — leave it in localStorage for OIDC/widget auth methods that actually send it to the backend - Consume campaign slug once into ref to survive retries (codesConsumedRef pattern) - Update loginWithDeepLink to only accept (token, campaignSlug) — no referralCode - Update pollDeepLinkToken API to match backend schema change
This commit is contained in:
@@ -290,7 +290,7 @@ export const authApi = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
pollDeepLinkToken: async (token: string): Promise<AuthResponse> => {
|
||||
pollDeepLinkToken: async (token: string, campaignSlug?: string | null): Promise<AuthResponse> => {
|
||||
// validateStatus: only treat 200 as success.
|
||||
// Server returns 202 for "pending" and 410 for "expired" —
|
||||
// these must reject so the polling catch-block can handle them.
|
||||
@@ -299,7 +299,7 @@ export const authApi = {
|
||||
// which triggers checkAdminStatus() → 401 → safeRedirectToLogin() → infinite reload.
|
||||
const response = await apiClient.post<AuthResponse>(
|
||||
'/cabinet/auth/deeplink/poll',
|
||||
{ token },
|
||||
{ token, campaign_slug: campaignSlug || undefined },
|
||||
{ validateStatus: (status) => status === 200 },
|
||||
);
|
||||
return response.data;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { brandingApi, type TelegramWidgetConfig } from '../api/branding';
|
||||
import { authApi } from '../api/auth';
|
||||
import { useAuthStore } from '../store/auth';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { consumeCampaignSlug } from '../utils/campaign';
|
||||
|
||||
interface TelegramLoginButtonProps {
|
||||
referralCode?: string;
|
||||
@@ -34,6 +35,10 @@ export default function TelegramLoginButton({ referralCode }: TelegramLoginButto
|
||||
|
||||
const loginWithDeepLink = useAuthStore((s) => s.loginWithDeepLink);
|
||||
|
||||
// Capture campaign slug once on mount (before any retry clears it)
|
||||
const capturedCampaignRef = useRef<string | null>(null);
|
||||
const codesConsumedRef = useRef(false);
|
||||
|
||||
const { data: widgetConfig } = useQuery<TelegramWidgetConfig>({
|
||||
queryKey: ['telegram-widget-config'],
|
||||
queryFn: brandingApi.getTelegramWidgetConfig,
|
||||
@@ -239,6 +244,17 @@ export default function TelegramLoginButton({ referralCode }: TelegramLoginButto
|
||||
}
|
||||
|
||||
try {
|
||||
// Consume campaign slug ONCE (first call only).
|
||||
// Clears localStorage on first call, so subsequent retries reuse the ref.
|
||||
// Note: referral code is NOT consumed here — deep link auth is for existing
|
||||
// bot users where referrals don't apply. Leaving it in localStorage allows
|
||||
// other auth methods (OIDC, widget) to pick it up if the user switches paths.
|
||||
if (!codesConsumedRef.current) {
|
||||
capturedCampaignRef.current = consumeCampaignSlug();
|
||||
codesConsumedRef.current = true;
|
||||
}
|
||||
const capturedCampaign = capturedCampaignRef.current;
|
||||
|
||||
const response = await authApi.requestDeepLinkToken();
|
||||
const { token, bot_username, expires_in } = response;
|
||||
setDeepLinkToken(token);
|
||||
@@ -249,7 +265,8 @@ export default function TelegramLoginButton({ referralCode }: TelegramLoginButto
|
||||
const poll = async () => {
|
||||
if (!mountedRef.current) return;
|
||||
try {
|
||||
await loginWithDeepLink(token);
|
||||
// Deep link auth is for existing bot users — only campaign_slug applies
|
||||
await loginWithDeepLink(token, capturedCampaign);
|
||||
// Success — auth store is updated, navigate
|
||||
if (expireTimeoutRef.current) {
|
||||
clearTimeout(expireTimeoutRef.current);
|
||||
|
||||
@@ -22,7 +22,7 @@ import LanguageSwitcher from '../components/LanguageSwitcher';
|
||||
import TelegramLoginButton from '../components/TelegramLoginButton';
|
||||
import OAuthProviderIcon from '../components/OAuthProviderIcon';
|
||||
import { saveOAuthState } from '../utils/oauth';
|
||||
import { consumeReferralCode, getPendingReferralCode } from '../utils/referral';
|
||||
import { getPendingReferralCode } from '../utils/referral';
|
||||
|
||||
export default function Login() {
|
||||
const { t } = useTranslation();
|
||||
@@ -146,16 +146,6 @@ export default function Login() {
|
||||
}
|
||||
};
|
||||
|
||||
const botUsername = import.meta.env.VITE_TELEGRAM_BOT_USERNAME || '';
|
||||
|
||||
// If email auth is disabled but user came with ref param, redirect to bot
|
||||
useEffect(() => {
|
||||
if (referralCode && emailAuthConfig?.enabled === false && botUsername) {
|
||||
consumeReferralCode();
|
||||
window.location.href = `https://t.me/${botUsername}?start=${encodeURIComponent(referralCode)}`;
|
||||
}
|
||||
}, [referralCode, emailAuthConfig, botUsername]);
|
||||
|
||||
const appName = branding ? branding.name : import.meta.env.VITE_APP_NAME || 'VPN';
|
||||
const appLogo = branding?.logo_letter || import.meta.env.VITE_APP_LOGO || 'V';
|
||||
const logoUrl = branding ? brandingApi.getLogoUrl(branding) : null;
|
||||
|
||||
@@ -45,7 +45,7 @@ interface AuthState {
|
||||
state: string,
|
||||
deviceId?: string | null,
|
||||
) => Promise<void>;
|
||||
loginWithDeepLink: (token: string) => Promise<void>;
|
||||
loginWithDeepLink: (token: string, campaignSlug?: string | null) => Promise<void>;
|
||||
registerWithEmail: (
|
||||
email: string,
|
||||
password: string,
|
||||
@@ -322,8 +322,8 @@ export const useAuthStore = create<AuthState>()(
|
||||
await get().checkAdminStatus();
|
||||
},
|
||||
|
||||
loginWithDeepLink: async (token) => {
|
||||
const response = await authApi.pollDeepLinkToken(token);
|
||||
loginWithDeepLink: async (token, campaignSlug) => {
|
||||
const response = await authApi.pollDeepLinkToken(token, campaignSlug);
|
||||
if (!response.access_token || !response.refresh_token) {
|
||||
throw new Error('Invalid auth response: missing tokens');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user