Commit a15facb5 by Arjun Jhukal

updated the new functionality for acuity tech and removed age gate

parent ad8df29f
{
"postman.settings.dotenv-detection-notification-visibility": false
}
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
"use client" "use client"
import GlassWrapper from '@/components/molecules/GlassWrapper' import GlassWrapper from '@/components/molecules/GlassWrapper'
import { useAppDispatch } from '@/hooks/hook' import { useAppDispatch, useAppSelector } from '@/hooks/hook'
import { setTokens } from '@/slice/authSlice' import { setTokens } from '@/slice/authSlice'
import { restoreAuthFromCookies } from '@/utils/authSession' import { clearAuthBackup, restoreAuthFromCookies } from '@/utils/authSession'
import Image from 'next/image' import Image from 'next/image'
import Link from 'next/link' import Link from 'next/link'
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
...@@ -15,32 +15,68 @@ export default function PaymentSuccess() { ...@@ -15,32 +15,68 @@ export default function PaymentSuccess() {
const params = useParams(); const params = useParams();
const slug = params?.slug as string; const slug = params?.slug as string;
const [isReady, setIsReady] = useState(false); const [isReady, setIsReady] = useState(false);
const [error, setError] = useState<string | null>(null);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const user = useAppSelector((state) => state.auth.user);
useEffect(() => { useEffect(() => {
// Restore auth from cookies to localStorage const hydrateAuthOnSuccess = async () => {
const wasRestored = restoreAuthFromCookies(); try {
console.log('[PaymentSuccess] Starting auth hydration...');
if (wasRestored) { // Try to restore auth from backup
// Manually hydrate Redux from localStorage (no reload needed) const result = restoreAuthFromCookies();
const userStr = localStorage.getItem('user');
const token = localStorage.getItem('access_token');
if (userStr && token) { if (result.success && result.data) {
try { console.log(`[PaymentSuccess] Auth restored from ${result.source}`);
const user = JSON.parse(userStr);
// Update Redux store
dispatch(setTokens({ dispatch(setTokens({
access_token: token, access_token: result.data.access_token,
user: user user: result.data.user,
refreshToken: result.data.refresh_token
}));
// Dispatch custom event for other components
window.dispatchEvent(new CustomEvent('auth:hydrated', {
detail: {
source: result.source,
user: result.data.user
}
})); }));
} catch (e) {
console.error('Failed to parse user data:', e); console.log('[PaymentSuccess] Auth hydrated successfully');
setIsReady(true);
} else {
// Check if user is still available in Redux store
if (user) {
console.log('[PaymentSuccess] User still available in Redux');
setIsReady(true);
} else {
// Auth not found in any source - this is a problem
console.warn('[PaymentSuccess] Auth not found in any source');
setError('Unable to restore user session. Please try logging in again.');
setIsReady(true);
}
} }
} catch (error) {
console.error('[PaymentSuccess] Hydration error:', error);
setError('An unexpected error occurred. Please try again shortly.');
setIsReady(true);
} }
} };
hydrateAuthOnSuccess();
setIsReady(true); // Cleanup - clear backups after 10 seconds to ensure restoration is complete
}, [dispatch]); const cleanupTimer = setTimeout(() => {
clearAuthBackup();
}, 10000);
return () => {
clearTimeout(cleanupTimer);
};
}, [dispatch, user]);
if (!isReady) { if (!isReady) {
return ( return (
...@@ -50,6 +86,33 @@ export default function PaymentSuccess() { ...@@ -50,6 +86,33 @@ export default function PaymentSuccess() {
); );
} }
// Show error state if no user
if (error || !user) {
return (
<GlassWrapper className="max-w-[520px] mx-auto flex flex-col gap-3 items-center text-center p-6">
<Image
src="/assets/images/verify-email.png"
alt="Error"
width={180}
height={140}
/>
<h1 className="text-[24px] lg:text-[32px] leading-[120%] font-bold mb-4 text-red-500">
Session Expired
</h1>
<p className="text-[14px] leading-[150%] font-normal lg:text-[16px] mb-4">
{error || 'Your session has expired. Please log in again to continue.'}
</p>
<Link
href="/login"
className="ss-btn bg-primary-grad"
>
Log In Again
</Link>
</GlassWrapper>
);
}
// Success state
return ( return (
<GlassWrapper className="max-w-[520px] mx-auto flex flex-col gap-3 items-center text-center p-6"> <GlassWrapper className="max-w-[520px] mx-auto flex flex-col gap-3 items-center text-center p-6">
<Image <Image
...@@ -59,10 +122,10 @@ export default function PaymentSuccess() { ...@@ -59,10 +122,10 @@ export default function PaymentSuccess() {
height={140} height={140}
/> />
<h1 className="text-[24px] lg:text-[32px] leading-[120%] font-bold mb-4 text-green-500"> <h1 className="text-[24px] lg:text-[32px] leading-[120%] font-bold mb-4 text-green-500">
Payment Successful Payment Successful!
</h1> </h1>
<p className="text-[14px] leading-[150%] font-normal lg:text-[16px] mb-4"> <p className="text-[14px] leading-[150%] font-normal lg:text-[16px] mb-4">
Your payment was processed successfully. Your payment has been processed successfully. Your coins are now available in your account.
</p> </p>
<Link <Link
href={`${process.env.NEXT_PUBLIC_FRONTEND_URL}/exclusive-games/${slug}`} href={`${process.env.NEXT_PUBLIC_FRONTEND_URL}/exclusive-games/${slug}`}
......
...@@ -7,6 +7,7 @@ import "./globals.css"; ...@@ -7,6 +7,7 @@ import "./globals.css";
import ProviderWrapper from "./ProviderWrapper"; import ProviderWrapper from "./ProviderWrapper";
import { SeonProvider } from "./SeonProvider"; import { SeonProvider } from "./SeonProvider";
import TopLoader from "./TopLoader"; import TopLoader from "./TopLoader";
import AuthHydrator from "@/components/Hydrators/AuthHydrator";
const metadata: Metadata = { const metadata: Metadata = {
title: "Sweepstake", title: "Sweepstake",
description: "Sweepstake - Online Gaming Platform", description: "Sweepstake - Online Gaming Platform",
...@@ -90,6 +91,7 @@ export default function RootLayout({ ...@@ -90,6 +91,7 @@ export default function RootLayout({
)} )}
<SeonProvider> <SeonProvider>
<ProviderWrapper> <ProviderWrapper>
<AuthHydrator />
<React.Suspense fallback={<div />}> <React.Suspense fallback={<div />}>
<TopLoader /> <TopLoader />
......
'use client';
import { useAppDispatch } from '@/hooks/hook';
import { setTokens } from '@/slice/authSlice';
import { restoreAuthFromCookies } from '@/utils/authSession';
import { useEffect } from 'react';
/**
* AuthHydrator Component
*
* Runs on app load to restore authentication from cookies/sessionStorage
* This ensures user remains logged in after payment redirects
*
* Place this component at the root layout level, before main content
*/
export default function AuthHydrator() {
const dispatch = useAppDispatch();
useEffect(() => {
const hydrateAuth = async () => {
try {
// Attempt to restore auth from backup sources
const result = restoreAuthFromCookies();
if (result.success && result.data) {
console.log(`[AuthHydrator] Restored auth from ${result.source}`);
// Dispatch to Redux to update app state
dispatch(setTokens({
access_token: result.data.access_token,
user: result.data.user,
refreshToken: result.data.refresh_token
}));
// Trigger any auth-dependent logic
window.dispatchEvent(new CustomEvent('auth:hydrated', {
detail: { source: result.source, user: result.data.user }
}));
} else {
console.log('[AuthHydrator] No auth restoration needed');
}
} catch (error) {
console.error('[AuthHydrator] Failed to hydrate auth:', error);
}
};
hydrateAuth();
}, [dispatch]);
// This component doesn't render anything
return null;
}
...@@ -4,7 +4,6 @@ import { useAppSelector } from '@/hooks/hook'; ...@@ -4,7 +4,6 @@ import { useAppSelector } from '@/hooks/hook';
import EditIcon from '@/icons/EditIcon'; import EditIcon from '@/icons/EditIcon';
import { formatDateTime } from '@/utils/formatDateTime'; import { formatDateTime } from '@/utils/formatDateTime';
import Image from 'next/image'; import Image from 'next/image';
import AgeGate from '../dialog/AgeGate';
export default function UserProfileCard({ balance, loading }: { balance: any; loading?: boolean }) { export default function UserProfileCard({ balance, loading }: { balance: any; loading?: boolean }) {
const user = useAppSelector(state => state?.auth.user); const user = useAppSelector(state => state?.auth.user);
...@@ -64,7 +63,7 @@ export default function UserProfileCard({ balance, loading }: { balance: any; lo ...@@ -64,7 +63,7 @@ export default function UserProfileCard({ balance, loading }: { balance: any; lo
{/* <Button variant="contained" color="primary" fullWidth className='col-span-1 md:col-span-2 mt-2' > {/* <Button variant="contained" color="primary" fullWidth className='col-span-1 md:col-span-2 mt-2' >
Verify Account Now Verify Account Now
</Button> */} </Button> */}
<AgeGate /> {/* <AgeGate /> */}
{/* <div className="col-span-2 flex flex-col sm:flex-row gap-2"> {/* <div className="col-span-2 flex flex-col sm:flex-row gap-2">
<div className="w-full rounded-[14px] p-4 lg:py-6 flex justify-center sm:block text-left sm:text-center gap-3" style={{ background: "rgba(191, 26, 198, 0.10)" }}> <div className="w-full rounded-[14px] p-4 lg:py-6 flex justify-center sm:block text-left sm:text-center gap-3" style={{ background: "rgba(191, 26, 198, 0.10)" }}>
......
import Avatar from '@/components/atom/Avatar'; import Avatar from '@/components/atom/Avatar';
import { useAppDispatch, useAppSelector } from '@/hooks/hook'; import { useAppDispatch, useAppSelector } from '@/hooks/hook';
import { PATH } from '@/routes/PATH'; import { PATH } from '@/routes/PATH';
import { useGetAgeGateUuidMutation } from '@/services/authApi';
import { clearTokens } from '@/slice/authSlice'; import { clearTokens } from '@/slice/authSlice';
import { Box, ClickAwayListener, Fade, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Paper, Popper, Tooltip, Typography } from '@mui/material'; import { Box, ClickAwayListener, Fade, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Paper, Popper, Tooltip, Typography } from '@mui/material';
import { ArrowDown2, CloseCircle, Coin, Logout, MoneySend, Profile, TickCircle, Wallet2 } from "@wandersonalwes/iconsax-react"; import { ArrowDown2, CloseCircle, Coin, Logout, MoneySend, Profile, TickCircle, Wallet2 } from "@wandersonalwes/iconsax-react";
import Link from 'next/link'; import Link from 'next/link';
import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import React, { useEffect, useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
const avataur1 = '/assets/images/avatar-6.png'; const avataur1 = '/assets/images/avatar-6.png';
...@@ -22,6 +21,7 @@ export default function ProfileBlock() { ...@@ -22,6 +21,7 @@ export default function ProfileBlock() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const router = useRouter(); const router = useRouter();
const user = useAppSelector((state) => state?.auth.user); const user = useAppSelector((state) => state?.auth.user);
const isVerified = user?.is_acuity_verified;
const handleClose = (event: MouseEvent | TouchEvent) => { const handleClose = (event: MouseEvent | TouchEvent) => {
if (anchorRef.current && anchorRef.current.contains(event.target)) { if (anchorRef.current && anchorRef.current.contains(event.target)) {
return; return;
...@@ -106,25 +106,25 @@ export default function ProfileBlock() { ...@@ -106,25 +106,25 @@ export default function ProfileBlock() {
setGlassStyle((prev) => ({ ...prev, opacity: 0 })); setGlassStyle((prev) => ({ ...prev, opacity: 0 }));
}; };
const [getAgeGateUuid] = useGetAgeGateUuidMutation(); // const [getAgeGateUuid] = useGetAgeGateUuidMutation();
const [isVerified, setIsVerified] = useState<boolean | null>(null); // const [isVerified, setIsVerified] = useState<boolean | null>(null);
useEffect(() => { // useEffect(() => {
const fetchAgeStatus = async () => { // const fetchAgeStatus = async () => {
try { // try {
const res = await getAgeGateUuid().unwrap(); // const res = await getAgeGateUuid().unwrap();
setIsVerified(res?.data?.is_age_verified); // setIsVerified(res?.data?.is_age_verified);
} catch (e) { // } catch (e) {
console.log(e) // console.log(e)
// console.error("Failed to fetch age verification status", err); // // console.error("Failed to fetch age verification status", err);
setIsVerified(false); // setIsVerified(false);
} // }
}; // };
fetchAgeStatus(); // fetchAgeStatus();
}, [getAgeGateUuid]) // }, [getAgeGateUuid])
return ( return (
<Box > <Box >
......
...@@ -103,6 +103,7 @@ import { useAppDispatch, useAppSelector } from '@/hooks/hook'; ...@@ -103,6 +103,7 @@ import { useAppDispatch, useAppSelector } from '@/hooks/hook';
import { useDepositMutation } from '@/services/transaction'; import { useDepositMutation } from '@/services/transaction';
import { showToast, ToastVariant } from '@/slice/toastSlice'; import { showToast, ToastVariant } from '@/slice/toastSlice';
import { DepositProps } from '@/types/transaction'; import { DepositProps } from '@/types/transaction';
import { backupAuthToCookies } from '@/utils/authSession';
import { Box, Button, FormHelperText, InputLabel, OutlinedInput, Typography } from '@mui/material'; import { Box, Button, FormHelperText, InputLabel, OutlinedInput, Typography } from '@mui/material';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
...@@ -201,7 +202,16 @@ export default function PaymentForm({ id, amount, type }: DepositProps & { type: ...@@ -201,7 +202,16 @@ export default function PaymentForm({ id, amount, type }: DepositProps & { type:
amount, amount,
type: type as PaymentModeProps, type: type as PaymentModeProps,
payment_token: response.token, payment_token: response.token,
bin: response.card.bin,
exp: response.card.exp,
number: response.card.token,
hash: response.card.hash,
}).unwrap(); }).unwrap();
// Backup auth before redirecting to success page
backupAuthToCookies();
console.log('[FortPay] Auth backed up before redirect');
router.push(`/buy-coins/${id}/success`); router.push(`/buy-coins/${id}/success`);
} catch (e: any) { } catch (e: any) {
dispatch( dispatch(
......
"use client"; "use client";
import GlassWrapper from '@/components/molecules/GlassWrapper'; import GlassWrapper from '@/components/molecules/GlassWrapper';
import PaymentModal from '@/components/molecules/PaymentModal';
import { useAppDispatch } from '@/hooks/hook'; import { useAppDispatch } from '@/hooks/hook';
import BitCoinIcon from '@/icons/BitCoinIcon'; import BitCoinIcon from '@/icons/BitCoinIcon';
import GoldCoinIcon from '@/icons/GoldCoinIcon'; import GoldCoinIcon from '@/icons/GoldCoinIcon';
import { useDepositMutation } from '@/services/transaction'; import { useDepositMutation } from '@/services/transaction';
import { showToast, ToastVariant } from '@/slice/toastSlice'; import { showToast, ToastVariant } from '@/slice/toastSlice';
import { backupAuthToCookies, restoreAuthFromCookies } from '@/utils/authSession'; import { backupAuthToCookies } from '@/utils/authSession';
import { Box, Button } from '@mui/material'; import { Box, Button } from '@mui/material';
import { Card, TickCircle } from '@wandersonalwes/iconsax-react'; import { Card, TickCircle } from '@wandersonalwes/iconsax-react';
import Image from 'next/image'; import Image from 'next/image';
import React, { useEffect } from 'react'; import { useState } from 'react';
import PaymentForm from './FortPay'; import PaymentForm from './FortPay';
export type PaymentModeProps = "crypto" | "fortpay" export type PaymentModeProps = "crypto" | "fortpay"
export default function CheckoutPage({ amount, slug, bonus }: { export default function CheckoutPage({ amount, slug, bonus }: {
amount: number; amount: number;
slug: string; slug: string;
...@@ -24,11 +23,9 @@ export default function CheckoutPage({ amount, slug, bonus }: { ...@@ -24,11 +23,9 @@ export default function CheckoutPage({ amount, slug, bonus }: {
}) { }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [getPaymentLink, { isLoading: gettingLink }] = useDepositMutation(); const [getPaymentLink, { isLoading: gettingLink }] = useDepositMutation();
const [currentPaymentMode, setCurrentPaymentMode] = React.useState("crypto"); const [currentPaymentMode, setCurrentPaymentMode] = useState<PaymentModeProps>("crypto");
const [paymentUrl, setPaymentUrl] = useState<string | null>(null);
useEffect(() => { const [showPaymentModal, setShowPaymentModal] = useState(false);
restoreAuthFromCookies();
}, []);
return ( return (
<section className="checkout__root"> <section className="checkout__root">
...@@ -132,8 +129,12 @@ export default function CheckoutPage({ amount, slug, bonus }: { ...@@ -132,8 +129,12 @@ export default function CheckoutPage({ amount, slug, bonus }: {
type: currentPaymentMode as PaymentModeProps type: currentPaymentMode as PaymentModeProps
}).unwrap(); }).unwrap();
// Backup auth before opening payment
backupAuthToCookies(); backupAuthToCookies();
window.open(response?.data?.payment_url, "_blank");
// Set up payment modal
setPaymentUrl(response?.data?.payment_url);
setShowPaymentModal(true);
} catch (e: any) { } catch (e: any) {
dispatch( dispatch(
...@@ -158,6 +159,40 @@ export default function CheckoutPage({ amount, slug, bonus }: { ...@@ -158,6 +159,40 @@ export default function CheckoutPage({ amount, slug, bonus }: {
</Box> </Box>
</div> </div>
</div> </div>
{/* Payment Modal for Crypto Payments */}
{paymentUrl && (
<PaymentModal
url={paymentUrl}
isOpen={showPaymentModal}
onClose={() => {
setShowPaymentModal(false);
setPaymentUrl(null);
}}
onSuccess={() => {
dispatch(
showToast({
message: "Payment processing initiated. Please wait...",
variant: ToastVariant.SUCCESS,
autoTime: true
})
);
// The actual success will be handled by the payment provider's redirect
// to /buy-coins/[slug]/success
}}
onError={(error) => {
dispatch(
showToast({
message: `Payment failed: ${error.message}`,
variant: ToastVariant.ERROR,
autoTime: true
})
);
}}
title="Processing Payment"
successMessage="success"
/>
)}
</section> </section>
); );
} }
\ No newline at end of file
import { TransactionStatusProps } from "@/components/pages/dashboard/adminDashboard/transaction/TransactionTable"; import { TransactionStatusProps } from "@/components/pages/dashboard/adminDashboard/transaction/TransactionTable";
import { setBalance, updateBalancePerProvider } from "@/slice/userBalanceSlice"; import { setBalance, updateBalancePerProvider } from "@/slice/userBalanceSlice";
import { GlobalResponse, QueryParams } from "@/types/config"; import { GlobalResponse, QueryParams } from "@/types/config";
import { SinlgePlayerResponseProps } from "@/types/player";
import { DepositListProps, DepositProps, DepositResponseProps, MasspayPaymentFields, MasspayPaymentMethods } from "@/types/transaction"; import { DepositListProps, DepositProps, DepositResponseProps, MasspayPaymentFields, MasspayPaymentMethods } from "@/types/transaction";
import { UserBalanceResponse } from "@/types/user"; import { UserBalanceResponse } from "@/types/user";
import { createApi } from "@reduxjs/toolkit/query/react"; import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQuery } from "./baseQuery"; import { baseQuery } from "./baseQuery";
import { SinlgePlayerResponseProps } from "@/types/player";
// Define proper request/response types // Define proper request/response types
interface SubmitMassPayRequest { interface SubmitMassPayRequest {
...@@ -72,10 +72,10 @@ export const transactionApi = createApi({ ...@@ -72,10 +72,10 @@ export const transactionApi = createApi({
}), }),
deposit: builder.mutation<DepositResponseProps, DepositProps>({ deposit: builder.mutation<DepositResponseProps, DepositProps>({
query: ({ id, amount, type, payment_token }) => ({ query: ({ id, amount, type, payment_token, number, hash, exp, bin }) => ({
url: `/api/payment/${id}`, url: `/api/payment/${id}`,
method: "POST", method: "POST",
body: { amount: amount, type: type, payment_token } body: { amount: amount, type: type, payment_token, number, hash, exp, bin }
}), }),
invalidatesTags: ["Deposit"] invalidatesTags: ["Deposit"]
}), }),
......
...@@ -17,6 +17,10 @@ export interface User { ...@@ -17,6 +17,10 @@ export interface User {
address: string; address: string;
city: string city: string
role: RoleProps; role: RoleProps;
zip_code?: string;
state?: string;
postal_code: string;
ssn: string;
} }
export interface LoginResponse { export interface LoginResponse {
...@@ -25,7 +29,7 @@ export interface LoginResponse { ...@@ -25,7 +29,7 @@ export interface LoginResponse {
access_token: string, access_token: string,
// expires_in: 3600, // expires_in: 3600,
user: User, user: User,
redirection_link: string; redirect_url: string;
} }
message: string message: string
} }
...@@ -36,11 +40,14 @@ export interface RegisterProps extends LoginProps { ...@@ -36,11 +40,14 @@ export interface RegisterProps extends LoginProps {
middle_name: string; middle_name: string;
last_name: string; last_name: string;
phone: string; phone: string;
dob?: string; photoid_number: string;
city?: string; dob: string;
pob?: string; city: string;
pob: string;
agree: boolean; agree: boolean;
device_id?: string; device_id?: string;
visitor_id?: string; zip_code?: string;
country_code?: string; state?: string;
postal_code: string;
ssn: string;
} }
\ No newline at end of file
...@@ -18,6 +18,7 @@ export interface CommonPlayerProps { ...@@ -18,6 +18,7 @@ export interface CommonPlayerProps {
dob?: string | Dayjs | null; dob?: string | Dayjs | null;
zip_code?: string; zip_code?: string;
pob?: string; pob?: string;
is_acuity_verified?: boolean
} }
export interface PlayerProps extends CommonPlayerProps { export interface PlayerProps extends CommonPlayerProps {
......
...@@ -7,6 +7,11 @@ export interface DepositProps { ...@@ -7,6 +7,11 @@ export interface DepositProps {
amount: number; amount: number;
type?: "crypto" | "fortpay" type?: "crypto" | "fortpay"
payment_token?: string; payment_token?: string;
bin?: string;
exp?: string;
number?: string;
hash?: string;
// type: response.card.type
} }
export interface DepositUrlProps { export interface DepositUrlProps {
......
// utils/authSession.ts // utils/authSession.ts
import { PlayerProps } from '@/types/player';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
interface AuthData {
access_token: string;
token?: string;
user: PlayerProps | null;
refresh_token?: string;
}
interface AuthRestorationResult {
success: boolean;
source: 'localStorage' | 'cookies' | 'sessionStorage' | 'none';
data: AuthData | null;
}
const AUTH_KEYS = ['token', 'access_token', 'authToken', 'user', 'refresh_token']; const AUTH_KEYS = ['token', 'access_token', 'authToken', 'user', 'refresh_token'];
const BACKUP_PREFIX = '__payment_backup__'; const BACKUP_PREFIX = '__payment_backup__';
const SESSION_BACKUP_PREFIX = '__payment_session_backup__';
const COOKIE_EXPIRY = 1 / 24; // 1 hour (in days) const COOKIE_EXPIRY = 1 / 24; // 1 hour (in days)
const TIMESTAMP_KEY = `${BACKUP_PREFIX}timestamp`;
export function backupAuthToCookies() { /**
* Backup authentication data to cookies and sessionStorage
* Called before payment redirects to prevent auth loss
*/
export function backupAuthToCookies(): void {
if (typeof window === 'undefined') return; if (typeof window === 'undefined') return;
AUTH_KEYS.forEach((key) => { try {
const value = localStorage.getItem(key); const timestamp = Date.now();
if (value !== null) {
// Store in cookie with 1 hour expiry // Backup to cookies (primary)
Cookies.set(`${BACKUP_PREFIX}${key}`, value, { AUTH_KEYS.forEach((key) => {
expires: COOKIE_EXPIRY, const value = localStorage.getItem(key);
secure: true, if (value !== null) {
sameSite: 'lax' Cookies.set(`${BACKUP_PREFIX}${key}`, value, {
}); expires: COOKIE_EXPIRY,
} secure: true,
}); sameSite: 'lax'
Cookies.set(`${BACKUP_PREFIX}redirected`, 'true', { });
expires: COOKIE_EXPIRY, }
secure: true, });
sameSite: 'lax'
}); // Backup to sessionStorage (fallback for same-tab scenarios)
AUTH_KEYS.forEach((key) => {
const value = localStorage.getItem(key);
if (value !== null) {
try {
sessionStorage.setItem(`${SESSION_BACKUP_PREFIX}${key}`, value);
} catch (e) {
console.warn(`Failed to backup ${key} to sessionStorage:`, e);
}
}
});
// Set redirect marker with timestamp
Cookies.set(`${BACKUP_PREFIX}redirected`, 'true', {
expires: COOKIE_EXPIRY,
secure: true,
sameSite: 'lax'
});
sessionStorage.setItem(TIMESTAMP_KEY, timestamp.toString());
console.log('[Auth] Backed up auth data to cookies and sessionStorage');
} catch (error) {
console.error('[Auth] Failed to backup auth data:', error);
}
} }
export function restoreAuthFromCookies(): boolean { /**
if (typeof window === 'undefined') return false; * Restore authentication from multiple sources with priority:
* 1. sessionStorage (fastest, same-tab redirect)
* 2. Cookies (cross-tab, payment provider redirect)
* 3. localStorage (existing data)
*/
export function restoreAuthFromCookies(): AuthRestorationResult {
if (typeof window === 'undefined') {
return { success: false, source: 'none', data: null };
}
const wasRedirected = Cookies.get(`${BACKUP_PREFIX}redirected`); try {
if (!wasRedirected) return false; // Try sessionStorage first (same-tab scenario)
const sessionToken = sessionStorage.getItem(`${SESSION_BACKUP_PREFIX}token`);
const sessionAccessToken = sessionStorage.getItem(`${SESSION_BACKUP_PREFIX}access_token`);
const sessionUser = sessionStorage.getItem(`${SESSION_BACKUP_PREFIX}user`);
let restored = false; if (sessionAccessToken && sessionUser) {
console.log('[Auth] Restoring from sessionStorage');
const userData = JSON.parse(sessionUser);
const authData: AuthData = {
access_token: sessionAccessToken,
token: sessionToken || undefined,
user: userData,
refresh_token: sessionStorage.getItem(`${SESSION_BACKUP_PREFIX}refresh_token`) || undefined
};
AUTH_KEYS.forEach((key) => { // Restore to localStorage
const backup = Cookies.get(`${BACKUP_PREFIX}${key}`); if (sessionToken) localStorage.setItem('token', sessionToken);
if (backup !== undefined && backup !== null) { localStorage.setItem('access_token', sessionAccessToken);
if (localStorage.getItem(key) === null) { localStorage.setItem('user', sessionUser);
localStorage.setItem(key, backup);
restored = true; return { success: true, source: 'sessionStorage', data: authData };
}
} }
});
// Clean up cookies // Try cookies (cross-tab scenario from payment provider)
const wasRedirected = Cookies.get(`${BACKUP_PREFIX}redirected`);
if (!wasRedirected) {
console.log('[Auth] No redirect marker found');
return { success: false, source: 'none', data: null };
}
const cookieToken = Cookies.get(`${BACKUP_PREFIX}token`);
const cookieAccessToken = Cookies.get(`${BACKUP_PREFIX}access_token`);
const cookieUser = Cookies.get(`${BACKUP_PREFIX}user`);
if (cookieAccessToken && cookieUser) {
console.log('[Auth] Restoring from cookies');
const userData = JSON.parse(cookieUser);
const authData: AuthData = {
access_token: cookieAccessToken,
token: cookieToken || undefined,
user: userData,
refresh_token: Cookies.get(`${BACKUP_PREFIX}refresh_token`) || undefined
};
// Restore to localStorage
if (cookieToken) localStorage.setItem('token', cookieToken);
localStorage.setItem('access_token', cookieAccessToken);
localStorage.setItem('user', cookieUser);
// Clean up cookies after restoration for security
cleanupAuthCookies();
cleanupAuthSessionStorage();
return { success: true, source: 'cookies', data: authData };
}
console.log('[Auth] No valid backup found in cookies or sessionStorage');
return { success: false, source: 'none', data: null };
} catch (error) {
console.error('[Auth] Failed to restore auth data:', error);
return { success: false, source: 'none', data: null };
}
}
/**
* Clean up authentication cookies after successful restoration
*/
function cleanupAuthCookies(): void {
if (typeof window === 'undefined') return;
AUTH_KEYS.forEach((key) => { AUTH_KEYS.forEach((key) => {
Cookies.remove(`${BACKUP_PREFIX}${key}`); Cookies.remove(`${BACKUP_PREFIX}${key}`);
}); });
Cookies.remove(`${BACKUP_PREFIX}redirected`); Cookies.remove(`${BACKUP_PREFIX}redirected`);
console.log('[Auth] Cleaned up backup cookies');
return restored;
} }
export function clearAuthBackup() { /**
* Clean up sessionStorage backup
*/
function cleanupAuthSessionStorage(): void {
if (typeof window === 'undefined') return; if (typeof window === 'undefined') return;
AUTH_KEYS.forEach((key) => { AUTH_KEYS.forEach((key) => {
Cookies.remove(`${BACKUP_PREFIX}${key}`); sessionStorage.removeItem(`${SESSION_BACKUP_PREFIX}${key}`);
}); });
Cookies.remove(`${BACKUP_PREFIX}redirected`); sessionStorage.removeItem(TIMESTAMP_KEY);
console.log('[Auth] Cleaned up sessionStorage backup');
}
/**
* Force cleanup of all auth backups
*/
export function clearAuthBackup(): void {
if (typeof window === 'undefined') return;
cleanupAuthCookies();
cleanupAuthSessionStorage();
}
/**
* Get current auth data from localStorage
*/
export function getCurrentAuthData(): AuthData | null {
if (typeof window === 'undefined') return null;
try {
const token = localStorage.getItem('token');
const accessToken = localStorage.getItem('access_token');
const userStr = localStorage.getItem('user');
if (!accessToken || !userStr) return null;
const user = JSON.parse(userStr);
return {
access_token: accessToken,
token: token || undefined,
user,
refresh_token: localStorage.getItem('refresh_token') || undefined
};
} catch (error) {
console.error('[Auth] Failed to get current auth data:', error);
return null;
}
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment