@djangocfg/api 2.1.37 → 2.1.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/api",
3
- "version": "2.1.37",
3
+ "version": "2.1.38",
4
4
  "description": "Auto-generated TypeScript API client with React hooks, SWR integration, and Zod validation for Django REST Framework backends",
5
5
  "keywords": [
6
6
  "django",
@@ -74,7 +74,7 @@
74
74
  "check": "tsc --noEmit"
75
75
  },
76
76
  "peerDependencies": {
77
- "@djangocfg/ui-nextjs": "^2.1.37",
77
+ "@djangocfg/ui-nextjs": "^2.1.38",
78
78
  "consola": "^3.4.2",
79
79
  "next": "^14 || ^15",
80
80
  "p-retry": "^7.0.0",
@@ -85,7 +85,7 @@
85
85
  "devDependencies": {
86
86
  "@types/node": "^24.7.2",
87
87
  "@types/react": "^19.0.0",
88
- "@djangocfg/typescript-config": "^2.1.37",
88
+ "@djangocfg/typescript-config": "^2.1.38",
89
89
  "next": "^15.0.0",
90
90
  "react": "^19.0.0",
91
91
  "tsup": "^8.5.0",
@@ -27,7 +27,6 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
27
27
  // Constants
28
28
  const EMAIL_STORAGE_KEY = 'auth_email';
29
29
  const PHONE_STORAGE_KEY = 'auth_phone';
30
- const AUTH_REDIRECT_KEY = 'auth_redirect_url';
31
30
 
32
31
  const hasValidTokens = (): boolean => {
33
32
  if (typeof window === 'undefined') return false;
@@ -54,10 +53,9 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
54
53
  const pathname = usePathname();
55
54
  const queryParams = useQueryParams();
56
55
 
57
- // Use localStorage hooks for email, phone, and redirect
56
+ // Use localStorage hooks for email and phone
58
57
  const [storedEmail, setStoredEmail, clearStoredEmail] = useLocalStorage<string | null>(EMAIL_STORAGE_KEY, null);
59
58
  const [storedPhone, setStoredPhone, clearStoredPhone] = useLocalStorage<string | null>(PHONE_STORAGE_KEY, null);
60
- const [redirectUrl, setRedirectUrl, clearRedirectUrl] = useLocalStorage<string | null>(AUTH_REDIRECT_KEY, null);
61
59
 
62
60
  // Map AccountsContext profile to UserProfile
63
61
  const user = accounts.profile as UserProfile | null;
@@ -317,10 +315,10 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
317
315
  );
318
316
 
319
317
  const verifyOTP = useCallback(
320
- async (identifier: string, otpCode: string, channel?: 'email' | 'phone', sourceUrl?: string): Promise<{ success: boolean; message: string; user?: UserProfile }> => {
318
+ async (identifier: string, otpCode: string, channel?: 'email' | 'phone', sourceUrl?: string, redirectUrl?: string): Promise<{ success: boolean; message: string; user?: UserProfile }> => {
321
319
  try {
322
- const channelValue = channel === 'phone'
323
- ? Enums.OTPVerifyRequestChannel.PHONE
320
+ const channelValue = channel === 'phone'
321
+ ? Enums.OTPVerifyRequestChannel.PHONE
324
322
  : Enums.OTPVerifyRequestChannel.EMAIL;
325
323
  // AccountsContext automatically saves tokens and refreshes profile
326
324
  const result = await accounts.verifyOTP({
@@ -361,16 +359,10 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
361
359
  Analytics.setUser(String(result.user.id));
362
360
  }
363
361
 
364
- // Handle redirect logic here
362
+ // Handle redirect logic - use provided redirectUrl or fallback to config
365
363
  // 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
- }
364
+ const finalRedirectUrl = redirectUrl || config?.routes?.defaultCallback || defaultRoutes.defaultCallback;
365
+ router.hardPush(finalRedirectUrl);
374
366
 
375
367
  return {
376
368
  success: true,
@@ -392,7 +384,7 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
392
384
  };
393
385
  }
394
386
  },
395
- [setStoredEmail, setStoredPhone, clearStoredEmail, clearStoredPhone, redirectUrl, clearRedirectUrl, config?.routes?.defaultCallback, accounts],
387
+ [setStoredEmail, setStoredPhone, clearStoredEmail, clearStoredPhone, config?.routes?.defaultCallback, accounts, router],
396
388
  );
397
389
 
398
390
  const refreshToken = useCallback(async (): Promise<{ success: boolean; message: string }> => {
@@ -439,18 +431,6 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
439
431
  }
440
432
  }, [clearAuthState, accounts]);
441
433
 
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
434
  const logout = useCallback(async (): Promise<void> => {
455
435
  const performLogout = () => {
456
436
  // Track logout
@@ -489,37 +469,6 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
489
469
  }
490
470
  }, [accounts, config?.routes?.defaultAuthCallback, router]);
491
471
 
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
472
  // Computed: Is admin user (staff or superuser)
524
473
  const isAdminUser = useMemo(() => {
525
474
  return Boolean(user?.is_staff || user?.is_superuser);
@@ -547,12 +496,6 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
547
496
  verifyOTP,
548
497
  refreshToken,
549
498
  logout,
550
- getSavedRedirectUrl,
551
- saveRedirectUrl,
552
- clearSavedRedirectUrl,
553
- getFinalRedirectUrl,
554
- useAndClearRedirectUrl,
555
- saveCurrentUrlForRedirect,
556
499
  }),
557
500
  [
558
501
  user,
@@ -570,12 +513,6 @@ const AuthProviderInternal: React.FC<AuthProviderProps> = ({ children, config })
570
513
  verifyOTP,
571
514
  refreshToken,
572
515
  logout,
573
- getSavedRedirectUrl,
574
- saveRedirectUrl,
575
- clearSavedRedirectUrl,
576
- getFinalRedirectUrl,
577
- useAndClearRedirectUrl,
578
- saveCurrentUrlForRedirect,
579
516
  ],
580
517
  );
581
518
 
@@ -48,17 +48,9 @@ export interface AuthContextType {
48
48
 
49
49
  // OTP Methods - Multi-channel support
50
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 }>;
51
+ verifyOTP: (identifier: string, otpCode: string, channel?: 'email' | 'phone', sourceUrl?: string, redirectUrl?: string) => Promise<{ success: boolean; message: string; user?: UserProfile }>;
52
52
  refreshToken: () => Promise<{ success: boolean; message: string }>;
53
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
54
  }
