@djangocfg/layouts 2.1.227 → 2.1.229

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 (97) hide show
  1. package/README.md +3 -17
  2. package/package.json +18 -18
  3. package/src/components/errors/ErrorLayout.tsx +2 -2
  4. package/src/components/errors/ErrorsTracker/index.ts +1 -0
  5. package/src/components/errors/ErrorsTracker/utils/formatters.ts +23 -1
  6. package/src/hooks/useLogout.ts +9 -12
  7. package/src/layouts/AppLayout/AppLayout.tsx +20 -8
  8. package/src/layouts/AppLayout/BaseApp.tsx +5 -28
  9. package/src/layouts/AuthLayout/AuthLayout.tsx +51 -22
  10. package/src/layouts/AuthLayout/README.md +78 -0
  11. package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +2 -2
  12. package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -2
  13. package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +2 -2
  14. package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +3 -2
  15. package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +4 -1
  16. package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +2 -2
  17. package/src/layouts/AuthLayout/components/shared/index.ts +0 -2
  18. package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +25 -80
  19. package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +8 -13
  20. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +2 -2
  21. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +2 -2
  22. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +2 -2
  23. package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +61 -42
  24. package/src/layouts/AuthLayout/context.tsx +0 -2
  25. package/src/layouts/AuthLayout/index.ts +9 -6
  26. package/src/layouts/AuthLayout/styles/auth.css +265 -120
  27. package/src/layouts/AuthLayout/types.ts +60 -7
  28. package/src/layouts/ProfileLayout/.claude/.sidecar/activity.jsonl +2 -0
  29. package/src/layouts/ProfileLayout/.claude/.sidecar/history/2026-03-15.md +35 -0
  30. package/src/layouts/ProfileLayout/.claude/.sidecar/review.md +35 -0
  31. package/src/layouts/ProfileLayout/.claude/.sidecar/scan.log +3 -0
  32. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-001.md +18 -0
  33. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-002.md +19 -0
  34. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-003.md +18 -0
  35. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-004.md +18 -0
  36. package/src/layouts/ProfileLayout/.claude/.sidecar/tasks/T-005.md +18 -0
  37. package/src/layouts/ProfileLayout/.claude/.sidecar/usage.json +5 -0
  38. package/src/layouts/ProfileLayout/ProfileLayout.tsx +52 -403
  39. package/src/layouts/ProfileLayout/components/ActionButton.tsx +38 -0
  40. package/src/layouts/ProfileLayout/components/DeleteAccountSection.tsx +109 -148
  41. package/src/layouts/ProfileLayout/components/EditableField.tsx +119 -0
  42. package/src/layouts/ProfileLayout/components/Section.tsx +22 -0
  43. package/src/layouts/ProfileLayout/components/index.ts +4 -1
  44. package/src/layouts/ProfileLayout/context.tsx +31 -0
  45. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +2 -2
  46. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +2 -2
  47. package/src/layouts/_components/UserMenu.tsx +2 -2
  48. package/src/layouts/types/README.md +0 -20
  49. package/src/layouts/types/index.ts +2 -2
  50. package/src/layouts/types/layout.types.ts +3 -5
  51. package/src/layouts/types/providers.types.ts +0 -27
  52. package/src/snippets/AuthDialog/AuthDialog.tsx +2 -2
  53. package/src/snippets/Breadcrumbs.tsx +2 -2
  54. package/src/snippets/index.ts +0 -69
  55. package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +0 -56
  56. package/src/snippets/McpChat/README.md +0 -441
  57. package/src/snippets/McpChat/components/AIChatWidget.tsx +0 -361
  58. package/src/snippets/McpChat/components/AskAIButton.tsx +0 -92
  59. package/src/snippets/McpChat/components/ChatMessages.tsx +0 -138
  60. package/src/snippets/McpChat/components/ChatPanel.tsx +0 -131
  61. package/src/snippets/McpChat/components/ChatSidebar.tsx +0 -156
  62. package/src/snippets/McpChat/components/ChatWidget.tsx +0 -115
  63. package/src/snippets/McpChat/components/MessageBubble.tsx +0 -142
  64. package/src/snippets/McpChat/components/MessageInput.tsx +0 -140
  65. package/src/snippets/McpChat/components/index.ts +0 -24
  66. package/src/snippets/McpChat/config.ts +0 -94
  67. package/src/snippets/McpChat/context/AIChatContext.tsx +0 -327
  68. package/src/snippets/McpChat/context/ChatContext.tsx +0 -361
  69. package/src/snippets/McpChat/context/index.ts +0 -7
  70. package/src/snippets/McpChat/hooks/index.ts +0 -6
  71. package/src/snippets/McpChat/hooks/useAIChat.ts +0 -503
  72. package/src/snippets/McpChat/hooks/useChatLayout.ts +0 -442
  73. package/src/snippets/McpChat/hooks/useMcpChat.ts +0 -90
  74. package/src/snippets/McpChat/index.ts +0 -79
  75. package/src/snippets/McpChat/types.ts +0 -189
  76. package/src/snippets/PWAInstall/@docs/README.md +0 -92
  77. package/src/snippets/PWAInstall/@docs/research/ios-android-install-flows.md +0 -576
  78. package/src/snippets/PWAInstall/README.md +0 -235
  79. package/src/snippets/PWAInstall/components/A2HSHint.tsx +0 -236
  80. package/src/snippets/PWAInstall/components/DesktopGuide.tsx +0 -234
  81. package/src/snippets/PWAInstall/components/IOSGuide.tsx +0 -29
  82. package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +0 -103
  83. package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +0 -103
  84. package/src/snippets/PWAInstall/components/PWAPageResumeManager.tsx +0 -33
  85. package/src/snippets/PWAInstall/context/InstallContext.tsx +0 -102
  86. package/src/snippets/PWAInstall/hooks/useInstallPrompt.ts +0 -168
  87. package/src/snippets/PWAInstall/hooks/useIsPWA.ts +0 -116
  88. package/src/snippets/PWAInstall/hooks/usePWAPageResume.ts +0 -163
  89. package/src/snippets/PWAInstall/index.ts +0 -80
  90. package/src/snippets/PWAInstall/types/components.ts +0 -95
  91. package/src/snippets/PWAInstall/types/config.ts +0 -29
  92. package/src/snippets/PWAInstall/types/index.ts +0 -26
  93. package/src/snippets/PWAInstall/types/install.ts +0 -38
  94. package/src/snippets/PWAInstall/types/platform.ts +0 -29
  95. package/src/snippets/PWAInstall/utils/localStorage.ts +0 -181
  96. package/src/snippets/PWAInstall/utils/logger.ts +0 -149
  97. package/src/snippets/PWAInstall/utils/platform.ts +0 -151
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useMemo } from 'react';
4
4
 
