@djangocfg/api 2.1.37 → 2.1.39
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 +94 -56
- package/dist/auth.cjs +12 -65
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +5 -7
- package/dist/auth.d.ts +5 -7
- package/dist/auth.mjs +12 -65
- package/dist/auth.mjs.map +1 -1
- package/package.json +3 -3
- package/src/auth/context/AuthContext.tsx +8 -71
- package/src/auth/context/types.ts +1 -9
- package/src/auth/hooks/useAuthForm.ts +11 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/api",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.39",
|
|
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.
|
|
77
|
+
"@djangocfg/ui-nextjs": "^2.1.39",
|
|
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.
|
|
88
|
+
"@djangocfg/typescript-config": "^2.1.39",
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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);
|