63
55
 
64
56
  // Provider props
@@ -39,10 +39,14 @@ export interface UseAuthFormOptions {
39
39
  onOTPSuccess?: () => void;
40
40
  onError?: (message: string) => void;
41
41
  sourceUrl: string;
42
+ /** URL to redirect after successful OTP verification */
43
+ redirectUrl?: string;
44
+ /** If true, user must accept terms before submitting. Default: false */
45
+ requireTermsAcceptance?: boolean;
42
46
  }
43
47
 
44
48
  export const useAuthForm = (options: UseAuthFormOptions): AuthFormState & AuthFormHandlers => {
45
- const { onIdentifierSuccess, onOTPSuccess, onError, sourceUrl } = options;
49
+ const { onIdentifierSuccess, onOTPSuccess, onError, sourceUrl, redirectUrl, requireTermsAcceptance = false } = options;
46
50
 
47
51
  // Form state
48
52
  const [identifier, setIdentifier] = useState('');
@@ -147,7 +151,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormState & AuthFo
147
151
  return;
148
152
  }
149
153
 
150
- if (!acceptedTerms) {
154
+ if (requireTermsAcceptance && !acceptedTerms) {
151
155
  const message = 'Please accept the Terms of Service and Privacy Policy';
152
156
  setError(message);
153
157
  onError?.(message);
@@ -187,7 +191,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormState & AuthFo
187
191
 
188
192
  const handleOTPSubmit = useCallback(async (e: React.FormEvent) => {
189
193
  e.preventDefault();
190
-
194
+
191
195
  if (!otp || otp.length < 6) {
192
196
  const message = 'Please enter the 6-digit verification code';
193
197
  setError(message);
@@ -197,10 +201,10 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormState & AuthFo
197
201
 
198
202
  setIsLoading(true);
199
203
  clearError();
200
-
204
+
201
205
  try {
202
- const result = await verifyOTP(identifier, otp, channel, sourceUrl);
203
-
206
+ const result = await verifyOTP(identifier, otp, channel, sourceUrl, redirectUrl);
207
+
204
208
  if (result.success) {
205
209
  // Save identifier on successful login, clear opposite channel
206
210
  if (channel === 'email') {
@@ -222,7 +226,7 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormState & AuthFo
222
226
  } finally {
223
227
  setIsLoading(false);
224
228
  }
225
- }, [identifier, otp, channel, verifyOTP, clearError, setSavedEmail, onOTPSuccess, onError, sourceUrl]);
229
+ }, [identifier, otp, channel, verifyOTP, clearError, setSavedEmail, onOTPSuccess, onError, sourceUrl, redirectUrl]);
226
230
 
227
231
  const handleResendOTP = useCallback(async () => {
228
232
  setIsLoading(true);