5
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
5
+ import { useAppT } from '@djangocfg/i18n';
6
6
  import { Checkbox } from '@djangocfg/ui-core/components';
7
7
 
8
8
  export interface TermsCheckboxProps {
@@ -25,7 +25,7 @@ export const TermsCheckbox: React.FC<TermsCheckboxProps> = ({
25
25
  disabled = false,
26
26
  className = '',
27
27
  }) => {
28
- const t = useTypedT<I18nTranslations>();
28
+ const t = useAppT();
29
29
  const labels = useMemo(() => ({
30
30
  agree: t('layouts.auth.terms.agree'),
31
31
  and: t('layouts.auth.terms.and'),
@@ -6,7 +6,6 @@ export { AuthError } from './AuthError';
6
6
  export { AuthButton } from './AuthButton';
7
7
  export { AuthLink } from './AuthLink';
8
8
  export { AuthOTPInput } from './AuthOTPInput';
9
- export { ChannelToggle } from './ChannelToggle';
10
9
  export { TermsCheckbox } from './TermsCheckbox';
11
10
 
12
11
  export type { AuthContainerProps } from './AuthContainer';
@@ -17,5 +16,4 @@ export type { AuthErrorProps } from './AuthError';
17
16
  export type { AuthButtonProps } from './AuthButton';
18
17
  export type { AuthLinkProps } from './AuthLink';
19
18
  export type { AuthOTPInputProps } from './AuthOTPInput';
20
- export type { ChannelToggleProps } from './ChannelToggle';
21
19
  export type { TermsCheckboxProps } from './TermsCheckbox';
@@ -1,12 +1,13 @@
1
1
  'use client';
2
2
 
3
3
  import { Github } from 'lucide-react';
4
- import React, { useEffect, useMemo } from 'react';
4
+ import React, { useMemo } from 'react';
5
5
 
6
6
  import { useGithubAuth } from '@djangocfg/api/auth';
7
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
8
- import { Input, PhoneInput } from '@djangocfg/ui-core/components';
7
+ import { useAppT } from '@djangocfg/i18n';
8
+ import { Input } from '@djangocfg/ui-core/components';
9
9
 
10
+ import { useAuthLayoutContext } from '../../AuthLayout';
10
11
  import { useAuthFormContext } from '../../context';
11
12
  import {
12
13
  AuthButton,
@@ -15,54 +16,49 @@ import {
15
16
  AuthError,
16
17
  AuthFooter,
17
18
  AuthHeader,
18
- ChannelToggle,
19
19
  TermsCheckbox,
20
20
  } from '../shared';
21
21
 
22
22
  /**
23
- * IdentifierStep - Apple-style email/phone input step
23
+ * IdentifierStep - Apple-style email input step
24
24
  *
25
25
  * Clean, minimal design with:
26
26
  * - Optional logo
27
- * - Channel toggle (email/phone)
28
- * - Single input field
27
+ * - Single email input field
29
28
  * - Terms checkbox
30
29
  * - OAuth options
31
30
  */
32
31
  export const IdentifierStep: React.FC = () => {
32
+ const { hideHeader } = useAuthLayoutContext();
33
+
33
34
  const {
34
35
  identifier,
35
- channel,
36
36
  isLoading,
37
37
  acceptedTerms,
38
38
  error,
39
+ isRateLimited,
40
+ rateLimitLabel,
39
41
  logoUrl,
40
42
  termsUrl,
41
43
  privacyUrl,
42
44
  supportUrl,
43
- enablePhoneAuth,
44
45
  enableGithubAuth,
45
46
  sourceUrl,
46
47
  setIdentifier,
47
- setChannel,
48
48
  setAcceptedTerms,
49
49
  setError,
50
50
  handleIdentifierSubmit,
51
- detectChannelFromIdentifier,
52
- validateIdentifier,
53
51
  } = useAuthFormContext();
54
52
 
55
53
  // Translations
56
- const t = useTypedT<I18nTranslations>();
54
+ const t = useAppT();
57
55
  const content = useMemo(() => ({
58
56
  title: t('layouts.auth.identifier.title'),
59
57
  subtitle: {
60
58
  email: t('layouts.auth.identifier.subtitle.email'),
61
- phone: t('layouts.auth.identifier.subtitle.phone'),
62
59
  },
63
60
  placeholder: {
64
61
  email: t('layouts.auth.identifier.placeholder.email'),
65
- phone: t('layouts.auth.identifier.placeholder.phone'),
66
62
  },
67
63
  button: t('layouts.auth.identifier.button'),
68
64
  oauth: {
@@ -76,75 +72,24 @@ export const IdentifierStep: React.FC = () => {
76
72
  onError: setError,
77
73
  });
78
74
 
79
- // Force email if phone disabled
80
- useEffect(() => {
81
- if (!enablePhoneAuth && channel === 'phone') {
82
- setChannel('email');
83
- if (identifier && detectChannelFromIdentifier(identifier) === 'phone') {
84
- setIdentifier('');
85
- }
86
- }
87
- }, [enablePhoneAuth, channel, identifier, setChannel, setIdentifier, detectChannelFromIdentifier]);
88
-
89
- // Handle identifier change with auto-detection
90
- const handleChange = (value: string) => {
91
- setIdentifier(value);
92
- const detected = detectChannelFromIdentifier(value);
93
- if (detected && detected !== channel) {
94
- if (detected === 'phone' && !enablePhoneAuth) return;
95
- setChannel(detected);
96
- }
97
- };
98
-
99
- // Handle channel switch
100
- const handleChannelChange = (newChannel: 'email' | 'phone') => {
101
- if (newChannel === 'phone' && !enablePhoneAuth) return;
102
- setChannel(newChannel);
103
- if (identifier && !validateIdentifier(identifier, newChannel)) {
104
- setIdentifier('');
105
- }
106
- };
107
-
108
- const subtitle = channel === 'phone' ? content.subtitle.phone : content.subtitle.email;
109
75
  const hasTerms = Boolean(termsUrl || privacyUrl);
110
76
 
111
77
  return (
112
78
  <AuthContainer step="identifier">
113
- <AuthHeader logo={logoUrl} title={content.title} subtitle={subtitle} />
79
+ {!hideHeader && <AuthHeader logo={logoUrl} title={content.title} subtitle={content.subtitle.email} />}
114
80
 
115
81
  <form onSubmit={handleIdentifierSubmit} className="auth-form-group">
116
- {enablePhoneAuth && (
117
- <ChannelToggle
118
- channel={channel}
119
- onChange={handleChannelChange}
120
- disabled={isLoading}
121
- />
122
- )}
123
-
124
- {channel === 'phone' ? (
125
- <div className="auth-phone-input">
126
- <PhoneInput
127
- value={identifier}
128
- onChange={(value) => handleChange(value || '')}
129
- disabled={isLoading}
130
- placeholder={content.placeholder.phone}
131
- defaultCountry="US"
132
- className="auth-input"
133
- />
134
- </div>
135
- ) : (
136
- <Input
137
- type="email"
138
- value={identifier}
139
- onChange={(e) => handleChange(e.target.value)}
140
- placeholder={content.placeholder.email}
141
- disabled={isLoading}
142
- required
143
- autoFocus
144
- autoComplete="off"
145
- className="auth-input"
146
- />
147
- )}
82
+ <Input
83
+ type="email"
84
+ value={identifier}
85
+ onChange={(e) => setIdentifier(e.target.value)}
86
+ placeholder={content.placeholder.email}
87
+ disabled={isLoading}
88
+ required
89
+ autoFocus
90
+ autoComplete="off"
91
+ className="auth-input"
92
+ />
148
93
 
149
94
  <TermsCheckbox
150
95
  checked={acceptedTerms}
@@ -158,9 +103,9 @@ export const IdentifierStep: React.FC = () => {
158
103
 
159
104
  <AuthButton
160
105
  loading={isLoading}
161
- disabled={!identifier || (hasTerms && !acceptedTerms)}
106
+ disabled={!identifier || (hasTerms && !acceptedTerms) || isRateLimited}
162
107
  >
163
- {content.button}
108
+ {isRateLimited ? `${content.button} (${rateLimitLabel})` : content.button}
164
109
  </AuthButton>
165
110
  </form>
166
111
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useCallback, useMemo, useState } from 'react';
4
4
 
5
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
5
+ import { useAppT } from '@djangocfg/i18n';
6
6
 
7
7
  import { config } from '../../../../utils';
8
8
  import { AUTH } from '../../constants';
@@ -30,10 +30,11 @@ type SubmitState = 'idle' | 'submitting';
30
30
  export const OTPStep: React.FC = () => {
31
31
  const {
32
32
  identifier,
33
- channel,
34
33
  otp,
35
34
  isLoading,
36
35
  error,
36
+ isRateLimited,
37
+ rateLimitLabel,
37
38
  setOtp,
38
39
  handleOTPSubmit,
39
40
  handleResendOTP,
@@ -44,26 +45,22 @@ export const OTPStep: React.FC = () => {
44
45
  const [submitState, setSubmitState] = useState<SubmitState>('idle');
45
46
 
46
47
  // Translations
47
- const t = useTypedT<I18nTranslations>();
48
+ const t = useAppT();
48
49
  const content = useMemo(() => ({
49
50
  title: t('layouts.auth.otp.title'),
50
51
  subtitle: t('layouts.auth.otp.subtitle'),
51
52
  button: t('layouts.auth.otp.button'),
52
53
  resend: t('layouts.auth.otp.resend'),
53
54
  changeEmail: t('layouts.auth.otp.changeEmail'),
54
- changePhone: t('layouts.auth.otp.changePhone'),
55
55
  devMode: t('layouts.auth.dev.anyCodeWorks'),
56
56
  }), [t]);
57
57
 
58
58
  // Mask identifier for privacy
59
59
  const maskedIdentifier = useMemo(() => {
60
- if (channel === 'phone') {
61
- return identifier.slice(-4).padStart(identifier.length, '*');
62
- }
63
60
  const [local, domain] = identifier.split('@');
64
61
  if (!local || !domain) return identifier;
65
62
  return `${local[0]}${'*'.repeat(Math.min(local.length - 1, 5))}@${domain}`;
66
- }, [identifier, channel]);
63
+ }, [identifier]);
67
64
 
68
65
  // Auto-submit on complete (state machine approach)
69
66
  const handleOTPComplete = useCallback(
@@ -85,8 +82,6 @@ export const OTPStep: React.FC = () => {
85
82
  [handleOTPSubmit, isLoading, isAutoSubmittingFromUrl, submitState]
86
83
  );
87
84
 
88
- const changeIdentifierLabel = channel === 'phone' ? content.changePhone : content.changeEmail;
89
-
90
85
  return (
91
86
  <AuthContainer step="otp">
92
87
  <AuthHeader
@@ -114,11 +109,11 @@ export const OTPStep: React.FC = () => {
114
109
  </AuthButton>
115
110
 
116
111
  <div className="auth-actions">
117
- <AuthLink onClick={handleResendOTP} disabled={isLoading}>
118
- {content.resend}
112
+ <AuthLink onClick={handleResendOTP} disabled={isLoading || isRateLimited}>
113
+ {isRateLimited ? `${content.resend} (${rateLimitLabel})` : content.resend}
119
114
  </AuthLink>
120
115
  <AuthLink onClick={handleBackToIdentifier} disabled={isLoading}>
121
- {changeIdentifierLabel}
116
+ {content.changeEmail}
122
117
  </AuthLink>
123
118
  </div>
124
119
  </form>
@@ -3,7 +3,7 @@
3
3
  import { Check, Copy } from 'lucide-react';
4
4
  import React, { useMemo } from 'react';
5
5
 
6
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
6
+ import { useAppT } from '@djangocfg/i18n';
7
7
 
8
8
  import { useCopyToClipboard } from '../../../hooks';
9
9
  import { AuthButton, AuthContainer, AuthHeader } from '../../shared';
@@ -23,7 +23,7 @@ export const SetupComplete: React.FC<SetupCompleteProps> = ({
23
23
  onDone,
24
24
  }) => {
25
25
  const { copied, copy } = useCopyToClipboard();
26
- const t = useTypedT<I18nTranslations>();
26
+ const t = useAppT();
27
27
  const content = useMemo(() => ({
28
28
  title: t('layouts.auth.setup.complete.title'),
29
29
  subtitle: t('layouts.auth.setup.complete.subtitle'),
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useMemo } from 'react';
4
4
 
5
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
5
+ import { useAppT } from '@djangocfg/i18n';
6
6
 
7
7
  import { AuthButton, AuthContainer, AuthHeader } from '../../shared';
8
8
 
@@ -10,7 +10,7 @@ import { AuthButton, AuthContainer, AuthHeader } from '../../shared';
10
10
  * SetupLoading - Loading state for 2FA setup
11
11
  */
12
12
  export const SetupLoading: React.FC = () => {
13
- const t = useTypedT<I18nTranslations>();
13
+ const t = useAppT();
14
14
  const content = useMemo(() => ({
15
15
  title: t('layouts.auth.setup.loading.title'),
16
16
  button: t('layouts.auth.setup.loading.button'),
@@ -4,7 +4,7 @@ import { Check, Copy } from 'lucide-react';
4
4
  import React, { useMemo, useState } from 'react';
5
5
  import { QRCodeSVG } from 'qrcode.react';
6
6
 
7
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
7
+ import { useAppT } from '@djangocfg/i18n';
8
8
  import {
9
9
  Collapsible,
10
10
  CollapsibleContent,
@@ -44,7 +44,7 @@ export const SetupQRCode: React.FC<SetupQRCodeProps> = ({
44
44
  }) => {
45
45
  const [confirmCode, setConfirmCode] = useState('');
46
46
  const { copied, copy } = useCopyToClipboard();
47
- const t = useTypedT<I18nTranslations>();
47
+ const t = useAppT();
48
48
  const content = useMemo(() => ({
49
49
  title: t('layouts.auth.setup.qrCode.title'),
50
50
  subtitle: t('layouts.auth.setup.qrCode.subtitle'),
@@ -2,8 +2,7 @@
2
2
 
3
3
  import React, { useMemo } from 'react';
4
4
 
5
- import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
6
- import { Input } from '@djangocfg/ui-core/components';
5
+ import { useAppT } from '@djangocfg/i18n';
7
6
 
8
7
  import { AUTH } from '../../constants';
9
8
  import { useAuthFormContext } from '../../context';
@@ -31,14 +30,16 @@ export const TwoFactorStep: React.FC = () => {
31
30
  error,
32
31
  is2FALoading,
33
32
  twoFactorWarning,
33
+ twoFactorAttemptsRemaining,
34
34
  setTwoFactorCode,
35
35
  handle2FASubmit,
36
36
  handleUseBackupCode,
37
37
  handleUseTOTP,
38
+ handleBackToIdentifier,
38
39
  } = useAuthFormContext();
39
40
 
40
41
  // Translations
41
- const t = useTypedT<I18nTranslations>();
42
+ const t = useAppT();
42
43
  const content = useMemo(() => ({
43
44
  title: t('layouts.auth.twoFactor.title'),
44
45
  subtitle: {
@@ -53,57 +54,75 @@ export const TwoFactorStep: React.FC = () => {
53
54
  toBackup: t('layouts.auth.twoFactor.toggle.toBackup'),
54
55
  toTotp: t('layouts.auth.twoFactor.toggle.toTotp'),
55
56
  },
57
+ changeEmail: t('layouts.auth.otp.changeEmail'),
56
58
  }), [t]);
57
59
 
58
60
  const subtitle = useBackupCode ? content.subtitle.backup : content.subtitle.totp;
59
61
 
60
62
  return (
61
63
  <AuthContainer step="2fa">
62
- <AuthHeader title={content.title} subtitle={subtitle} />
64
+ <div className="auth-2fa-box">
65
+ <div className="auth-2fa-shield" aria-hidden="true">
66
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
67
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
68
+ </svg>
69
+ </div>
70
+
71
+ <AuthHeader title={content.title} subtitle={subtitle} />
63
72
 
64
- <form onSubmit={handle2FASubmit} className="auth-form-group">
65
- {useBackupCode ? (
66
- <Input
67
- type="text"
68
- value={twoFactorCode}
69
- onChange={(e) => setTwoFactorCode(e.target.value.toUpperCase())}
70
- placeholder={content.placeholder.backup}
71
- disabled={is2FALoading}
72
- maxLength={AUTH.BACKUP_CODE_MAX_LENGTH}
73
- autoComplete="off"
74
- autoFocus
75
- className="auth-input font-mono tracking-widest text-center"
76
- />
77
- ) : (
78
- <AuthOTPInput
79
- value={twoFactorCode}
80
- onChange={setTwoFactorCode}
81
- disabled={is2FALoading}
82
- />
83
- )}
73
+ <form onSubmit={handle2FASubmit} className="auth-form-group">
74
+ {useBackupCode ? (
75
+ <input
76
+ type="text"
77
+ value={twoFactorCode}
78
+ onChange={(e) => setTwoFactorCode(e.target.value.toUpperCase())}
79
+ placeholder={content.placeholder.backup}
80
+ disabled={is2FALoading}
81
+ maxLength={AUTH.BACKUP_CODE_MAX_LENGTH}
82
+ autoComplete="off"
83
+ autoFocus
84
+ className="auth-input"
85
+ />
86
+ ) : (
87
+ <AuthOTPInput
88
+ value={twoFactorCode}
89
+ onChange={setTwoFactorCode}
90
+ disabled={is2FALoading}
91
+ />
92
+ )}
84
93
 
85
- {twoFactorWarning && (
86
- <div className="auth-dev-notice">{twoFactorWarning}</div>
87
- )}
94
+ {twoFactorWarning && (
95
+ <div className="auth-dev-notice">{twoFactorWarning}</div>
96
+ )}
88
97
 
89
- <AuthError message={error} />
98
+ <AuthError message={error} />
90
99
 
91
- <AuthButton
92
- loading={is2FALoading}
93
- disabled={!useBackupCode && twoFactorCode.length !== AUTH.OTP_LENGTH}
94
- >
95
- {content.button}
96
- </AuthButton>
100
+ {twoFactorAttemptsRemaining !== null && twoFactorAttemptsRemaining <= 3 && (
101
+ <div className="auth-dev-notice">
102
+ {twoFactorAttemptsRemaining} attempt{twoFactorAttemptsRemaining !== 1 ? 's' : ''} remaining
103
+ </div>
104
+ )}
97
105
 
98
- <div className="auth-actions">
99
- <AuthLink
100
- onClick={useBackupCode ? handleUseTOTP : handleUseBackupCode}
101
- disabled={is2FALoading}
106
+ <AuthButton
107
+ loading={is2FALoading}
108
+ disabled={!useBackupCode && twoFactorCode.length !== AUTH.OTP_LENGTH}
102
109
  >
103
- {useBackupCode ? content.toggle.toTotp : content.toggle.toBackup}
104
- </AuthLink>
105
- </div>
106
- </form>
110
+ {content.button}
111
+ </AuthButton>
112
+
113
+ <div className="auth-actions">
114
+ <AuthLink
115
+ onClick={useBackupCode ? handleUseTOTP : handleUseBackupCode}
116
+ disabled={is2FALoading}
117
+ >
118
+ {useBackupCode ? content.toggle.toTotp : content.toggle.toBackup}
119
+ </AuthLink>
120
+ <AuthLink onClick={handleBackToIdentifier} disabled={is2FALoading}>
121
+ {content.changeEmail}
122
+ </AuthLink>
123
+ </div>
124
+ </form>
125
+ </div>
107
126
  </AuthContainer>
108
127
  );
109
128
  };
@@ -14,7 +14,6 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
14
14
  supportUrl,
15
15
  termsUrl,
16
16
  privacyUrl,
17
- enablePhoneAuth = false,
18
17
  enableGithubAuth = false,
19
18
  enable2FASetup = true,
20
19
  logoUrl,
@@ -46,7 +45,6 @@ export const AuthFormProvider: React.FC<AuthLayoutProps> = ({
46
45
  supportUrl,
47
46
  termsUrl,
48
47
  privacyUrl,
49
- enablePhoneAuth,
50
48
  enableGithubAuth,
51
49
  enable2FASetup,
52
50
  logoUrl,
@@ -3,13 +3,12 @@
3
3
  */
4
4
 
5
5
  // Main layout
6
- export { AuthLayout } from './AuthLayout';
7
- export type { AuthLayoutProps } from './AuthLayout';
6
+ export { AuthLayout, useAuthLayoutContext } from './AuthLayout';
8
7
 
9
8
  // Context and hooks
10
9
  export { AuthFormProvider, useAuthFormContext } from './context';
11
10
 
12
- // Apple-style step components
11
+ // Step components
13
12
  export {
14
13
  IdentifierStep,
15
14
  OTPStep,
@@ -23,12 +22,16 @@ export {
23
22
  // Shared UI components
24
23
  export * from './components/shared';
25
24
 
26
- // Types (re-exported from @djangocfg/api/auth)
25
+ // Types all from single source: ./types
27
26
  export type {
28
- AuthChannel,
27
+ // Auth logic (re-exported from @djangocfg/api/auth)
29
28
  AuthStep,
30
29
  AuthFormState,
31
- AuthFormContextType,
30
+ AuthFormReturn,
31
+ UseAuthFormOptions,
32
+ // Layout UI types (owned by this package)
32
33
  AuthLayoutConfig,
34
+ AuthFormContextType,
35
+ AuthLayoutProps,
33
36
  AuthHelpProps,
34
37
  } from './types';