@djangocfg/layouts 2.1.109 → 2.1.110

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 (38) hide show
  1. package/package.json +14 -14
  2. package/src/layouts/AuthLayout/AuthLayout.tsx +92 -20
  3. package/src/layouts/AuthLayout/components/index.ts +11 -7
  4. package/src/layouts/AuthLayout/components/oauth/index.ts +0 -1
  5. package/src/layouts/AuthLayout/components/shared/AuthButton.tsx +35 -0
  6. package/src/layouts/AuthLayout/components/shared/AuthContainer.tsx +56 -0
  7. package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +22 -0
  8. package/src/layouts/AuthLayout/components/shared/AuthError.tsx +26 -0
  9. package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +47 -0
  10. package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +53 -0
  11. package/src/layouts/AuthLayout/components/shared/AuthLink.tsx +41 -0
  12. package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +42 -0
  13. package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +48 -0
  14. package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +57 -0
  15. package/src/layouts/AuthLayout/components/shared/index.ts +21 -0
  16. package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +171 -0
  17. package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +114 -0
  18. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +70 -0
  19. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +24 -0
  20. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +125 -0
  21. package/src/layouts/AuthLayout/components/steps/SetupStep/index.tsx +91 -0
  22. package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +92 -0
  23. package/src/layouts/AuthLayout/components/steps/index.ts +6 -0
  24. package/src/layouts/AuthLayout/constants.ts +24 -0
  25. package/src/layouts/AuthLayout/content.ts +78 -0
  26. package/src/layouts/AuthLayout/hooks/index.ts +1 -0
  27. package/src/layouts/AuthLayout/hooks/useCopyToClipboard.ts +37 -0
  28. package/src/layouts/AuthLayout/index.ts +9 -5
  29. package/src/layouts/AuthLayout/styles/auth.css +578 -0
  30. package/src/layouts/ProfileLayout/ProfileLayout.tsx +2 -2
  31. package/src/layouts/ProfileLayout/components/TwoFactorSection.tsx +2 -2
  32. package/src/layouts/AuthLayout/components/AuthHelp.tsx +0 -114
  33. package/src/layouts/AuthLayout/components/AuthSuccess.tsx +0 -101
  34. package/src/layouts/AuthLayout/components/IdentifierForm.tsx +0 -322
  35. package/src/layouts/AuthLayout/components/OTPForm.tsx +0 -174
  36. package/src/layouts/AuthLayout/components/TwoFactorForm.tsx +0 -140
  37. package/src/layouts/AuthLayout/components/TwoFactorSetup.tsx +0 -286
  38. package/src/layouts/AuthLayout/components/oauth/OAuthProviders.tsx +0 -56
