@djangocfg/layouts 2.1.9 → 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/McpChat/hooks/useAIChat.ts +16 -3
- 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,332 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
import { useAuth } from '../context';
|
|
6
|
-
import { useAutoAuth } from './useAutoAuth';
|
|
7
|
-
import { useLocalStorage } from './useLocalStorage';
|
|
8
|
-
import { authLogger } from '../../utils/logger';
|
|
9
|
-
|
|
10
|
-
export interface AuthFormState {
|
|
11
|
-
identifier: string; // Email or phone number
|
|
12
|
-
channel: 'email' | 'phone';
|
|
13
|
-
otp: string;
|
|
14
|
-
isLoading: boolean;
|
|
15
|
-
acceptedTerms: boolean;
|
|
16
|
-
step: 'identifier' | 'otp';
|
|
17
|
-
error: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface AuthFormHandlers {
|
|
21
|
-
setIdentifier: (identifier: string) => void;
|
|
22
|
-
setChannel: (channel: 'email' | 'phone') => void;
|
|
23
|
-
setOtp: (otp: string) => void;
|
|
24
|
-
setAcceptedTerms: (accepted: boolean) => void;
|
|
25
|
-
setError: (error: string) => void;
|
|
26
|
-
clearError: () => void;
|
|
27
|
-
handleIdentifierSubmit: (e: React.FormEvent) => Promise<void>;
|
|
28
|
-
handleOTPSubmit: (e: React.FormEvent) => Promise<void>;
|
|
29
|
-
handleResendOTP: () => Promise<void>;
|
|
30
|
-
handleBackToIdentifier: () => void;
|
|
31
|
-
forceOTPStep: () => void;
|
|
32
|
-
// Utility methods
|
|
33
|
-
detectChannelFromIdentifier: (identifier: string) => 'email' | 'phone' | null;
|
|
34
|
-
validateIdentifier: (identifier: string, channel?: 'email' | 'phone') => boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface UseAuthFormOptions {
|
|
38
|
-
onIdentifierSuccess?: (identifier: string, channel: 'email' | 'phone') => void;
|
|
39
|
-
onOTPSuccess?: () => void;
|
|
40
|
-
onError?: (message: string) => void;
|
|
41
|
-
sourceUrl: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export const useAuthForm = (options: UseAuthFormOptions): AuthFormState & AuthFormHandlers => {
|
|
45
|
-
const { onIdentifierSuccess, onOTPSuccess, onError, sourceUrl } = options;
|
|
46
|
-
|
|
47
|
-
// Form state
|
|
48
|
-
const [identifier, setIdentifier] = useState('');
|
|
49
|
-
const [channel, setChannel] = useState<'email' | 'phone'>('email');
|
|
50
|
-
const [otp, setOtp] = useState('');
|
|
51
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
52
|
-
const [acceptedTerms, setAcceptedTerms] = useState(false);
|
|
53
|
-
const [step, setStep] = useState<'identifier' | 'otp'>('identifier');
|
|
54
|
-
const [error, setError] = useState('');
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Auth hooks
|
|
59
|
-
const { requestOTP, verifyOTP, getSavedEmail, saveEmail, getSavedPhone, savePhone } = useAuth();
|
|
60
|
-
const [savedTermsAccepted, setSavedTermsAccepted] = useLocalStorage('auth_terms_accepted', false);
|
|
61
|
-
const [savedEmail, setSavedEmail] = useLocalStorage('auth_email', '');
|
|
62
|
-
const [savedPhone, setSavedPhone] = useLocalStorage('auth_phone', '');
|
|
63
|
-
|
|
64
|
-
// Utility functions
|
|
65
|
-
const detectChannelFromIdentifier = useCallback((identifier: string): 'email' | 'phone' | null => {
|
|
66
|
-
if (!identifier) return null;
|
|
67
|
-
|
|
68
|
-
// Email detection
|
|
69
|
-
if (identifier.includes('@')) {
|
|
70
|
-
return 'email';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Phone detection (starts with + and contains digits)
|
|
74
|
-
if (identifier.startsWith('+') && /^\+[1-9]\d{6,14}$/.test(identifier)) {
|
|
75
|
-
return 'phone';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return null;
|
|
79
|
-
}, []);
|
|
80
|
-
|
|
81
|
-
const validateIdentifier = useCallback((identifier: string, channelType?: 'email' | 'phone'): boolean => {
|
|
82
|
-
if (!identifier) return false;
|
|
83
|
-
|
|
84
|
-
const detectedChannel = channelType || detectChannelFromIdentifier(identifier);
|
|
85
|
-
|
|
86
|
-
if (detectedChannel === 'email') {
|
|
87
|
-
// Basic email validation
|
|
88
|
-
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(identifier);
|
|
89
|
-
} else if (detectedChannel === 'phone') {
|
|
90
|
-
// E.164 phone validation
|
|
91
|
-
return /^\+[1-9]\d{6,14}$/.test(identifier);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return false;
|
|
95
|
-
}, [detectChannelFromIdentifier]);
|
|
96
|
-
|
|
97
|
-
// Load saved data on mount
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
const authSavedEmail = getSavedEmail();
|
|
100
|
-
const authSavedPhone = getSavedPhone();
|
|
101
|
-
|
|
102
|
-
// Prioritize phone over email if both exist
|
|
103
|
-
if (authSavedPhone) {
|
|
104
|
-
setIdentifier(authSavedPhone);
|
|
105
|
-
setChannel('phone');
|
|
106
|
-
} else if (authSavedEmail) {
|
|
107
|
-
setIdentifier(authSavedEmail);
|
|
108
|
-
setChannel('email');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (savedTermsAccepted) {
|
|
112
|
-
setAcceptedTerms(savedTermsAccepted);
|
|
113
|
-
}
|
|
114
|
-
}, [getSavedEmail, getSavedPhone, savedTermsAccepted]);
|
|
115
|
-
|
|
116
|
-
// Auto-detect channel when identifier changes
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
if (identifier) {
|
|
119
|
-
const detectedChannel = detectChannelFromIdentifier(identifier);
|
|
120
|
-
if (detectedChannel && detectedChannel !== channel) {
|
|
121
|
-
setChannel(detectedChannel);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}, [identifier, channel, detectChannelFromIdentifier]);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const clearError = useCallback(() => setError(''), []);
|
|
129
|
-
|
|
130
|
-
const handleIdentifierSubmit = useCallback(async (e: React.FormEvent) => {
|
|
131
|
-
e.preventDefault();
|
|
132
|
-
|
|
133
|
-
if (!identifier) {
|
|
134
|
-
const message = channel === 'phone' ? 'Please enter your phone number' : 'Please enter your email address';
|
|
135
|
-
setError(message);
|
|
136
|
-
onError?.(message);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Validate identifier format
|
|
141
|
-
if (!validateIdentifier(identifier, channel)) {
|
|
142
|
-
const message = channel === 'phone'
|
|
143
|
-
? 'Please enter a valid phone number (e.g., +1234567890)'
|
|
144
|
-
: 'Please enter a valid email address';
|
|
145
|
-
setError(message);
|
|
146
|
-
onError?.(message);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!acceptedTerms) {
|
|
151
|
-
const message = 'Please accept the Terms of Service and Privacy Policy';
|
|
152
|
-
setError(message);
|
|
153
|
-
onError?.(message);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
setIsLoading(true);
|
|
158
|
-
clearError();
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
const result = await requestOTP(identifier, channel, sourceUrl);
|
|
162
|
-
|
|
163
|
-
if (result.success) {
|
|
164
|
-
// Save identifier and terms acceptance on successful request, clear opposite channel
|
|
165
|
-
if (channel === 'email') {
|
|
166
|
-
saveEmail(identifier);
|
|
167
|
-
setSavedPhone(''); // Clear phone storage
|
|
168
|
-
} else if (channel === 'phone') {
|
|
169
|
-
savePhone(identifier);
|
|
170
|
-
setSavedEmail(''); // Clear email storage
|
|
171
|
-
}
|
|
172
|
-
setSavedTermsAccepted(true);
|
|
173
|
-
setStep('otp');
|
|
174
|
-
onIdentifierSuccess?.(identifier, channel);
|
|
175
|
-
} else {
|
|
176
|
-
setError(result.message);
|
|
177
|
-
onError?.(result.message);
|
|
178
|
-
}
|
|
179
|
-
} catch (error) {
|
|
180
|
-
const message = 'An unexpected error occurred';
|
|
181
|
-
setError(message);
|
|
182
|
-
onError?.(message);
|
|
183
|
-
} finally {
|
|
184
|
-
setIsLoading(false);
|
|
185
|
-
}
|
|
186
|
-
}, [identifier, channel, acceptedTerms, validateIdentifier, requestOTP, saveEmail, clearError, setSavedTermsAccepted, onIdentifierSuccess, onError, sourceUrl]);
|
|
187
|
-
|
|
188
|
-
const handleOTPSubmit = useCallback(async (e: React.FormEvent) => {
|
|
189
|
-
e.preventDefault();
|
|
190
|
-
|
|
191
|
-
if (!otp || otp.length < 6) {
|
|
192
|
-
const message = 'Please enter the 6-digit verification code';
|
|
193
|
-
setError(message);
|
|
194
|
-
onError?.(message);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
setIsLoading(true);
|
|
199
|
-
clearError();
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
const result = await verifyOTP(identifier, otp, channel, sourceUrl);
|
|
203
|
-
|
|
204
|
-
if (result.success) {
|
|
205
|
-
// Save identifier on successful login, clear opposite channel
|
|
206
|
-
if (channel === 'email') {
|
|
207
|
-
setSavedEmail(identifier);
|
|
208
|
-
setSavedPhone(''); // Clear phone storage
|
|
209
|
-
} else if (channel === 'phone') {
|
|
210
|
-
setSavedPhone(identifier);
|
|
211
|
-
setSavedEmail(''); // Clear email storage
|
|
212
|
-
}
|
|
213
|
-
onOTPSuccess?.();
|
|
214
|
-
} else {
|
|
215
|
-
setError(result.message);
|
|
216
|
-
onError?.(result.message);
|
|
217
|
-
}
|
|
218
|
-
} catch (error) {
|
|
219
|
-
const message = 'An unexpected error occurred';
|
|
220
|
-
setError(message);
|
|
221
|
-
onError?.(message);
|
|
222
|
-
} finally {
|
|
223
|
-
setIsLoading(false);
|
|
224
|
-
}
|
|
225
|
-
}, [identifier, otp, channel, verifyOTP, clearError, setSavedEmail, onOTPSuccess, onError, sourceUrl]);
|
|
226
|
-
|
|
227
|
-
const handleResendOTP = useCallback(async () => {
|
|
228
|
-
setIsLoading(true);
|
|
229
|
-
clearError();
|
|
230
|
-
|
|
231
|
-
try {
|
|
232
|
-
const result = await requestOTP(identifier, channel, sourceUrl);
|
|
233
|
-
|
|
234
|
-
if (result.success) {
|
|
235
|
-
// Save identifier and clear OTP input, clear opposite channel
|
|
236
|
-
if (channel === 'email') {
|
|
237
|
-
saveEmail(identifier);
|
|
238
|
-
setSavedPhone(''); // Clear phone storage
|
|
239
|
-
} else if (channel === 'phone') {
|
|
240
|
-
savePhone(identifier);
|
|
241
|
-
setSavedEmail(''); // Clear email storage
|
|
242
|
-
}
|
|
243
|
-
setOtp('');
|
|
244
|
-
} else {
|
|
245
|
-
setError(result.message);
|
|
246
|
-
onError?.(result.message);
|
|
247
|
-
}
|
|
248
|
-
} catch (error) {
|
|
249
|
-
const message = 'Failed to resend verification code';
|
|
250
|
-
setError(message);
|
|
251
|
-
onError?.(message);
|
|
252
|
-
} finally {
|
|
253
|
-
setIsLoading(false);
|
|
254
|
-
}
|
|
255
|
-
}, [identifier, channel, requestOTP, saveEmail, clearError, setOtp, onError, sourceUrl]);
|
|
256
|
-
|
|
257
|
-
const handleBackToIdentifier = useCallback(() => {
|
|
258
|
-
setStep('identifier');
|
|
259
|
-
clearError();
|
|
260
|
-
}, [clearError]);
|
|
261
|
-
|
|
262
|
-
const forceOTPStep = useCallback(() => {
|
|
263
|
-
setStep('otp');
|
|
264
|
-
clearError();
|
|
265
|
-
}, [clearError]);
|
|
266
|
-
|
|
267
|
-
const handleAcceptedTermsChange = useCallback((checked: boolean) => {
|
|
268
|
-
setAcceptedTerms(checked);
|
|
269
|
-
setSavedTermsAccepted(checked);
|
|
270
|
-
}, [setSavedTermsAccepted]);
|
|
271
|
-
|
|
272
|
-
// Auto-detect OTP from URL query parameters
|
|
273
|
-
useAutoAuth({
|
|
274
|
-
onOTPDetected: (otp: string) => {
|
|
275
|
-
authLogger.info('OTP detected, auto-submitting');
|
|
276
|
-
|
|
277
|
-
// Get saved identifier from auth context
|
|
278
|
-
const savedEmail = getSavedEmail();
|
|
279
|
-
const savedPhone = getSavedPhone();
|
|
280
|
-
|
|
281
|
-
// Prioritize phone over email if both exist
|
|
282
|
-
if (savedPhone) {
|
|
283
|
-
setIdentifier(savedPhone);
|
|
284
|
-
setChannel('phone');
|
|
285
|
-
} else if (savedEmail) {
|
|
286
|
-
setIdentifier(savedEmail);
|
|
287
|
-
setChannel('email');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Set OTP and force OTP step
|
|
291
|
-
setOtp(otp);
|
|
292
|
-
setStep('otp');
|
|
293
|
-
|
|
294
|
-
// Auto-submit after a short delay to ensure state is updated
|
|
295
|
-
setTimeout(() => {
|
|
296
|
-
const fakeEvent = { preventDefault: () => {} } as React.FormEvent;
|
|
297
|
-
handleOTPSubmit(fakeEvent);
|
|
298
|
-
}, 200);
|
|
299
|
-
},
|
|
300
|
-
cleanupUrl: true,
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
// Form state
|
|
305
|
-
identifier,
|
|
306
|
-
channel,
|
|
307
|
-
otp,
|
|
308
|
-
isLoading,
|
|
309
|
-
acceptedTerms,
|
|
310
|
-
step,
|
|
311
|
-
error,
|
|
312
|
-
|
|
313
|
-
// Form handlers
|
|
314
|
-
setIdentifier,
|
|
315
|
-
setChannel,
|
|
316
|
-
setOtp,
|
|
317
|
-
setAcceptedTerms: handleAcceptedTermsChange,
|
|
318
|
-
setError,
|
|
319
|
-
clearError,
|
|
320
|
-
|
|
321
|
-
// Auth handlers
|
|
322
|
-
handleIdentifierSubmit,
|
|
323
|
-
handleOTPSubmit,
|
|
324
|
-
handleResendOTP,
|
|
325
|
-
handleBackToIdentifier,
|
|
326
|
-
forceOTPStep,
|
|
327
|
-
|
|
328
|
-
// Utility methods
|
|
329
|
-
detectChannelFromIdentifier,
|
|
330
|
-
validateIdentifier,
|
|
331
|
-
};
|
|
332
|
-
};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import { useEffect } from 'react';
|
|
4
|
-
|
|
5
|
-
import { useAuth } from '../context';
|
|
6
|
-
import { useCfgRouter } from '@djangocfg/ui-nextjs/hooks';
|
|
7
|
-
|
|
8
|
-
interface UseAuthGuardOptions {
|
|
9
|
-
redirectTo?: string;
|
|
10
|
-
requireAuth?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const useAuthGuard = (options: UseAuthGuardOptions = {}) => {
|
|
14
|
-
const { redirectTo = '/auth', requireAuth = true } = options;
|
|
15
|
-
const { isAuthenticated, isLoading } = useAuth();
|
|
16
|
-
const router = useCfgRouter();
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (!isLoading && requireAuth && !isAuthenticated) {
|
|
20
|
-
router.push(redirectTo);
|
|
21
|
-
}
|
|
22
|
-
}, [isAuthenticated, isLoading, router, redirectTo, requireAuth]);
|
|
23
|
-
|
|
24
|
-
return { isAuthenticated, isLoading };
|
|
25
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { useSessionStorage } from './useSessionStorage';
|
|
2
|
-
|
|
3
|
-
const AUTH_REDIRECT_KEY = 'auth_redirect_url';
|
|
4
|
-
|
|
5
|
-
export interface AuthRedirectOptions {
|
|
6
|
-
fallbackUrl?: string;
|
|
7
|
-
clearOnUse?: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const useAuthRedirectManager = (options: AuthRedirectOptions = {}) => {
|
|
11
|
-
const { fallbackUrl = '/dashboard', clearOnUse = true } = options;
|
|
12
|
-
const [redirectUrl, setRedirectUrl, removeRedirectUrl] = useSessionStorage<string>(AUTH_REDIRECT_KEY, '');
|
|
13
|
-
|
|
14
|
-
const setRedirect = (url: string) => {
|
|
15
|
-
setRedirectUrl(url);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const getRedirect = () => {
|
|
19
|
-
return redirectUrl;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const clearRedirect = () => {
|
|
23
|
-
removeRedirectUrl();
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const hasRedirect = () => {
|
|
27
|
-
return redirectUrl.length > 0;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const getFinalRedirectUrl = () => {
|
|
31
|
-
return redirectUrl || fallbackUrl;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const useAndClearRedirect = () => {
|
|
35
|
-
const finalUrl = getFinalRedirectUrl();
|
|
36
|
-
if (clearOnUse) {
|
|
37
|
-
clearRedirect();
|
|
38
|
-
}
|
|
39
|
-
return finalUrl;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
redirectUrl,
|
|
44
|
-
setRedirect,
|
|
45
|
-
getRedirect,
|
|
46
|
-
clearRedirect,
|
|
47
|
-
hasRedirect,
|
|
48
|
-
getFinalRedirectUrl,
|
|
49
|
-
useAndClearRedirect
|
|
50
|
-
};
|
|
51
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect } from 'react';
|
|
4
|
-
import { usePathname } from 'next/navigation';
|
|
5
|
-
import { useQueryParams, useCfgRouter } from '@djangocfg/ui-nextjs/hooks';
|
|
6
|
-
import { authLogger } from '../../utils/logger';
|
|
7
|
-
|
|
8
|
-
export interface UseAutoAuthOptions {
|
|
9
|
-
onOTPDetected?: (otp: string) => void;
|
|
10
|
-
cleanupUrl?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Hook for automatic authentication from URL query parameters
|
|
15
|
-
* Detects OTP from URL and triggers callback
|
|
16
|
-
*/
|
|
17
|
-
export const useAutoAuth = (options: UseAutoAuthOptions = {}) => {
|
|
18
|
-
const { onOTPDetected, cleanupUrl = true } = options;
|
|
19
|
-
const queryParams = useQueryParams();
|
|
20
|
-
const pathname = usePathname();
|
|
21
|
-
const router = useCfgRouter();
|
|
22
|
-
|
|
23
|
-
const isReady = !!pathname && !!queryParams.get('otp');
|
|
24
|
-
const hasOTP = !!(queryParams.get('otp'));
|
|
25
|
-
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (!isReady) return;
|
|
28
|
-
|
|
29
|
-
const queryOtp = queryParams.get('otp') as string;
|
|
30
|
-
|
|
31
|
-
// Handle OTP detection
|
|
32
|
-
if (queryOtp && typeof queryOtp === 'string' && queryOtp.length === 6) {
|
|
33
|
-
authLogger.info('OTP detected in URL:', queryOtp);
|
|
34
|
-
onOTPDetected?.(queryOtp);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Clean up URL to remove sensitive params for security
|
|
38
|
-
if (cleanupUrl && queryOtp) {
|
|
39
|
-
const cleanQuery = Object.fromEntries(queryParams.entries());
|
|
40
|
-
delete cleanQuery.otp;
|
|
41
|
-
router.push(`${pathname}?${new URLSearchParams(cleanQuery).toString()}`);
|
|
42
|
-
}
|
|
43
|
-
}, [pathname, queryParams, onOTPDetected, cleanupUrl, router]);
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
isReady,
|
|
47
|
-
hasOTP,
|
|
48
|
-
};
|
|
49
|
-
};
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useCallback, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
import { api } from '@djangocfg/api';
|
|
6
|
-
import { useCfgRouter } from '@djangocfg/ui-nextjs/hooks';
|
|
7
|
-
import { authLogger } from '../../utils/logger';
|
|
8
|
-
import { Analytics, AnalyticsEvent, AnalyticsCategory } from '../../snippets/Analytics';
|
|
9
|
-
|
|
10
|
-
export interface UseGithubAuthOptions {
|
|
11
|
-
sourceUrl?: string;
|
|
12
|
-
onSuccess?: (user: any, isNewUser: boolean) => void;
|
|
13
|
-
onError?: (error: string) => void;
|
|
14
|
-
redirectUrl?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface UseGithubAuthReturn {
|
|
18
|
-
isLoading: boolean;
|
|
19
|
-
error: string | null;
|
|
20
|
-
startGithubAuth: () => Promise<void>;
|
|
21
|
-
handleGithubCallback: (code: string, state: string) => Promise<void>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Hook for GitHub OAuth authentication flow.
|
|
26
|
-
*
|
|
27
|
-
* Usage:
|
|
28
|
-
* 1. Call startGithubAuth() to redirect user to GitHub
|
|
29
|
-
* 2. After GitHub redirects back, call handleGithubCallback(code, state)
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```tsx
|
|
33
|
-
* const { isLoading, error, startGithubAuth } = useGithubAuth({
|
|
34
|
-
* onSuccess: (user) => router.push('/dashboard'),
|
|
35
|
-
* onError: (error) => console.error(error),
|
|
36
|
-
* });
|
|
37
|
-
*
|
|
38
|
-
* <Button onClick={startGithubAuth} disabled={isLoading}>
|
|
39
|
-
* Continue with GitHub
|
|
40
|
-
* </Button>
|
|
41
|
-
* ```
|
|
42
|
-
*/
|
|
43
|
-
export const useGithubAuth = (options: UseGithubAuthOptions = {}): UseGithubAuthReturn => {
|
|
44
|
-
const { sourceUrl, onSuccess, onError, redirectUrl } = options;
|
|
45
|
-
const router = useCfgRouter();
|
|
46
|
-
|
|
47
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
48
|
-
const [error, setError] = useState<string | null>(null);
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Start GitHub OAuth flow - redirects user to GitHub authorization page.
|
|
52
|
-
*/
|
|
53
|
-
const startGithubAuth = useCallback(async () => {
|
|
54
|
-
setIsLoading(true);
|
|
55
|
-
setError(null);
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
authLogger.info('Starting GitHub OAuth flow...');
|
|
59
|
-
|
|
60
|
-
// Track OAuth start
|
|
61
|
-
Analytics.event(AnalyticsEvent.AUTH_OAUTH_START, {
|
|
62
|
-
category: AnalyticsCategory.AUTH,
|
|
63
|
-
label: 'github',
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// Call API to get authorization URL
|
|
67
|
-
// The API will auto-generate redirect_uri from config if not provided
|
|
68
|
-
const response = await api.cfg_oauth.accountsOauthGithubAuthorizeCreate({
|
|
69
|
-
source_url: sourceUrl || (typeof window !== 'undefined' ? window.location.href : ''),
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
if (!response.authorization_url) {
|
|
73
|
-
throw new Error('Failed to get authorization URL');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
authLogger.info('Redirecting to GitHub...', response.authorization_url);
|
|
77
|
-
|
|
78
|
-
// Store state in sessionStorage for verification on callback
|
|
79
|
-
if (typeof window !== 'undefined') {
|
|
80
|
-
sessionStorage.setItem('oauth_state', response.state);
|
|
81
|
-
sessionStorage.setItem('oauth_provider', 'github');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Redirect to GitHub
|
|
85
|
-
window.location.href = response.authorization_url;
|
|
86
|
-
|
|
87
|
-
} catch (err) {
|
|
88
|
-
const errorMessage = err instanceof Error ? err.message : 'Failed to start GitHub authentication';
|
|
89
|
-
authLogger.error('GitHub OAuth start error:', err);
|
|
90
|
-
setError(errorMessage);
|
|
91
|
-
onError?.(errorMessage);
|
|
92
|
-
|
|
93
|
-
// Track OAuth error
|
|
94
|
-
Analytics.event(AnalyticsEvent.AUTH_OAUTH_FAIL, {
|
|
95
|
-
category: AnalyticsCategory.AUTH,
|
|
96
|
-
label: 'github',
|
|
97
|
-
});
|
|
98
|
-
} finally {
|
|
99
|
-
setIsLoading(false);
|
|
100
|
-
}
|
|
101
|
-
}, [sourceUrl, onError]);
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Handle GitHub OAuth callback - exchanges code for JWT tokens.
|
|
105
|
-
*
|
|
106
|
-
* @param code - Authorization code from GitHub callback
|
|
107
|
-
* @param state - State token for CSRF verification
|
|
108
|
-
*/
|
|
109
|
-
const handleGithubCallback = useCallback(async (code: string, state: string) => {
|
|
110
|
-
setIsLoading(true);
|
|
111
|
-
setError(null);
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
authLogger.info('Processing GitHub OAuth callback...');
|
|
115
|
-
|
|
116
|
-
// Verify state matches what we stored
|
|
117
|
-
if (typeof window !== 'undefined') {
|
|
118
|
-
const storedState = sessionStorage.getItem('oauth_state');
|
|
119
|
-
if (storedState && storedState !== state) {
|
|
120
|
-
throw new Error('Invalid OAuth state - possible CSRF attack');
|
|
121
|
-
}
|
|
122
|
-
// Clear stored state
|
|
123
|
-
sessionStorage.removeItem('oauth_state');
|
|
124
|
-
sessionStorage.removeItem('oauth_provider');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Exchange code for tokens
|
|
128
|
-
// The API will auto-generate redirect_uri from config if not provided
|
|
129
|
-
const response = await api.cfg_oauth.accountsOauthGithubCallbackCreate({
|
|
130
|
-
code,
|
|
131
|
-
state,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
if (!response.access || !response.refresh) {
|
|
135
|
-
throw new Error('Invalid response from OAuth callback');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
authLogger.info('GitHub OAuth successful, user:', response.user);
|
|
139
|
-
|
|
140
|
-
// Save tokens using API client
|
|
141
|
-
api.setToken(response.access, response.refresh);
|
|
142
|
-
|
|
143
|
-
// Track successful OAuth
|
|
144
|
-
Analytics.event(AnalyticsEvent.AUTH_LOGIN_SUCCESS, {
|
|
145
|
-
category: AnalyticsCategory.AUTH,
|
|
146
|
-
label: 'github',
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Set user ID for future tracking
|
|
150
|
-
if (response.user?.id) {
|
|
151
|
-
Analytics.setUser(String(response.user.id));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Call success callback
|
|
155
|
-
onSuccess?.(response.user, response.is_new_user || false);
|
|
156
|
-
|
|
157
|
-
// Redirect to dashboard or specified URL
|
|
158
|
-
// Use hardPush for full page reload - ensures all React contexts reinitialize
|
|
159
|
-
const finalRedirectUrl = redirectUrl || '/dashboard';
|
|
160
|
-
router.hardPush(finalRedirectUrl);
|
|
161
|
-
|
|
162
|
-
} catch (err) {
|
|
163
|
-
const errorMessage = err instanceof Error ? err.message : 'GitHub authentication failed';
|
|
164
|
-
authLogger.error('GitHub OAuth callback error:', err);
|
|
165
|
-
setError(errorMessage);
|
|
166
|
-
onError?.(errorMessage);
|
|
167
|
-
|
|
168
|
-
// Track OAuth error
|
|
169
|
-
Analytics.event(AnalyticsEvent.AUTH_OAUTH_FAIL, {
|
|
170
|
-
category: AnalyticsCategory.AUTH,
|
|
171
|
-
label: 'github',
|
|
172
|
-
});
|
|
173
|
-
} finally {
|
|
174
|
-
setIsLoading(false);
|
|
175
|
-
}
|
|
176
|
-
}, [onSuccess, onError, redirectUrl, router]);
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
isLoading,
|
|
180
|
-
error,
|
|
181
|
-
startGithubAuth,
|
|
182
|
-
handleGithubCallback,
|
|
183
|
-
};
|
|
184
|
-
};
|