@djangocfg/layouts 2.1.10 → 2.1.15

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.
Files changed (107) hide show
  1. package/README.md +53 -161
  2. package/package.json +6 -6
  3. package/src/components/RedirectPage/RedirectPage.tsx +1 -1
  4. package/src/index.ts +0 -6
  5. package/src/layouts/AppLayout/AppLayout.tsx +1 -1
  6. package/src/layouts/AppLayout/BaseApp.tsx +1 -1
  7. package/src/layouts/AuthLayout/AuthContext.tsx +1 -1
  8. package/src/layouts/AuthLayout/OAuthCallback.tsx +1 -1
  9. package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -1
  10. package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
  11. package/src/layouts/PrivateLayout/components/PrivateHeader.tsx +1 -1
  12. package/src/layouts/ProfileLayout/ProfileLayout.tsx +2 -2
  13. package/src/layouts/ProfileLayout/components/AvatarSection.tsx +2 -2
  14. package/src/layouts/ProfileLayout/components/ProfileForm.tsx +2 -2
  15. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
  16. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
  17. package/src/layouts/_components/UserMenu.tsx +1 -1
  18. package/src/layouts/index.ts +0 -2
  19. package/src/snippets/Analytics/useAnalytics.ts +1 -1
  20. package/src/snippets/index.ts +0 -3
  21. package/src/auth/README.md +0 -962
  22. package/src/auth/context/AccountsContext.tsx +0 -240
  23. package/src/auth/context/AuthContext.tsx +0 -604
  24. package/src/auth/context/index.ts +0 -4
  25. package/src/auth/context/types.ts +0 -68
  26. package/src/auth/hooks/index.ts +0 -17
  27. package/src/auth/hooks/useAuthForm.ts +0 -332
  28. package/src/auth/hooks/useAuthGuard.ts +0 -25
  29. package/src/auth/hooks/useAuthRedirect.ts +0 -51
  30. package/src/auth/hooks/useAutoAuth.ts +0 -49
  31. package/src/auth/hooks/useGithubAuth.ts +0 -184
  32. package/src/auth/hooks/useLocalStorage.ts +0 -214
  33. package/src/auth/hooks/useProfileCache.ts +0 -146
  34. package/src/auth/hooks/useSessionStorage.ts +0 -189
  35. package/src/auth/index.ts +0 -10
  36. package/src/auth/middlewares/index.ts +0 -1
  37. package/src/auth/middlewares/proxy.ts +0 -32
  38. package/src/auth/server.ts +0 -6
  39. package/src/auth/utils/errors.ts +0 -34
  40. package/src/auth/utils/index.ts +0 -2
  41. package/src/auth/utils/validation.ts +0 -14
  42. package/src/contexts/LeadsContext.tsx +0 -156
  43. package/src/contexts/NewsletterContext.tsx +0 -263
  44. package/src/contexts/SupportContext.tsx +0 -256
  45. package/src/contexts/index.ts +0 -59
  46. package/src/contexts/knowbase/ChatContext.tsx +0 -174
  47. package/src/contexts/knowbase/DocumentsContext.tsx +0 -304
  48. package/src/contexts/knowbase/SessionsContext.tsx +0 -174
  49. package/src/contexts/knowbase/index.ts +0 -61
  50. package/src/contexts/payments/BalancesContext.tsx +0 -65
  51. package/src/contexts/payments/CurrenciesContext.tsx +0 -66
  52. package/src/contexts/payments/OverviewContext.tsx +0 -174
  53. package/src/contexts/payments/PaymentsContext.tsx +0 -132
  54. package/src/contexts/payments/README.md +0 -201
  55. package/src/contexts/payments/RootPaymentsContext.tsx +0 -68
  56. package/src/contexts/payments/index.ts +0 -50
  57. package/src/layouts/PaymentsLayout/PaymentsLayout.tsx +0 -92
  58. package/src/layouts/PaymentsLayout/components/CreatePaymentDialog.tsx +0 -291
  59. package/src/layouts/PaymentsLayout/components/PaymentDetailsDialog.tsx +0 -290
  60. package/src/layouts/PaymentsLayout/components/index.ts +0 -2
  61. package/src/layouts/PaymentsLayout/events.ts +0 -47
  62. package/src/layouts/PaymentsLayout/index.ts +0 -16
  63. package/src/layouts/PaymentsLayout/types.ts +0 -6
  64. package/src/layouts/PaymentsLayout/views/overview/components/BalanceCard.tsx +0 -128
  65. package/src/layouts/PaymentsLayout/views/overview/components/RecentPayments.tsx +0 -142
  66. package/src/layouts/PaymentsLayout/views/overview/components/index.ts +0 -2
  67. package/src/layouts/PaymentsLayout/views/overview/index.tsx +0 -20
  68. package/src/layouts/PaymentsLayout/views/payments/components/PaymentsList.tsx +0 -276
  69. package/src/layouts/PaymentsLayout/views/payments/components/index.ts +0 -1
  70. package/src/layouts/PaymentsLayout/views/payments/index.tsx +0 -17
  71. package/src/layouts/PaymentsLayout/views/transactions/components/TransactionsList.tsx +0 -273
  72. package/src/layouts/PaymentsLayout/views/transactions/components/index.ts +0 -1
  73. package/src/layouts/PaymentsLayout/views/transactions/index.tsx +0 -17
  74. package/src/layouts/SupportLayout/README.md +0 -91
  75. package/src/layouts/SupportLayout/SupportLayout.tsx +0 -179
  76. package/src/layouts/SupportLayout/components/CreateTicketDialog.tsx +0 -155
  77. package/src/layouts/SupportLayout/components/MessageInput.tsx +0 -92
  78. package/src/layouts/SupportLayout/components/MessageList.tsx +0 -314
  79. package/src/layouts/SupportLayout/components/TicketCard.tsx +0 -96
  80. package/src/layouts/SupportLayout/components/TicketList.tsx +0 -153
  81. package/src/layouts/SupportLayout/components/index.ts +0 -6
  82. package/src/layouts/SupportLayout/context/SupportLayoutContext.tsx +0 -263
  83. package/src/layouts/SupportLayout/context/index.ts +0 -2
  84. package/src/layouts/SupportLayout/events.ts +0 -33
  85. package/src/layouts/SupportLayout/hooks/index.ts +0 -2
  86. package/src/layouts/SupportLayout/hooks/useInfiniteMessages.ts +0 -119
  87. package/src/layouts/SupportLayout/hooks/useInfiniteTickets.ts +0 -92
  88. package/src/layouts/SupportLayout/index.ts +0 -8
  89. package/src/layouts/SupportLayout/types.ts +0 -21
  90. package/src/snippets/Chat/ChatUIContext.tsx +0 -110
  91. package/src/snippets/Chat/ChatWidget.tsx +0 -476
  92. package/src/snippets/Chat/README.md +0 -122
  93. package/src/snippets/Chat/components/MessageInput.tsx +0 -124
  94. package/src/snippets/Chat/components/MessageList.tsx +0 -169
  95. package/src/snippets/Chat/components/SessionList.tsx +0 -192
  96. package/src/snippets/Chat/components/index.ts +0 -9
  97. package/src/snippets/Chat/hooks/index.ts +0 -6
  98. package/src/snippets/Chat/hooks/useInfiniteSessions.ts +0 -82
  99. package/src/snippets/Chat/index.tsx +0 -45
  100. package/src/snippets/Chat/types.ts +0 -80
  101. package/src/snippets/ContactForm/ContactForm.tsx +0 -346
  102. package/src/snippets/ContactForm/ContactFormProvider.tsx +0 -153
  103. package/src/snippets/ContactForm/ContactInfo.tsx +0 -114
  104. package/src/snippets/ContactForm/ContactPage.tsx +0 -131
  105. package/src/snippets/ContactForm/dynamic.tsx +0 -55
  106. package/src/snippets/ContactForm/index.ts +0 -34
  107. 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
- }
@@ -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';