@@ -1,140 +0,0 @@
1
- 'use client';
2
-
3
- import { KeyRound, Loader2, ShieldCheck } from 'lucide-react';
4
- import React from 'react';
5
-
6
- import {
7
- Alert,
8
- AlertDescription,
9
- Button,
10
- Card,
11
- CardContent,
12
- CardDescription,
13
- CardFooter,
14
- CardHeader,
15
- CardTitle,
16
- OTPInput,
17
- Input,
18
- } from '@djangocfg/ui-core/components';
19
-
20
- import { useAuthFormContext } from '../context';
21
-
22
- /**
23
- * Two-Factor Authentication Form
24
- *
25
- * Displays TOTP code input or backup code input based on user selection.
26
- * Used after OTP/OAuth verification when user has 2FA enabled.
27
- */
28
- export const TwoFactorForm: React.FC = () => {
29
- const {
30
- twoFactorCode,
31
- useBackupCode,
32
- error,
33
- is2FALoading,
34
- twoFactorWarning,
35
- setTwoFactorCode,
36
- handle2FASubmit,
37
- handleUseBackupCode,
38
- handleUseTOTP,
39
- } = useAuthFormContext();
40
-
41
- return (
42
- <Card className="w-full">
43
- <CardHeader className="space-y-1 text-center">
44
- <div className="mx-auto w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-2">
45
- <ShieldCheck className="w-6 h-6 text-primary" />
46
- </div>
47
- <CardTitle className="text-2xl">Two-Factor Authentication</CardTitle>
48
- <CardDescription>
49
- {useBackupCode
50
- ? 'Enter one of your backup recovery codes'
51
- : 'Enter the 6-digit code from your authenticator app'}
52
- </CardDescription>
53
- </CardHeader>
54
-
55
- <form onSubmit={handle2FASubmit}>
56
- <CardContent className="space-y-4">
57
- {/* Error Alert */}
58
- {error && (
59
- <Alert variant="destructive">
60
- <AlertDescription>{error}</AlertDescription>
61
- </Alert>
62
- )}
63
-
64
- {/* Warning Alert (e.g., low backup codes) */}
65
- {twoFactorWarning && (
66
- <Alert>
67
- <AlertDescription>{twoFactorWarning}</AlertDescription>
68
- </Alert>
69
- )}
70
-
71
- {/* TOTP Code Input */}
72
- {!useBackupCode && (
73
- <div className="flex justify-center">
74
- <OTPInput
75
- length={6}
76
- validationMode="numeric"
77
- pasteBehavior="clean"
78
- value={twoFactorCode}
79
- onChange={setTwoFactorCode}
80
- disabled={is2FALoading}
81
- autoFocus={true}
82
- size="lg"
83
- />
84
- </div>
85
- )}
86
-
87
- {/* Backup Code Input */}
88
- {useBackupCode && (
89
- <div className="space-y-2">
90
- <Input
91
- type="text"
92
- placeholder="Enter backup code"
93
- value={twoFactorCode}
94
- onChange={(e) => setTwoFactorCode(e.target.value.toUpperCase())}
95
- disabled={is2FALoading}
96
- className="text-center font-mono text-lg tracking-widest"
97
- maxLength={12}
98
- autoComplete="off"
99
- />
100
- <p className="text-xs text-muted-foreground text-center">
101
- Backup codes are 8 characters, letters and numbers
102
- </p>
103
- </div>
104
- )}
105
- </CardContent>
106
-
107
- <CardFooter className="flex flex-col space-y-3">
108
- <Button
109
- type="submit"
110
- className="w-full"
111
- disabled={is2FALoading || (!useBackupCode && twoFactorCode.length !== 6)}
112
- >
113
- {is2FALoading ? (
114
- <>
115
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
116
- Verifying...
117
- </>
118
- ) : (
119
- 'Verify'
120
- )}
121
- </Button>
122
-
123
- {/* Toggle between TOTP and Backup Code */}
124
- <Button
125
- type="button"
126
- variant="ghost"
127
- className="w-full text-sm"
128
- onClick={useBackupCode ? handleUseTOTP : handleUseBackupCode}
129
- disabled={is2FALoading}
130
- >
131
- <KeyRound className="mr-2 h-4 w-4" />
132
- {useBackupCode
133
- ? 'Use authenticator app instead'
134
- : "Can't access your authenticator? Use a backup code"}
135
- </Button>
136
- </CardFooter>
137
- </form>
138
- </Card>
139
- );
140
- };
@@ -1,286 +0,0 @@
1
- 'use client';
2
-
3
- import { CheckCircle, Copy, Eye, EyeOff, Loader2, ShieldCheck } from 'lucide-react';
4
- import React, { useState } from 'react';
5
- import { QRCodeSVG } from 'qrcode.react';
6
-
7
- import { useTwoFactorSetup } from '@djangocfg/api/auth';
8
- import {
9
- Alert,
10
- AlertDescription,
11
- Button,
12
- Card,
13
- CardContent,
14
- CardDescription,
15
- CardFooter,
16
- CardHeader,
17
- CardTitle,
18
- OTPInput,
19
- } from '@djangocfg/ui-core/components';
20
-
21
- export interface TwoFactorSetupProps {
22
- /** Callback when setup is complete */
23
- onComplete?: (backupCodes: string[]) => void;
24
- /** Callback to skip setup */
25
- onSkip?: () => void;
26
- /** Callback on error */
27
- onError?: (error: string) => void;
28
- /** Device name for the authenticator */
29
- deviceName?: string;
30
- }
31
-
32
- /**
33
- * Two-Factor Authentication Setup Component
34
- *
35
- * Guides user through enabling 2FA:
36
- * 1. Shows QR code to scan with authenticator app
37
- * 2. User enters code to confirm
38
- * 3. Shows backup codes to save
39
- */
40
- export const TwoFactorSetup: React.FC<TwoFactorSetupProps> = ({
41
- onComplete,
42
- onSkip,
43
- onError,
44
- deviceName,
45
- }) => {
46
- const [confirmCode, setConfirmCode] = useState('');
47
- const [showSecret, setShowSecret] = useState(false);
48
- const [copiedSecret, setCopiedSecret] = useState(false);
49
- const [copiedBackupCodes, setCopiedBackupCodes] = useState(false);
50
-
51
- const {
52
- isLoading,
53
- error,
54
- setupData,
55
- backupCodes,
56
- backupCodesWarning,
57
- setupStep,
58
- startSetup,
59
- confirmSetup,
60
- resetSetup,
61
- } = useTwoFactorSetup({
62
- onComplete,
63
- onError,
64
- });
65
-
66
- // Start setup on mount if not already started
67
- React.useEffect(() => {
68
- if (setupStep === 'idle') {
69
- startSetup(deviceName);
70
- }
71
- }, [setupStep, startSetup, deviceName]);
72
-
73
- const handleConfirm = async (e: React.FormEvent) => {
74
- e.preventDefault();
75
- await confirmSetup(confirmCode);
76
- };
77
-
78
- const copySecret = async () => {
79
- if (setupData?.secret) {
80
- await navigator.clipboard.writeText(setupData.secret);
81
- setCopiedSecret(true);
82
- setTimeout(() => setCopiedSecret(false), 2000);
83
- }
84
- };
85
-
86
- const copyBackupCodes = async () => {
87
- if (backupCodes) {
88
- await navigator.clipboard.writeText(backupCodes.join('\n'));
89
- setCopiedBackupCodes(true);
90
- setTimeout(() => setCopiedBackupCodes(false), 2000);
91
- }
92
- };
93
-
94
- // Loading state
95
- if (isLoading && !setupData) {
96
- return (
97
- <Card className="w-full max-w-md mx-auto">
98
- <CardContent className="flex items-center justify-center py-12">
99
- <Loader2 className="w-8 h-8 animate-spin text-primary" />
100
- </CardContent>
101
- </Card>
102
- );
103
- }
104
-
105
- // Complete - show backup codes
106
- if (setupStep === 'complete' && backupCodes) {
107
- return (
108
- <Card className="w-full max-w-md mx-auto">
109
- <CardHeader className="space-y-1 text-center">
110
- <div className="mx-auto w-12 h-12 bg-green-100 dark:bg-green-900/20 rounded-full flex items-center justify-center mb-2">
111
- <CheckCircle className="w-6 h-6 text-green-600 dark:text-green-400" />
112
- </div>
113
- <CardTitle className="text-2xl">2FA Enabled!</CardTitle>
114
- <CardDescription>
115
- Save these backup codes in a secure place
116
- </CardDescription>
117
- </CardHeader>
118
-
119
- <CardContent className="space-y-4">
120
- {backupCodesWarning && (
121
- <Alert>
122
- <AlertDescription>{backupCodesWarning}</AlertDescription>
123
- </Alert>
124
- )}
125
-
126
- <div className="bg-muted rounded-lg p-4">
127
- <div className="grid grid-cols-2 gap-2 font-mono text-sm">
128
- {backupCodes.map((code, index) => (
129
- <div key={index} className="text-center py-1">
130
- {code}
131
- </div>
132
- ))}
133
- </div>
134
- </div>
135
-
136
- <p className="text-xs text-muted-foreground text-center">
137
- Each code can only be used once. Store them securely.
138
- </p>
139
- </CardContent>
140
-
141
- <CardFooter className="flex flex-col space-y-3">
142
- <Button
143
- type="button"
144
- variant="outline"
145
- className="w-full"
146
- onClick={copyBackupCodes}
147
- >
148
- <Copy className="mr-2 h-4 w-4" />
149
- {copiedBackupCodes ? 'Copied!' : 'Copy all codes'}
150
- </Button>
151
-
152
- <Button
153
- type="button"
154
- className="w-full"
155
- onClick={() => onComplete?.(backupCodes)}
156
- >
157
- I've saved my backup codes
158
- </Button>
159
- </CardFooter>
160
- </Card>
161
- );
162
- }
163
-
164
- // Scanning/Confirming - show QR code
165
- return (
166
- <Card className="w-full max-w-md mx-auto">
167
- <CardHeader className="space-y-1 text-center">
168
- <div className="mx-auto w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-2">
169
- <ShieldCheck className="w-6 h-6 text-primary" />
170
- </div>
171
- <CardTitle className="text-2xl">Set Up 2FA</CardTitle>
172
- <CardDescription>
173
- Scan this QR code with your authenticator app
174
- </CardDescription>
175
- </CardHeader>
176
-
177
- <form onSubmit={handleConfirm}>
178
- <CardContent className="space-y-6">
179
- {error && (
180
- <Alert variant="destructive">
181
- <AlertDescription>{error}</AlertDescription>
182
- </Alert>
183
- )}
184
-
185
- {/* QR Code */}
186
- {setupData && (
187
- <div className="flex justify-center">
188
- <div className="bg-white p-4 rounded-lg">
189
- <QRCodeSVG
190
- value={setupData.provisioningUri}
191
- size={200}
192
- level="M"
193
- marginSize={0}
194
- />
195
- </div>
196
- </div>
197
- )}
198
-
199
- {/* Manual entry option */}
200
- {setupData && (
201
- <div className="space-y-2">
202
- <Button
203
- type="button"
204
- variant="ghost"
205
- size="sm"
206
- className="w-full text-xs"
207
- onClick={() => setShowSecret(!showSecret)}
208
- >
209
- {showSecret ? (
210
- <EyeOff className="mr-2 h-3 w-3" />
211
- ) : (
212
- <Eye className="mr-2 h-3 w-3" />
213
- )}
214
- {showSecret ? 'Hide' : 'Show'} manual entry code
215
- </Button>
216
-
217
- {showSecret && (
218
- <div className="flex items-center gap-2 bg-muted rounded-lg p-3">
219
- <code className="flex-1 text-xs font-mono break-all">
220
- {setupData.secret}
221
- </code>
222
- <Button
223
- type="button"
224
- variant="ghost"
225
- size="sm"
226
- onClick={copySecret}
227
- >
228
- <Copy className="h-4 w-4" />
229
- </Button>
230
- </div>
231
- )}
232
- </div>
233
- )}
234
-
235
- {/* Confirm code input */}
236
- <div className="space-y-2">
237
- <p className="text-sm text-center text-muted-foreground">
238
- Enter the 6-digit code from your app to confirm
239
- </p>
240
- <div className="flex justify-center">
241
- <OTPInput
242
- length={6}
243
- validationMode="numeric"
244
- pasteBehavior="clean"
245
- value={confirmCode}
246
- onChange={setConfirmCode}
247
- disabled={isLoading}
248
- autoFocus={true}
249
- size="lg"
250
- />
251
- </div>
252
- </div>
253
- </CardContent>
254
-
255
- <CardFooter className="flex flex-col space-y-3">
256
- <Button
257
- type="submit"
258
- className="w-full"
259
- disabled={isLoading || confirmCode.length !== 6}
260
- >
261
- {isLoading ? (
262
- <>
263
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
264
- Verifying...
265
- </>
266
- ) : (
267
- 'Confirm & Enable 2FA'
268
- )}
269
- </Button>
270
-
271
- {onSkip && (
272
- <Button
273
- type="button"
274
- variant="ghost"
275
- className="w-full"
276
- onClick={onSkip}
277
- disabled={isLoading}
278
- >
279
- Skip for now
280
- </Button>
281
- )}
282
- </CardFooter>
283
- </form>
284
- </Card>
285
- );
286
- };
@@ -1,56 +0,0 @@
1
- 'use client';
2
-
3
- import { Github } from 'lucide-react';
4
- import React from 'react';
5
-
6
- import { useGithubAuth } from '@djangocfg/api/auth';
7
- import { Button } from '@djangocfg/ui-core/components';
8
-
9
- import { useAuthFormContext } from '../../context';
10
-
11
- /**
12
- * OAuth Providers Component
13
- *
14
- * Shows OAuth login buttons (GitHub, etc.) when enabled.
15
- */
16
- export const OAuthProviders: React.FC = () => {
17
- const { enableGithubAuth, sourceUrl, setError } = useAuthFormContext();
18
-
19
- const { isLoading, startGithubAuth } = useGithubAuth({
20
- sourceUrl,
21
- onError: setError,
22
- });
23
-
24
- if (!enableGithubAuth) {
25
- return null;
26
- }
27
-
28
- return (
29
- <div className="space-y-4">
30
- {/* Divider */}
31
- <div className="relative">
32
- <div className="absolute inset-0 flex items-center">
33
- <div className="w-full border-t border-border" />
34
- </div>
35
- <div className="relative flex justify-center text-xs uppercase">
36
- <span className="bg-card px-2 text-muted-foreground">
37
- Or continue with
38
- </span>
39
- </div>
40
- </div>
41
-
42
- {/* OAuth Buttons */}
43
- <Button
44
- type="button"
45
- variant="outline"
46
- size="lg"
47
- className="w-full"
48
- onClick={startGithubAuth}
49
- loading={isLoading}
50
- >
51
- <Github className="w-5 h-5" />
52
- Continue with GitHub
53
- </Button>
54
- </div>
55
- );
56
- };