@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.
- package/package.json +14 -14
- package/src/layouts/AuthLayout/AuthLayout.tsx +92 -20
- package/src/layouts/AuthLayout/components/index.ts +11 -7
- package/src/layouts/AuthLayout/components/oauth/index.ts +0 -1
- package/src/layouts/AuthLayout/components/shared/AuthButton.tsx +35 -0
- package/src/layouts/AuthLayout/components/shared/AuthContainer.tsx +56 -0
- package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +22 -0
- package/src/layouts/AuthLayout/components/shared/AuthError.tsx +26 -0
- package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +47 -0
- package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +53 -0
- package/src/layouts/AuthLayout/components/shared/AuthLink.tsx +41 -0
- package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +42 -0
- package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +48 -0
- package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +57 -0
- package/src/layouts/AuthLayout/components/shared/index.ts +21 -0
- package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +171 -0
- package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +114 -0
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +70 -0
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +24 -0
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +125 -0
- package/src/layouts/AuthLayout/components/steps/SetupStep/index.tsx +91 -0
- package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +92 -0
- package/src/layouts/AuthLayout/components/steps/index.ts +6 -0
- package/src/layouts/AuthLayout/constants.ts +24 -0
- package/src/layouts/AuthLayout/content.ts +78 -0
- package/src/layouts/AuthLayout/hooks/index.ts +1 -0
- package/src/layouts/AuthLayout/hooks/useCopyToClipboard.ts +37 -0
- package/src/layouts/AuthLayout/index.ts +9 -5
- package/src/layouts/AuthLayout/styles/auth.css +578 -0
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +2 -2
- package/src/layouts/ProfileLayout/components/TwoFactorSection.tsx +2 -2
- package/src/layouts/AuthLayout/components/AuthHelp.tsx +0 -114
- package/src/layouts/AuthLayout/components/AuthSuccess.tsx +0 -101
- package/src/layouts/AuthLayout/components/IdentifierForm.tsx +0 -322
- package/src/layouts/AuthLayout/components/OTPForm.tsx +0 -174
- package/src/layouts/AuthLayout/components/TwoFactorForm.tsx +0 -140
- package/src/layouts/AuthLayout/components/TwoFactorSetup.tsx +0 -286
- package/src/layouts/AuthLayout/components/oauth/OAuthProviders.tsx +0 -56
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
import { Input } from '@djangocfg/ui-core/components';
|
|
6
|
+
|
|
7
|
+
import { AUTH } from '../../constants';
|
|
8
|
+
import { AUTH_CONTENT } from '../../content';
|
|
9
|
+
import { useAuthFormContext } from '../../context';
|
|
10
|
+
import {
|
|
11
|
+
AuthButton,
|
|
12
|
+
AuthContainer,
|
|
13
|
+
AuthError,
|
|
14
|
+
AuthHeader,
|
|
15
|
+
AuthLink,
|
|
16
|
+
AuthOTPInput,
|
|
17
|
+
} from '../shared';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* TwoFactorStep - Apple-style 2FA verification
|
|
21
|
+
*
|
|
22
|
+
* Minimal design with:
|
|
23
|
+
* - TOTP code input (default)
|
|
24
|
+
* - Backup code input (alternate)
|
|
25
|
+
* - Clean toggle between modes
|
|
26
|
+
*/
|
|
27
|
+
export const TwoFactorStep: React.FC = () => {
|
|
28
|
+
const {
|
|
29
|
+
twoFactorCode,
|
|
30
|
+
useBackupCode,
|
|
31
|
+
error,
|
|
32
|
+
is2FALoading,
|
|
33
|
+
twoFactorWarning,
|
|
34
|
+
setTwoFactorCode,
|
|
35
|
+
handle2FASubmit,
|
|
36
|
+
handleUseBackupCode,
|
|
37
|
+
handleUseTOTP,
|
|
38
|
+
} = useAuthFormContext();
|
|
39
|
+
|
|
40
|
+
const content = AUTH_CONTENT.twoFactor;
|
|
41
|
+
const subtitle = useBackupCode ? content.subtitle.backup : content.subtitle.totp;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<AuthContainer step="2fa">
|
|
45
|
+
<AuthHeader title={content.title} subtitle={subtitle} />
|
|
46
|
+
|
|
47
|
+
<form onSubmit={handle2FASubmit} className="auth-form-group">
|
|
48
|
+
{useBackupCode ? (
|
|
49
|
+
<Input
|
|
50
|
+
type="text"
|
|
51
|
+
value={twoFactorCode}
|
|
52
|
+
onChange={(e) => setTwoFactorCode(e.target.value.toUpperCase())}
|
|
53
|
+
placeholder={content.placeholder.backup}
|
|
54
|
+
disabled={is2FALoading}
|
|
55
|
+
maxLength={AUTH.BACKUP_CODE_MAX_LENGTH}
|
|
56
|
+
autoComplete="off"
|
|
57
|
+
autoFocus
|
|
58
|
+
className="auth-input font-mono tracking-widest text-center"
|
|
59
|
+
/>
|
|
60
|
+
) : (
|
|
61
|
+
<AuthOTPInput
|
|
62
|
+
value={twoFactorCode}
|
|
63
|
+
onChange={setTwoFactorCode}
|
|
64
|
+
disabled={is2FALoading}
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{twoFactorWarning && (
|
|
69
|
+
<div className="auth-dev-notice">{twoFactorWarning}</div>
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
<AuthError message={error} />
|
|
73
|
+
|
|
74
|
+
<AuthButton
|
|
75
|
+
loading={is2FALoading}
|
|
76
|
+
disabled={!useBackupCode && twoFactorCode.length !== AUTH.OTP_LENGTH}
|
|
77
|
+
>
|
|
78
|
+
{content.button}
|
|
79
|
+
</AuthButton>
|
|
80
|
+
|
|
81
|
+
<div className="auth-actions">
|
|
82
|
+
<AuthLink
|
|
83
|
+
onClick={useBackupCode ? handleUseTOTP : handleUseBackupCode}
|
|
84
|
+
disabled={is2FALoading}
|
|
85
|
+
>
|
|
86
|
+
{useBackupCode ? content.toggle.toTotp : content.toggle.toBackup}
|
|
87
|
+
</AuthLink>
|
|
88
|
+
</div>
|
|
89
|
+
</form>
|
|
90
|
+
</AuthContainer>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthLayout Constants
|
|
3
|
+
*
|
|
4
|
+
* All magic numbers extracted for clarity and maintainability.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const AUTH = {
|
|
8
|
+
// Input lengths
|
|
9
|
+
OTP_LENGTH: 6,
|
|
10
|
+
BACKUP_CODE_MAX_LENGTH: 12,
|
|
11
|
+
|
|
12
|
+
// Timing (ms)
|
|
13
|
+
ANIMATION_DURATION: 400,
|
|
14
|
+
AUTO_SUBMIT_DELAY: 100,
|
|
15
|
+
COPY_FEEDBACK_DURATION: 2000,
|
|
16
|
+
REDIRECT_DELAY: 1500,
|
|
17
|
+
ANIMATION_START_DELAY: 50,
|
|
18
|
+
|
|
19
|
+
// Sizes (px)
|
|
20
|
+
QR_CODE_SIZE: 160,
|
|
21
|
+
|
|
22
|
+
// Routes
|
|
23
|
+
DEFAULT_REDIRECT: '/dashboard',
|
|
24
|
+
} as const;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthLayout Content
|
|
3
|
+
*
|
|
4
|
+
* All UI strings centralized for easy updates and i18n.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const AUTH_CONTENT = {
|
|
8
|
+
identifier: {
|
|
9
|
+
title: 'Sign in',
|
|
10
|
+
subtitle: {
|
|
11
|
+
email: 'Enter your email to continue',
|
|
12
|
+
phone: 'Enter your phone number to continue',
|
|
13
|
+
},
|
|
14
|
+
placeholder: {
|
|
15
|
+
email: 'email@example.com',
|
|
16
|
+
phone: 'Enter your phone number',
|
|
17
|
+
},
|
|
18
|
+
button: 'Continue',
|
|
19
|
+
oauth: {
|
|
20
|
+
github: 'Continue with GitHub',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
otp: {
|
|
25
|
+
title: 'Enter code',
|
|
26
|
+
subtitle: 'We sent a code to',
|
|
27
|
+
button: 'Verify',
|
|
28
|
+
resend: 'Resend code',
|
|
29
|
+
changeIdentifier: (channel: string) => `Change ${channel}`,
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
twoFactor: {
|
|
33
|
+
title: 'Two-factor authentication',
|
|
34
|
+
subtitle: {
|
|
35
|
+
totp: 'Enter the 6-digit code from your authenticator',
|
|
36
|
+
backup: 'Enter one of your backup codes',
|
|
37
|
+
},
|
|
38
|
+
placeholder: {
|
|
39
|
+
backup: 'XXXXXXXX',
|
|
40
|
+
},
|
|
41
|
+
button: 'Verify',
|
|
42
|
+
toggle: {
|
|
43
|
+
toBackup: 'Use backup code',
|
|
44
|
+
toTotp: 'Use authenticator app',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
setup: {
|
|
49
|
+
loading: {
|
|
50
|
+
title: 'Setting up...',
|
|
51
|
+
button: 'Loading',
|
|
52
|
+
},
|
|
53
|
+
qrCode: {
|
|
54
|
+
title: 'Set up 2FA',
|
|
55
|
+
subtitle: 'Scan this QR code with your authenticator app',
|
|
56
|
+
manualEntry: "Can't scan? Enter manually",
|
|
57
|
+
confirmPrompt: 'Enter the 6-digit code to confirm',
|
|
58
|
+
button: 'Enable 2FA',
|
|
59
|
+
skip: 'Skip for now',
|
|
60
|
+
},
|
|
61
|
+
complete: {
|
|
62
|
+
title: '2FA enabled',
|
|
63
|
+
subtitle: 'Save these backup codes securely',
|
|
64
|
+
instruction: 'Each code can only be used once',
|
|
65
|
+
copyAll: 'Copy all codes',
|
|
66
|
+
copied: 'Copied',
|
|
67
|
+
done: 'Done',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
success: {
|
|
72
|
+
message: 'Signed in successfully',
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
dev: {
|
|
76
|
+
anyCodeWorks: 'Dev Mode: Any code works',
|
|
77
|
+
},
|
|
78
|
+
} as const;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useCopyToClipboard } from './useCopyToClipboard';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { AUTH } from '../constants';
|
|
6
|
+
|
|
7
|
+
interface UseCopyToClipboardResult {
|
|
8
|
+
copied: boolean;
|
|
9
|
+
copy: (text: string) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* useCopyToClipboard - Copy text with temporary feedback state
|
|
14
|
+
*
|
|
15
|
+
* @param duration - How long to show "copied" state (default: 2000ms)
|
|
16
|
+
*/
|
|
17
|
+
export const useCopyToClipboard = (
|
|
18
|
+
duration = AUTH.COPY_FEEDBACK_DURATION
|
|
19
|
+
): UseCopyToClipboardResult => {
|
|
20
|
+
const [copied, setCopied] = useState(false);
|
|
21
|
+
|
|
22
|
+
const copy = useCallback(
|
|
23
|
+
async (text: string) => {
|
|
24
|
+
try {
|
|
25
|
+
await navigator.clipboard.writeText(text);
|
|
26
|
+
setCopied(true);
|
|
27
|
+
setTimeout(() => setCopied(false), duration);
|
|
28
|
+
} catch {
|
|
29
|
+
// Clipboard API not available
|
|
30
|
+
console.warn('Clipboard API not available');
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
[duration]
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
return { copied, copy };
|
|
37
|
+
};
|
|
@@ -9,16 +9,20 @@ export type { AuthLayoutProps } from './AuthLayout';
|
|
|
9
9
|
// Context and hooks
|
|
10
10
|
export { AuthFormProvider, useAuthFormContext } from './context';
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// Apple-style step components
|
|
13
13
|
export {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
IdentifierStep,
|
|
15
|
+
OTPStep,
|
|
16
|
+
TwoFactorStep,
|
|
17
|
+
SetupStep,
|
|
18
|
+
type SetupStepProps,
|
|
18
19
|
OAuthCallback,
|
|
19
20
|
type OAuthCallbackProps,
|
|
20
21
|
} from './components';
|
|
21
22
|
|
|
23
|
+
// Shared UI components
|
|
24
|
+
export * from './components/shared';
|
|
25
|
+
|
|
22
26
|
// Types (re-exported from @djangocfg/api/auth)
|
|
23
27
|
export type {
|
|
24
28
|
AuthChannel,
|