@djangocfg/layouts 2.1.10 → 2.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -161
- package/package.json +6 -6
- package/src/components/RedirectPage/RedirectPage.tsx +1 -1
- package/src/index.ts +0 -6
- package/src/layouts/AppLayout/AppLayout.tsx +1 -1
- package/src/layouts/AppLayout/BaseApp.tsx +1 -1
- package/src/layouts/AuthLayout/AuthContext.tsx +1 -1
- package/src/layouts/AuthLayout/OAuthCallback.tsx +1 -1
- package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -1
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
- package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +1 -1
- package/src/layouts/ProfileLayout/components/AvatarSection.tsx +2 -2
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
- package/src/layouts/_components/UserMenu.tsx +1 -1
- package/src/layouts/index.ts +0 -2
- package/src/snippets/Analytics/useAnalytics.ts +1 -1
- package/src/snippets/index.ts +0 -3
- package/src/auth/README.md +0 -962
- package/src/auth/context/AccountsContext.tsx +0 -240
- package/src/auth/context/AuthContext.tsx +0 -604
- package/src/auth/context/index.ts +0 -4
- package/src/auth/context/types.ts +0 -68
- package/src/auth/hooks/index.ts +0 -17
- package/src/auth/hooks/useAuthForm.ts +0 -332
- package/src/auth/hooks/useAuthGuard.ts +0 -25
- package/src/auth/hooks/useAuthRedirect.ts +0 -51
- package/src/auth/hooks/useAutoAuth.ts +0 -49
- package/src/auth/hooks/useGithubAuth.ts +0 -184
- package/src/auth/hooks/useLocalStorage.ts +0 -214
- package/src/auth/hooks/useProfileCache.ts +0 -146
- package/src/auth/hooks/useSessionStorage.ts +0 -189
- package/src/auth/index.ts +0 -10
- package/src/auth/middlewares/index.ts +0 -1
- package/src/auth/middlewares/proxy.ts +0 -32
- package/src/auth/server.ts +0 -6
- package/src/auth/utils/errors.ts +0 -34
- package/src/auth/utils/index.ts +0 -2
- package/src/auth/utils/validation.ts +0 -14
- package/src/contexts/LeadsContext.tsx +0 -156
- package/src/contexts/NewsletterContext.tsx +0 -263
- package/src/contexts/SupportContext.tsx +0 -256
- package/src/contexts/index.ts +0 -59
- package/src/contexts/knowbase/ChatContext.tsx +0 -174
- package/src/contexts/knowbase/DocumentsContext.tsx +0 -304
- package/src/contexts/knowbase/SessionsContext.tsx +0 -174
- package/src/contexts/knowbase/index.ts +0 -61
- package/src/contexts/payments/BalancesContext.tsx +0 -65
- package/src/contexts/payments/CurrenciesContext.tsx +0 -66
- package/src/contexts/payments/OverviewContext.tsx +0 -174
- package/src/contexts/payments/PaymentsContext.tsx +0 -132
- package/src/contexts/payments/README.md +0 -201
- package/src/contexts/payments/RootPaymentsContext.tsx +0 -68
- package/src/contexts/payments/index.ts +0 -50
- package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -92
- package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -291
- package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -290
- package/src/layouts/PaymentsLayout/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/events.ts +0 -47
- package/src/layouts/PaymentsLayout/index.ts +0 -16
- package/src/layouts/PaymentsLayout/types.ts +0 -6
- package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -128
- package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -142
- package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
- package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -20
- package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -276
- package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -17
- package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -273
- package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
- package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -17
- package/src/layouts/SupportLayout/README.md +0 -91
- package/src/layouts/SupportLayout/SupportLayout.tsx +0 -179
- package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +0 -155
- package/src/layouts/SupportLayout/components/MessageInput.tsx +0 -92
- package/src/layouts/SupportLayout/components/MessageList.tsx +0 -314
- package/src/layouts/SupportLayout/components/TicketCard.tsx +0 -96
- package/src/layouts/SupportLayout/components/TicketList.tsx +0 -153
- package/src/layouts/SupportLayout/components/index.ts +0 -6
- package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +0 -263
- package/src/layouts/SupportLayout/context/index.ts +0 -2
- package/src/layouts/SupportLayout/events.ts +0 -33
- package/src/layouts/SupportLayout/hooks/index.ts +0 -2
- package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +0 -119
- package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +0 -92
- package/src/layouts/SupportLayout/index.ts +0 -8
- package/src/layouts/SupportLayout/types.ts +0 -21
- package/src/snippets/Chat/ChatUIContext.tsx +0 -110
- package/src/snippets/Chat/ChatWidget.tsx +0 -476
- package/src/snippets/Chat/README.md +0 -122
- package/src/snippets/Chat/components/MessageInput.tsx +0 -124
- package/src/snippets/Chat/components/MessageList.tsx +0 -169
- package/src/snippets/Chat/components/SessionList.tsx +0 -192
- package/src/snippets/Chat/components/index.ts +0 -9
- package/src/snippets/Chat/hooks/index.ts +0 -6
- package/src/snippets/Chat/hooks/useInfiniteSessions.ts +0 -82
- package/src/snippets/Chat/index.tsx +0 -45
- package/src/snippets/Chat/types.ts +0 -80
- package/src/snippets/ContactForm/ContactForm.tsx +0 -346
- package/src/snippets/ContactForm/ContactFormProvider.tsx +0 -153
- package/src/snippets/ContactForm/ContactInfo.tsx +0 -114
- package/src/snippets/ContactForm/ContactPage.tsx +0 -131
- package/src/snippets/ContactForm/dynamic.tsx +0 -55
- package/src/snippets/ContactForm/index.ts +0 -34
- package/src/snippets/ContactForm/types.ts +0 -110
|
@@ -1,604 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
'use client';
|
|
3
|
-
|
|
4
|
-
import { usePathname } from 'next/navigation';
|
|
5
|
-
import React, {
|
|
6
|
-
createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState
|
|
7
|
-
} from 'react';
|
|
8
|
-
|
|
9
|
-
import { api, Enums } from '@djangocfg/api';
|
|
10
|
-
import { useAccountsContext, AccountsProvider } from './AccountsContext';
|
|
11
|
-
import { useLocalStorage, useQueryParams, useCfgRouter } from '@djangocfg/ui-nextjs/hooks';
|
|
12
|
-
import { getCachedProfile, clearProfileCache } from '../hooks/useProfileCache';
|
|
13
|
-
|
|
14
|
-
import { authLogger } from '../../utils/logger';
|
|
15
|
-
import { Analytics, AnalyticsEvent, AnalyticsCategory } from '../../snippets/Analytics';
|
|
16
|
-
import type { AuthConfig, AuthContextType, AuthProviderProps, UserProfile } from './types';
|
|
17
|
-
|
|
18
|
-
// Default routes
|
|
19
|
-
const defaultRoutes = {
|
|
20
|
-
auth: '/auth',
|
|
21
|
-
defaultCallback: '/dashboard',
|
|
22
|
-
defaultAuthCallback: '/auth',
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
26
|
-
|
|
27
|
-
// Constants
|
|
28
|
-
const EMAIL_STORAGE_KEY = 'auth_email';
|
|
29
|
-
const PHONE_STORAGE_KEY = 'auth_phone';
|
|
30
|
-
const AUTH_REDIRECT_KEY = 'auth_redirect_url';
|
|
31
|
-
|
|
32
|
-
const hasValidTokens = (): boolean => {
|
|
33
|
-
if (typeof window === 'undefined') return false;
|
|
34
|
-
return api.isAuthenticated();
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Internal provider that uses AccountsContext
|
|
38
|
-
const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config }) => {
|
|
39
|
-
const accounts = useAccountsContext();
|
|
40
|
-
|
|
41
|
-
// Smart initial loading state: only true if we don't have tokens yet
|
|
42
|
-
const [isLoading, setIsLoading] = useState(() => {
|
|
43
|
-
// If we already have tokens and profile, don't show loading
|
|
44
|
-
if (typeof window !== 'undefined') {
|
|
45
|
-
const hasTokens = hasValidTokens();
|
|
46
|
-
// Only show loading on initial mount if no tokens
|
|
47
|
-
return !hasTokens;
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const [initialized, setInitialized] = useState(false);
|
|
53
|
-
const router = useCfgRouter();
|
|
54
|
-
const pathname = usePathname();
|
|
55
|
-
const queryParams = useQueryParams();
|
|
56
|
-
|
|
57
|
-
// Use localStorage hooks for email, phone, and redirect
|
|
58
|
-
const [storedEmail, setStoredEmail, clearStoredEmail] = useLocalStorage<string | null>(EMAIL_STORAGE_KEY, null);
|
|
59
|
-
const [storedPhone, setStoredPhone, clearStoredPhone] = useLocalStorage<string | null>(PHONE_STORAGE_KEY, null);
|
|
60
|
-
const [redirectUrl, setRedirectUrl, clearRedirectUrl] = useLocalStorage<string | null>(AUTH_REDIRECT_KEY, null);
|
|
61
|
-
|
|
62
|
-
// Map AccountsContext profile to UserProfile
|
|
63
|
-
const user = accounts.profile as UserProfile | null;
|
|
64
|
-
|
|
65
|
-
// Use refs to avoid dependency issues
|
|
66
|
-
const userRef = useRef(user);
|
|
67
|
-
const configRef = useRef(config);
|
|
68
|
-
const isLoadingProfileRef = useRef(false);
|
|
69
|
-
|
|
70
|
-
// Update refs when values change
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
userRef.current = user;
|
|
73
|
-
}, [user]);
|
|
74
|
-
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
configRef.current = config;
|
|
77
|
-
}, [config]);
|
|
78
|
-
|
|
79
|
-
// Note: API URL is configured in BaseClient, not at runtime
|
|
80
|
-
|
|
81
|
-
// Common function to clear auth state
|
|
82
|
-
const clearAuthState = useCallback((caller: string) => {
|
|
83
|
-
authLogger.info('clearAuthState >> caller', caller);
|
|
84
|
-
api.clearTokens();
|
|
85
|
-
clearProfileCache(); // Clear profile cache from localStorage
|
|
86
|
-
// Note: user is now managed by AccountsContext, will auto-update
|
|
87
|
-
setInitialized(true);
|
|
88
|
-
setIsLoading(false);
|
|
89
|
-
}, []);
|
|
90
|
-
|
|
91
|
-
// Global error handler for auth-related errors
|
|
92
|
-
const handleGlobalAuthError = useCallback((error: any, context: string = 'API Request') => {
|
|
93
|
-
// Simple error check - if response has error flag, it's an error
|
|
94
|
-
if (error?.success === false) {
|
|
95
|
-
authLogger.warn(`Error detected in ${context}, clearing tokens`);
|
|
96
|
-
clearAuthState(`globalAuthError:${context}`);
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return false;
|
|
101
|
-
}, [clearAuthState]);
|
|
102
|
-
|
|
103
|
-
// Simple profile loading without retry - now uses AccountsContext with memoization
|
|
104
|
-
const loadCurrentProfile = useCallback(async (callerId?: string): Promise<void> => {
|
|
105
|
-
const finalCallerId = callerId || 'AuthContext.loadCurrentProfile';
|
|
106
|
-
|
|
107
|
-
// Check if profile loading is already in progress
|
|
108
|
-
if (isLoadingProfileRef.current) {
|
|
109
|
-
authLogger.debug(`Profile loading already in progress, skipping duplicate call from: ${finalCallerId}`);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
authLogger.debug(`loadCurrentProfile called by: ${finalCallerId}`);
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
isLoadingProfileRef.current = true;
|
|
117
|
-
|
|
118
|
-
// Ensure API clients are properly initialized with current token
|
|
119
|
-
const isAuth = api.isAuthenticated();
|
|
120
|
-
const token = api.getToken();
|
|
121
|
-
// authLogger.debug('isAuthenticated:', isAuth, 'token:', token ? token.substring(0, 20) + '...' : 'null');
|
|
122
|
-
|
|
123
|
-
if (!isAuth) {
|
|
124
|
-
authLogger.warn('No valid authentication token, throwing error');
|
|
125
|
-
throw new Error('No valid authentication token');
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Check if profile is already loaded in AccountsContext to prevent duplicate API calls
|
|
129
|
-
if (accounts.profile && !accounts.isLoadingProfile) {
|
|
130
|
-
authLogger.debug('Profile already loaded in AccountsContext, skipping API call');
|
|
131
|
-
setInitialized(true);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Refresh profile from AccountsContext (now with memoization)
|
|
136
|
-
const refreshedProfile = await accounts.refreshProfile(finalCallerId);
|
|
137
|
-
|
|
138
|
-
if (refreshedProfile) {
|
|
139
|
-
authLogger.info('Profile loaded successfully:', refreshedProfile.id);
|
|
140
|
-
} else {
|
|
141
|
-
authLogger.warn('Profile refresh returned undefined - but keeping tokens');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Always mark as initialized if we have valid tokens
|
|
145
|
-
// Don't clear tokens just because profile fetch failed
|
|
146
|
-
setInitialized(true);
|
|
147
|
-
} catch (error) {
|
|
148
|
-
authLogger.error('Failed to load profile:', error);
|
|
149
|
-
// Use global error handler first, fallback to clearing state
|
|
150
|
-
if (!handleGlobalAuthError(error, 'loadCurrentProfile')) {
|
|
151
|
-
clearAuthState('loadCurrentProfile:error');
|
|
152
|
-
}
|
|
153
|
-
} finally {
|
|
154
|
-
isLoadingProfileRef.current = false;
|
|
155
|
-
}
|
|
156
|
-
}, [clearAuthState, handleGlobalAuthError, accounts]);
|
|
157
|
-
|
|
158
|
-
// Initialize auth state once
|
|
159
|
-
useEffect(() => {
|
|
160
|
-
if (initialized) return;
|
|
161
|
-
|
|
162
|
-
const initializeAuth = async () => {
|
|
163
|
-
authLogger.info('Initializing auth...');
|
|
164
|
-
|
|
165
|
-
// Check if running in iframe (AdminLayout will handle auth via postMessage)
|
|
166
|
-
const isInIframe = typeof window !== 'undefined' && window.self !== window.top;
|
|
167
|
-
authLogger.info('Is in iframe:', isInIframe);
|
|
168
|
-
|
|
169
|
-
// Debug token state
|
|
170
|
-
const token = api.getToken();
|
|
171
|
-
const refreshToken = api.getRefreshToken();
|
|
172
|
-
authLogger.info('Token from API:', token ? `${token.substring(0, 20)}...` : 'null');
|
|
173
|
-
authLogger.info('Refresh token from API:', refreshToken ? `${refreshToken.substring(0, 20)}...` : 'null');
|
|
174
|
-
authLogger.info('localStorage keys:', Object.keys(localStorage).filter(k => k.includes('token') || k.includes('auth')));
|
|
175
|
-
|
|
176
|
-
const hasTokens = hasValidTokens();
|
|
177
|
-
authLogger.info('Has tokens:', hasTokens);
|
|
178
|
-
|
|
179
|
-
// Check if profile is already loaded from cache (AccountsContext initialization)
|
|
180
|
-
if (userRef.current) {
|
|
181
|
-
authLogger.info('Profile already loaded from AccountsContext cache, skipping API request');
|
|
182
|
-
setInitialized(true);
|
|
183
|
-
setIsLoading(false);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Check if cache exists in localStorage (before userRef updates)
|
|
188
|
-
const cachedProfile = getCachedProfile();
|
|
189
|
-
if (cachedProfile) {
|
|
190
|
-
authLogger.info('Profile found in localStorage cache, skipping API request');
|
|
191
|
-
setInitialized(true);
|
|
192
|
-
setIsLoading(false);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// In iframe mode WITHOUT tokens yet - wait for AdminLayout to receive them via postMessage
|
|
197
|
-
// Don't initialize yet - AdminLayout.handleAuthToken will call loadCurrentProfile
|
|
198
|
-
if (isInIframe && !hasTokens) {
|
|
199
|
-
authLogger.info('Running in iframe without tokens - waiting for parent to send via postMessage');
|
|
200
|
-
authLogger.info('AdminLayout will handle auth initialization, skipping AuthContext init');
|
|
201
|
-
setInitialized(true); // Mark as initialized to prevent re-initialization
|
|
202
|
-
setIsLoading(false);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (hasTokens) {
|
|
207
|
-
setIsLoading(true);
|
|
208
|
-
try {
|
|
209
|
-
authLogger.info('No cached profile found, loading from API...');
|
|
210
|
-
await loadCurrentProfile('AuthContext.initializeAuth');
|
|
211
|
-
} catch (error) {
|
|
212
|
-
authLogger.error('Failed to load profile during initialization:', error);
|
|
213
|
-
// If profile loading fails, clear auth state
|
|
214
|
-
clearAuthState('initializeAuth:loadProfileFailed');
|
|
215
|
-
}
|
|
216
|
-
setIsLoading(false);
|
|
217
|
-
} else {
|
|
218
|
-
setInitialized(true);
|
|
219
|
-
setIsLoading(false);
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
initializeAuth();
|
|
224
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
225
|
-
}, [initialized]);
|
|
226
|
-
|
|
227
|
-
// Redirect logic - only for unauthenticated users on protected pages
|
|
228
|
-
useEffect(() => {
|
|
229
|
-
if (!initialized) return;
|
|
230
|
-
|
|
231
|
-
// Consider authenticated if we have valid tokens, even without profile
|
|
232
|
-
const isAuthenticated = api.isAuthenticated();
|
|
233
|
-
const authRoute = config?.routes?.auth || defaultRoutes.auth;
|
|
234
|
-
const isAuthPage = pathname === authRoute;
|
|
235
|
-
const flowParam = queryParams.get('flow');
|
|
236
|
-
|
|
237
|
-
// Only redirect authenticated users away from auth page if they're not in a flow
|
|
238
|
-
// This prevents interference with OTP verification flow
|
|
239
|
-
if (isAuthenticated && isAuthPage && !flowParam) {
|
|
240
|
-
const callbackUrl = config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
|
|
241
|
-
router.push(callbackUrl);
|
|
242
|
-
}
|
|
243
|
-
}, [initialized, pathname, queryParams, config?.routes]);
|
|
244
|
-
|
|
245
|
-
const pushToDefaultCallbackUrl = useCallback(() => {
|
|
246
|
-
const callbackUrl = config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
|
|
247
|
-
router.push(callbackUrl);
|
|
248
|
-
}, [config?.routes, router]);
|
|
249
|
-
|
|
250
|
-
const pushToDefaultAuthCallbackUrl = useCallback(() => {
|
|
251
|
-
const authCallbackUrl = config?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
|
|
252
|
-
router.push(authCallbackUrl);
|
|
253
|
-
}, [config?.routes, router]);
|
|
254
|
-
|
|
255
|
-
// Memoized checkAuthAndRedirect function
|
|
256
|
-
const checkAuthAndRedirect = useCallback(async () => {
|
|
257
|
-
try {
|
|
258
|
-
setIsLoading(true);
|
|
259
|
-
const isAuthenticated = api.isAuthenticated();
|
|
260
|
-
|
|
261
|
-
if (isAuthenticated) {
|
|
262
|
-
await loadCurrentProfile();
|
|
263
|
-
if (userRef.current) {
|
|
264
|
-
pushToDefaultCallbackUrl();
|
|
265
|
-
}
|
|
266
|
-
} else {
|
|
267
|
-
pushToDefaultAuthCallbackUrl();
|
|
268
|
-
}
|
|
269
|
-
} catch (error) {
|
|
270
|
-
authLogger.error('Failed to check authentication:', error);
|
|
271
|
-
// Use global error handler first
|
|
272
|
-
if (!handleGlobalAuthError(error, 'checkAuthAndRedirect')) {
|
|
273
|
-
clearAuthState('checkAuthAndRedirect');
|
|
274
|
-
}
|
|
275
|
-
pushToDefaultAuthCallbackUrl();
|
|
276
|
-
} finally {
|
|
277
|
-
setIsLoading(false);
|
|
278
|
-
}
|
|
279
|
-
}, [loadCurrentProfile, clearAuthState, pushToDefaultCallbackUrl, pushToDefaultAuthCallbackUrl, handleGlobalAuthError]);
|
|
280
|
-
|
|
281
|
-
// OTP methods - supports both email and phone - now uses AccountsContext
|
|
282
|
-
const requestOTP = useCallback(
|
|
283
|
-
async (identifier: string, channel?: 'email' | 'phone', sourceUrl?: string): Promise<{ success: boolean; message: string }> => {
|
|
284
|
-
// Clear tokens before requesting OTP
|
|
285
|
-
api.clearTokens();
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
const channelValue = channel === 'phone'
|
|
289
|
-
? Enums.OTPRequestRequestChannel.PHONE
|
|
290
|
-
: Enums.OTPRequestRequestChannel.EMAIL;
|
|
291
|
-
const result = await accounts.requestOTP({
|
|
292
|
-
identifier,
|
|
293
|
-
channel: channelValue,
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
const channelName = channel === 'phone' ? 'phone number' : 'email address';
|
|
297
|
-
|
|
298
|
-
// Track OTP request
|
|
299
|
-
Analytics.event(AnalyticsEvent.AUTH_OTP_REQUEST, {
|
|
300
|
-
category: AnalyticsCategory.AUTH,
|
|
301
|
-
label: channel || 'email',
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
success: true,
|
|
306
|
-
message: result.message || `OTP code sent to your ${channelName}`,
|
|
307
|
-
};
|
|
308
|
-
} catch (error) {
|
|
309
|
-
authLogger.error('Request OTP error:', error);
|
|
310
|
-
return {
|
|
311
|
-
success: false,
|
|
312
|
-
message: 'Failed to send OTP',
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
[accounts],
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
const verifyOTP = useCallback(
|
|
320
|
-
async (identifier: string, otpCode: string, channel?: 'email' | 'phone', sourceUrl?: string): Promise<{ success: boolean; message: string; user?: UserProfile }> => {
|
|
321
|
-
try {
|
|
322
|
-
const channelValue = channel === 'phone'
|
|
323
|
-
? Enums.OTPVerifyRequestChannel.PHONE
|
|
324
|
-
: Enums.OTPVerifyRequestChannel.EMAIL;
|
|
325
|
-
// AccountsContext automatically saves tokens and refreshes profile
|
|
326
|
-
const result = await accounts.verifyOTP({
|
|
327
|
-
identifier,
|
|
328
|
-
otp: otpCode,
|
|
329
|
-
channel: channelValue,
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// Verify that we got valid tokens
|
|
333
|
-
if (!result.access || !result.refresh) {
|
|
334
|
-
authLogger.error('Verify OTP returned invalid response:', result);
|
|
335
|
-
return {
|
|
336
|
-
success: false,
|
|
337
|
-
message: 'Invalid OTP verification response',
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Save identifier based on channel and clear opposite channel
|
|
342
|
-
if (channel === 'phone') {
|
|
343
|
-
setStoredPhone(identifier);
|
|
344
|
-
clearStoredEmail();
|
|
345
|
-
} else if (identifier.includes('@')) {
|
|
346
|
-
setStoredEmail(identifier);
|
|
347
|
-
clearStoredPhone();
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Small delay to ensure profile state is updated
|
|
351
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
352
|
-
|
|
353
|
-
// Track successful login
|
|
354
|
-
Analytics.event(AnalyticsEvent.AUTH_LOGIN_SUCCESS, {
|
|
355
|
-
category: AnalyticsCategory.AUTH,
|
|
356
|
-
label: channel || 'email',
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// Set user ID for future tracking
|
|
360
|
-
if (result.user?.id) {
|
|
361
|
-
Analytics.setUser(String(result.user.id));
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Handle redirect logic here
|
|
365
|
-
// Use hardPush for full page reload - ensures all React contexts reinitialize
|
|
366
|
-
const defaultCallback = config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
|
|
367
|
-
|
|
368
|
-
if (redirectUrl && redirectUrl !== defaultCallback) {
|
|
369
|
-
clearRedirectUrl();
|
|
370
|
-
router.hardPush(redirectUrl);
|
|
371
|
-
} else {
|
|
372
|
-
router.hardPush(defaultCallback);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return {
|
|
376
|
-
success: true,
|
|
377
|
-
message: 'Login successful',
|
|
378
|
-
user: result.user as UserProfile,
|
|
379
|
-
};
|
|
380
|
-
} catch (error) {
|
|
381
|
-
authLogger.error('Verify OTP error:', error);
|
|
382
|
-
|
|
383
|
-
// Track failed verification
|
|
384
|
-
Analytics.event(AnalyticsEvent.AUTH_OTP_VERIFY_FAIL, {
|
|
385
|
-
category: AnalyticsCategory.AUTH,
|
|
386
|
-
label: channel || 'email',
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
return {
|
|
390
|
-
success: false,
|
|
391
|
-
message: 'Failed to verify OTP',
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
[setStoredEmail, setStoredPhone, clearStoredEmail, clearStoredPhone, redirectUrl, clearRedirectUrl, config?.routes?.defaultCallback, accounts],
|
|
396
|
-
);
|
|
397
|
-
|
|
398
|
-
const refreshToken = useCallback(async (): Promise<{ success: boolean; message: string }> => {
|
|
399
|
-
try {
|
|
400
|
-
const refreshTokenValue = api.getRefreshToken();
|
|
401
|
-
if (!refreshTokenValue) {
|
|
402
|
-
clearAuthState('refreshToken:noToken');
|
|
403
|
-
|
|
404
|
-
// Track session expired
|
|
405
|
-
Analytics.event(AnalyticsEvent.AUTH_SESSION_EXPIRED, {
|
|
406
|
-
category: AnalyticsCategory.AUTH,
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
return {
|
|
410
|
-
success: false,
|
|
411
|
-
message: 'No refresh token available',
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
await accounts.refreshToken(refreshTokenValue);
|
|
416
|
-
|
|
417
|
-
// Track successful refresh
|
|
418
|
-
Analytics.event(AnalyticsEvent.AUTH_TOKEN_REFRESH, {
|
|
419
|
-
category: AnalyticsCategory.AUTH,
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
return {
|
|
423
|
-
success: true,
|
|
424
|
-
message: 'Token refreshed',
|
|
425
|
-
};
|
|
426
|
-
} catch (error) {
|
|
427
|
-
authLogger.error('Refresh token error:', error);
|
|
428
|
-
clearAuthState('refreshToken:error');
|
|
429
|
-
|
|
430
|
-
// Track refresh failure
|
|
431
|
-
Analytics.event(AnalyticsEvent.AUTH_TOKEN_REFRESH_FAIL, {
|
|
432
|
-
category: AnalyticsCategory.AUTH,
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
return {
|
|
436
|
-
success: false,
|
|
437
|
-
message: 'Error refreshing token',
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
}, [clearAuthState, accounts]);
|
|
441
|
-
|
|
442
|
-
const clearRedirect = useCallback((): void => {
|
|
443
|
-
clearRedirectUrl();
|
|
444
|
-
}, [clearRedirectUrl]);
|
|
445
|
-
|
|
446
|
-
// Save current URL for redirect after authentication
|
|
447
|
-
const saveCurrentUrlForRedirect = useCallback((): void => {
|
|
448
|
-
if (typeof window !== 'undefined') {
|
|
449
|
-
const currentUrl = window.location.pathname + window.location.search;
|
|
450
|
-
setRedirectUrl(currentUrl);
|
|
451
|
-
}
|
|
452
|
-
}, [setRedirectUrl]);
|
|
453
|
-
|
|
454
|
-
const logout = useCallback(async (): Promise<void> => {
|
|
455
|
-
const performLogout = () => {
|
|
456
|
-
// Track logout
|
|
457
|
-
Analytics.event(AnalyticsEvent.AUTH_LOGOUT, {
|
|
458
|
-
category: AnalyticsCategory.AUTH,
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
accounts.logout(); // Clear tokens and profile
|
|
462
|
-
setInitialized(true);
|
|
463
|
-
setIsLoading(false);
|
|
464
|
-
|
|
465
|
-
// Use hardReplace for full page reload + replace history
|
|
466
|
-
// This ensures contexts reinitialize AND back button won't return to protected page
|
|
467
|
-
const authCallbackUrl = config?.routes?.defaultAuthCallback || defaultRoutes.defaultAuthCallback;
|
|
468
|
-
router.hardReplace(authCallbackUrl);
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
// Use config.onConfirm if provided, otherwise use a simple confirm
|
|
472
|
-
if (configRef.current?.onConfirm) {
|
|
473
|
-
const { confirmed } = await configRef.current.onConfirm({
|
|
474
|
-
title: 'Logout',
|
|
475
|
-
description: 'Are you sure you want to logout?',
|
|
476
|
-
confirmationButtonText: 'Logout',
|
|
477
|
-
cancellationButtonText: 'Cancel',
|
|
478
|
-
color: 'error',
|
|
479
|
-
});
|
|
480
|
-
if (confirmed) {
|
|
481
|
-
performLogout();
|
|
482
|
-
}
|
|
483
|
-
} else {
|
|
484
|
-
// Fallback to browser confirm
|
|
485
|
-
const confirmed = window.confirm('Are you sure you want to logout?');
|
|
486
|
-
if (confirmed) {
|
|
487
|
-
performLogout();
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}, [accounts, config?.routes?.defaultAuthCallback, router]);
|
|
491
|
-
|
|
492
|
-
// Redirect URL methods
|
|
493
|
-
const getSavedRedirectUrl = useCallback((): string | null => {
|
|
494
|
-
if (typeof window !== 'undefined') {
|
|
495
|
-
return sessionStorage.getItem(AUTH_REDIRECT_KEY);
|
|
496
|
-
}
|
|
497
|
-
return null;
|
|
498
|
-
}, []);
|
|
499
|
-
|
|
500
|
-
const saveRedirectUrl = useCallback((url: string): void => {
|
|
501
|
-
if (typeof window !== 'undefined') {
|
|
502
|
-
sessionStorage.setItem(AUTH_REDIRECT_KEY, url);
|
|
503
|
-
}
|
|
504
|
-
}, []);
|
|
505
|
-
|
|
506
|
-
const clearSavedRedirectUrl = useCallback((): void => {
|
|
507
|
-
if (typeof window !== 'undefined') {
|
|
508
|
-
sessionStorage.removeItem(AUTH_REDIRECT_KEY);
|
|
509
|
-
}
|
|
510
|
-
}, []);
|
|
511
|
-
|
|
512
|
-
const getFinalRedirectUrl = useCallback((): string => {
|
|
513
|
-
const savedUrl = getSavedRedirectUrl();
|
|
514
|
-
return savedUrl || (config?.routes?.defaultCallback || defaultRoutes.defaultCallback);
|
|
515
|
-
}, [getSavedRedirectUrl, config?.routes?.defaultCallback]);
|
|
516
|
-
|
|
517
|
-
const useAndClearRedirectUrl = useCallback((): string => {
|
|
518
|
-
const finalUrl = getFinalRedirectUrl();
|
|
519
|
-
clearSavedRedirectUrl();
|
|
520
|
-
return finalUrl;
|
|
521
|
-
}, [getFinalRedirectUrl, clearSavedRedirectUrl]);
|
|
522
|
-
|
|
523
|
-
// Computed: Is admin user (staff or superuser)
|
|
524
|
-
const isAdminUser = useMemo(() => {
|
|
525
|
-
return Boolean(user?.is_staff || user?.is_superuser);
|
|
526
|
-
}, [user]);
|
|
527
|
-
|
|
528
|
-
// Memoized context value
|
|
529
|
-
const value = useMemo<AuthContextType>(
|
|
530
|
-
() => ({
|
|
531
|
-
user,
|
|
532
|
-
isLoading,
|
|
533
|
-
// Consider authenticated if we have valid tokens, even without user profile
|
|
534
|
-
isAuthenticated: api.isAuthenticated(),
|
|
535
|
-
isAdminUser,
|
|
536
|
-
loadCurrentProfile,
|
|
537
|
-
checkAuthAndRedirect,
|
|
538
|
-
getToken: () => api.getToken(),
|
|
539
|
-
getRefreshToken: () => api.getRefreshToken(),
|
|
540
|
-
getSavedEmail: () => storedEmail,
|
|
541
|
-
saveEmail: setStoredEmail,
|
|
542
|
-
clearSavedEmail: clearStoredEmail,
|
|
543
|
-
getSavedPhone: () => storedPhone,
|
|
544
|
-
savePhone: setStoredPhone,
|
|
545
|
-
clearSavedPhone: clearStoredPhone,
|
|
546
|
-
requestOTP,
|
|
547
|
-
verifyOTP,
|
|
548
|
-
refreshToken,
|
|
549
|
-
logout,
|
|
550
|
-
getSavedRedirectUrl,
|
|
551
|
-
saveRedirectUrl,
|
|
552
|
-
clearSavedRedirectUrl,
|
|
553
|
-
getFinalRedirectUrl,
|
|
554
|
-
useAndClearRedirectUrl,
|
|
555
|
-
saveCurrentUrlForRedirect,
|
|
556
|
-
}),
|
|
557
|
-
[
|
|
558
|
-
user,
|
|
559
|
-
isLoading,
|
|
560
|
-
isAdminUser,
|
|
561
|
-
loadCurrentProfile,
|
|
562
|
-
checkAuthAndRedirect,
|
|
563
|
-
storedEmail,
|
|
564
|
-
setStoredEmail,
|
|
565
|
-
clearStoredEmail,
|
|
566
|
-
storedPhone,
|
|
567
|
-
setStoredPhone,
|
|
568
|
-
clearStoredPhone,
|
|
569
|
-
requestOTP,
|
|
570
|
-
verifyOTP,
|
|
571
|
-
refreshToken,
|
|
572
|
-
logout,
|
|
573
|
-
getSavedRedirectUrl,
|
|
574
|
-
saveRedirectUrl,
|
|
575
|
-
clearSavedRedirectUrl,
|
|
576
|
-
getFinalRedirectUrl,
|
|
577
|
-
useAndClearRedirectUrl,
|
|
578
|
-
saveCurrentUrlForRedirect,
|
|
579
|
-
],
|
|
580
|
-
);
|
|
581
|
-
|
|
582
|
-
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
// Wrapper that provides AccountsContext
|
|
586
|
-
export const AuthProvider: React.FC<AuthProviderProps> = ({ children, config }) => {
|
|
587
|
-
return (
|
|
588
|
-
<AccountsProvider>
|
|
589
|
-
<AuthProviderInternal config={config}>
|
|
590
|
-
{children}
|
|
591
|
-
</AuthProviderInternal>
|
|
592
|
-
</AccountsProvider>
|
|
593
|
-
);
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
export const useAuth = (): AuthContextType => {
|
|
597
|
-
const context = useContext(AuthContext);
|
|
598
|
-
if (context === undefined) {
|
|
599
|
-
throw new Error('useAuth must be used within an AuthProvider');
|
|
600
|
-
}
|
|
601
|
-
return context;
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
export default AuthContext;
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { AuthProvider, useAuth } from './AuthContext';
|
|
2
|
-
export type { AuthConfig, AuthContextType, AuthProviderProps, UserProfile } from './types';
|
|
3
|
-
export { AccountsProvider, useAccountsContext, PatchedUserProfileUpdateRequestSchema } from './AccountsContext';
|
|
4
|
-
export type { AccountsContextValue, PatchedUserProfileUpdateRequest } from './AccountsContext';
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
import type { CfgUserProfileTypes } from '@djangocfg/api';
|
|
4
|
-
|
|
5
|
-
// User profile type
|
|
6
|
-
export type UserProfile = CfgUserProfileTypes.User;
|
|
7
|
-
|
|
8
|
-
// Auth configuration
|
|
9
|
-
export interface AuthConfig {
|
|
10
|
-
apiUrl?: string;
|
|
11
|
-
routes?: {
|
|
12
|
-
auth?: string;
|
|
13
|
-
defaultCallback?: string;
|
|
14
|
-
defaultAuthCallback?: string;
|
|
15
|
-
};
|
|
16
|
-
onLogout?: () => void;
|
|
17
|
-
onConfirm?: (options: {
|
|
18
|
-
title: string;
|
|
19
|
-
description: string;
|
|
20
|
-
confirmationButtonText: string;
|
|
21
|
-
cancellationButtonText: string;
|
|
22
|
-
color: string;
|
|
23
|
-
}) => Promise<{ confirmed: boolean }>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Auth context interface
|
|
27
|
-
export interface AuthContextType {
|
|
28
|
-
user: UserProfile | null;
|
|
29
|
-
isLoading: boolean;
|
|
30
|
-
isAuthenticated: boolean;
|
|
31
|
-
isAdminUser: boolean;
|
|
32
|
-
loadCurrentProfile: (callerId?: string) => Promise<void>;
|
|
33
|
-
checkAuthAndRedirect: () => Promise<void>;
|
|
34
|
-
|
|
35
|
-
// Token Methods
|
|
36
|
-
getToken: () => string | null;
|
|
37
|
-
getRefreshToken: () => string | null;
|
|
38
|
-
|
|
39
|
-
// Email Methods
|
|
40
|
-
getSavedEmail: () => string | null;
|
|
41
|
-
saveEmail: (email: string) => void;
|
|
42
|
-
clearSavedEmail: () => void;
|
|
43
|
-
|
|
44
|
-
// Phone Methods
|
|
45
|
-
getSavedPhone: () => string | null;
|
|
46
|
-
savePhone: (phone: string) => void;
|
|
47
|
-
clearSavedPhone: () => void;
|
|
48
|
-
|
|
49
|
-
// OTP Methods - Multi-channel support
|
|
50
|
-
requestOTP: (identifier: string, channel?: 'email' | 'phone', sourceUrl?: string) => Promise<{ success: boolean; message: string }>;
|
|
51
|
-
verifyOTP: (identifier: string, otpCode: string, channel?: 'email' | 'phone', sourceUrl?: string) => Promise<{ success: boolean; message: string; user?: UserProfile }>;
|
|
52
|
-
refreshToken: () => Promise<{ success: boolean; message: string }>;
|
|
53
|
-
logout: () => Promise<void>;
|
|
54
|
-
|
|
55
|
-
// Redirect Methods
|
|
56
|
-
getSavedRedirectUrl: () => string | null;
|
|
57
|
-
saveRedirectUrl: (url: string) => void;
|
|
58
|
-
clearSavedRedirectUrl: () => void;
|
|
59
|
-
getFinalRedirectUrl: () => string;
|
|
60
|
-
useAndClearRedirectUrl: () => string;
|
|
61
|
-
saveCurrentUrlForRedirect: () => void;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Provider props
|
|
65
|
-
export interface AuthProviderProps {
|
|
66
|
-
children: React.ReactNode;
|
|
67
|
-
config?: AuthConfig;
|
|
68
|
-
}
|
package/src/auth/hooks/index.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
export { useAuthRedirectManager } from './useAuthRedirect';
|
|
4
|
-
export { useAuthGuard } from './useAuthGuard';
|
|
5
|
-
export { useSessionStorage } from './useSessionStorage';
|
|
6
|
-
export { useLocalStorage } from './useLocalStorage';
|
|
7
|
-
export { useAuthForm } from './useAuthForm';
|
|
8
|
-
export { useAutoAuth } from './useAutoAuth';
|
|
9
|
-
export { useGithubAuth, type UseGithubAuthOptions, type UseGithubAuthReturn } from './useGithubAuth';
|
|
10
|
-
export {
|
|
11
|
-
getCachedProfile,
|
|
12
|
-
setCachedProfile,
|
|
13
|
-
clearProfileCache,
|
|
14
|
-
hasValidCache,
|
|
15
|
-
getCacheMetadata,
|
|
16
|
-
type ProfileCacheOptions
|
|
17
|
-
} from './useProfileCache';
|