@djangocfg/layouts 2.1.109 → 2.1.111

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 (53) hide show
  1. package/package.json +16 -14
  2. package/src/components/errors/ErrorBoundary.tsx +12 -6
  3. package/src/components/errors/ErrorLayout.tsx +19 -9
  4. package/src/components/errors/errorConfig.ts +28 -22
  5. package/src/layouts/AuthLayout/AuthLayout.tsx +92 -20
  6. package/src/layouts/AuthLayout/components/index.ts +11 -7
  7. package/src/layouts/AuthLayout/components/oauth/index.ts +0 -1
  8. package/src/layouts/AuthLayout/components/shared/AuthButton.tsx +35 -0
  9. package/src/layouts/AuthLayout/components/shared/AuthContainer.tsx +56 -0
  10. package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +22 -0
  11. package/src/layouts/AuthLayout/components/shared/AuthError.tsx +26 -0
  12. package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +47 -0
  13. package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +53 -0
  14. package/src/layouts/AuthLayout/components/shared/AuthLink.tsx +41 -0
  15. package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +42 -0
  16. package/src/layouts/AuthLayout/components/shared/ChannelToggle.tsx +48 -0
  17. package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +57 -0
  18. package/src/layouts/AuthLayout/components/shared/index.ts +21 -0
  19. package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +171 -0
  20. package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +114 -0
  21. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +70 -0
  22. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +24 -0
  23. package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +125 -0
  24. package/src/layouts/AuthLayout/components/steps/SetupStep/index.tsx +91 -0
  25. package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +92 -0
  26. package/src/layouts/AuthLayout/components/steps/index.ts +6 -0
  27. package/src/layouts/AuthLayout/constants.ts +24 -0
  28. package/src/layouts/AuthLayout/content.ts +78 -0
  29. package/src/layouts/AuthLayout/hooks/index.ts +1 -0
  30. package/src/layouts/AuthLayout/hooks/useCopyToClipboard.ts +37 -0
  31. package/src/layouts/AuthLayout/index.ts +9 -5
  32. package/src/layouts/AuthLayout/styles/auth.css +578 -0
  33. package/src/layouts/ProfileLayout/ProfileLayout.tsx +130 -58
  34. package/src/layouts/ProfileLayout/components/TwoFactorSection.tsx +2 -2
  35. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +12 -4
  36. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +6 -2
  37. package/src/layouts/_components/UserMenu.tsx +14 -6
  38. package/src/snippets/AuthDialog/AuthDialog.tsx +15 -6
  39. package/src/snippets/Breadcrumbs.tsx +19 -8
  40. package/src/snippets/McpChat/components/ChatPanel.tsx +16 -6
  41. package/src/snippets/McpChat/components/ChatSidebar.tsx +20 -8
  42. package/src/snippets/PWAInstall/components/A2HSHint.tsx +23 -10
  43. package/src/snippets/PWAInstall/components/DesktopGuide.tsx +44 -32
  44. package/src/snippets/PWAInstall/components/IOSGuideDrawer.tsx +34 -25
  45. package/src/snippets/PWAInstall/components/IOSGuideModal.tsx +34 -25
  46. package/src/snippets/PushNotifications/components/PushPrompt.tsx +16 -6
  47. package/src/layouts/AuthLayout/components/AuthHelp.tsx +0 -114
  48. package/src/layouts/AuthLayout/components/AuthSuccess.tsx +0 -101
  49. package/src/layouts/AuthLayout/components/IdentifierForm.tsx +0 -322
  50. package/src/layouts/AuthLayout/components/OTPForm.tsx +0 -174
  51. package/src/layouts/AuthLayout/components/TwoFactorForm.tsx +0 -140
  52. package/src/layouts/AuthLayout/components/TwoFactorSetup.tsx +0 -286
  53. package/src/layouts/AuthLayout/components/oauth/OAuthProviders.tsx +0 -56
@@ -8,9 +8,10 @@
8
8
  */
9
9
 
10
10
  import { Bell, X } from 'lucide-react';
11
- import React, { useEffect, useState } from 'react';
11
+ import React, { useEffect, useMemo, useState } from 'react';
12
12
 
13
13
  import { useAuth } from '@djangocfg/api/auth';
14
+ import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
14
15
  import { Button } from '@djangocfg/ui-core';
15
16
 
16
17
  import { usePushNotifications } from '../hooks/usePushNotifications';
@@ -66,10 +67,19 @@ export function PushPrompt({
66
67
  vapidPublicKey,
67
68
  subscribeEndpoint,
68
69
  });
70
+ const t = useTypedT<I18nTranslations>();
69
71
 
70
72
  const [show, setShow] = useState(false);
71
73
  const [enabling, setEnabling] = useState(false);
72
74
 
75
+ const labels = useMemo(() => ({
76
+ enableNotifications: t('layouts.push.enableNotifications'),
77
+ stayUpdated: t('layouts.push.stayUpdated'),
78
+ enable: t('layouts.push.enable'),
79
+ notNow: t('layouts.push.notNow'),
80
+ dismiss: t('layouts.push.dismiss'),
81
+ }), [t]);
82
+
73
83
  // Check if should show
74
84
  useEffect(() => {
75
85
  // Wait for auth to complete, don't show for unauthenticated users
@@ -132,9 +142,9 @@ export function PushPrompt({
132
142
 
133
143
  {/* Content */}
134
144
  <div className="flex-1 min-w-0">
135
- <p className="text-sm font-medium text-white mb-1">Enable notifications</p>
145
+ <p className="text-sm font-medium text-white mb-1">{labels.enableNotifications}</p>
136
146
  <p className="text-xs text-zinc-400 mb-3">
137
- Stay updated with important updates and alerts
147
+ {labels.stayUpdated}
138
148
  </p>
139
149
 
140
150
  {/* Actions */}
@@ -145,14 +155,14 @@ export function PushPrompt({
145
155
  size="sm"
146
156
  variant="default"
147
157
  >
148
- Enable
158
+ {labels.enable}
149
159
  </Button>
150
160
  <Button
151
161
  onClick={handleDismiss}
152
162
  size="sm"
153
163
  variant="ghost"
154
164
  >
155
- Not now
165
+ {labels.notNow}
156
166
  </Button>
157
167
  </div>
158
168
  </div>
@@ -163,7 +173,7 @@ export function PushPrompt({
163
173
  size="sm"
164
174
  variant="ghost"
165
175
  className="flex-shrink-0 p-1"
166
- aria-label="Dismiss"
176
+ aria-label={labels.dismiss}
167
177
  >
168
178
  <X className="w-4 h-4" />
169
179
  </Button>
@@ -1,114 +0,0 @@
1
- import { HelpCircle, Mail, MessageCircle } from 'lucide-react';
2
- import React from 'react';
3
-
4
- import { Button } from '@djangocfg/ui-core/components';
5
-
6
- import { useAuthFormContext } from '../context';
7
-
8
- import type { AuthHelpProps } from '../types';
9
-
10
- export const AuthHelp: React.FC<AuthHelpProps> = ({
11
- className = '',
12
- variant = 'default',
13
- }) => {
14
- const { supportUrl, channel } = useAuthFormContext();
15
-
16
- const getChannelIcon = () => {
17
- return channel === 'phone' ? (
18
- <MessageCircle className="w-4 h-4 text-muted-foreground" />
19
- ) : (
20
- <Mail className="w-4 h-4 text-muted-foreground" />
21
- );
22
- };
23
-
24
- const getHelpText = () => {
25
- return channel === 'phone' ? 'Check WhatsApp/SMS' : 'Check spam folder';
26
- };
27
-
28
- const getDetailedHelp = () => {
29
- if (channel === 'phone') {
30
- return {
31
- title: "Didn't receive the code?",
32
- tips: [
33
- '• Check your WhatsApp messages',
34
- '• Look for SMS messages',
35
- '• Ensure you have signal/internet',
36
- '• Wait a few minutes for delivery',
37
- ],
38
- };
39
- } else {
40
- return {
41
- title: "Didn't receive the email?",
42
- tips: [
43
- '• Check your spam or junk folder',
44
- '• Make sure you entered the correct email address',
45
- '• Wait a few minutes for the email to arrive',
46
- ],
47
- };
48
- }
49
- };
50
-
51
- if (variant === 'compact') {
52
- return (
53
- <div
54
- className={`flex items-center justify-between p-3 bg-muted/30 rounded-sm border border-border ${className}`}
55
- >
56
- <div className="flex items-center gap-2">
57
- {getChannelIcon()}
58
- <span className="text-sm text-muted-foreground">{getHelpText()}</span>
59
- </div>
60
- {supportUrl && (
61
- <Button
62
- asChild
63
- variant="ghost"
64
- size="sm"
65
- className="text-xs"
66
- >
67
- <a href={supportUrl} target="_blank" rel="noopener noreferrer" className="flex items-center gap-1">
68
- <HelpCircle className="w-3 h-3" />
69
- Need help?
70
- </a>
71
- </Button>
72
- )}
73
- </div>
74
- );
75
- }
76
-
77
- const helpData = getDetailedHelp();
78
-
79
- return (
80
- <div
81
- className={`flex flex-col gap-3 p-3 bg-muted/30 rounded-sm border border-border ${className}`}
82
- >
83
- <div className="flex items-start gap-3">
84
- {getChannelIcon()}
85
- <div className="flex flex-col gap-1">
86
- <h4 className="text-sm font-medium text-foreground">{helpData.title}</h4>
87
- <div className="flex flex-col gap-0.5 text-xs text-muted-foreground">
88
- {helpData.tips.map((tip, index) => (
89
- <p key={index}>{tip}</p>
90
- ))}
91
- </div>
92
- </div>
93
- </div>
94
-
95
- {supportUrl && (
96
- <div className="flex items-center justify-between pt-2 border-t border-border">
97
- <span className="text-xs text-muted-foreground">Still having trouble?</span>
98
- <Button
99
- asChild
100
- variant="ghost"
101
- size="sm"
102
- className="text-xs h-7 px-2"
103
- >
104
- <a href={supportUrl} target="_blank" rel="noopener noreferrer" className="flex items-center gap-1">
105
- <HelpCircle className="w-3 h-3" />
106
- Get Help
107
- </a>
108
- </Button>
109
- </div>
110
- )}
111
- </div>
112
- );
113
- };
114
-
@@ -1,101 +0,0 @@
1
- /**
2
- * Auth Success Component
3
- *
4
- * Full-screen success layout shown after successful authentication.
5
- * Displays a centered logo with a subtle animation, then redirects.
6
- */
7
-
8
- 'use client';
9
-
10
- import React, { useEffect, useState } from 'react';
11
-
12
- import { useCfgRouter } from '@djangocfg/api/auth';
13
-
14
- import { useAuthFormContext } from '../context';
15
-
16
- export interface AuthSuccessProps {
17
- className?: string;
18
- /** Delay before redirect in ms (default: 1500) */
19
- redirectDelay?: number;
20
- }
21
-
22
- export const AuthSuccess: React.FC<AuthSuccessProps> = ({ className, redirectDelay = 1500 }) => {
23
- const { logoUrl, redirectUrl } = useAuthFormContext();
24
- const router = useCfgRouter();
25
- const [isVisible, setIsVisible] = useState(false);
26
-
27
- useEffect(() => {
28
- // Trigger animation after mount
29
- const animTimer = setTimeout(() => setIsVisible(true), 50);
30
-
31
- // Redirect after delay
32
- const redirectTimer = setTimeout(() => {
33
- const finalUrl = redirectUrl || '/dashboard';
34
- router.hardPush(finalUrl);
35
- }, redirectDelay);
36
-
37
- return () => {
38
- clearTimeout(animTimer);
39
- clearTimeout(redirectTimer);
40
- };
41
- }, [redirectUrl, redirectDelay, router]);
42
-
43
- if (!logoUrl) {
44
- // Fallback: simple checkmark if no logo provided
45
- return (
46
- <div className={`fixed inset-0 flex items-center justify-center bg-background z-50 ${className || ''}`}>
47
- <div
48
- className={`transition-all duration-700 ease-out ${
49
- isVisible ? 'opacity-100 scale-100' : 'opacity-0 scale-95'
50
- }`}
51
- >
52
- <div className="w-24 h-24 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
53
- <svg
54
- className="w-12 h-12 text-green-600 dark:text-green-400"
55
- fill="none"
56
- stroke="currentColor"
57
- viewBox="0 0 24 24"
58
- >
59
- <path
60
- strokeLinecap="round"
61
- strokeLinejoin="round"
62
- strokeWidth={2}
63
- d="M5 13l4 4L19 7"
64
- />
65
- </svg>
66
- </div>
67
- </div>
68
- </div>
69
- );
70
- }
71
-
72
- return (
73
- <div className={`fixed inset-0 flex items-center justify-center bg-background z-50 ${className || ''}`}>
74
- <div
75
- className={`transition-all duration-700 ease-out ${
76
- isVisible ? 'opacity-100 scale-100' : 'opacity-0 scale-90'
77
- }`}
78
- >
79
- {/* Logo container with max size and animation */}
80
- <div className="relative">
81
- {/* Subtle glow effect */}
82
- <div
83
- className={`absolute inset-0 blur-3xl transition-opacity duration-1000 ${
84
- isVisible ? 'opacity-20' : 'opacity-0'
85
- }`}
86
- style={{
87
- background: 'radial-gradient(circle, currentColor 0%, transparent 70%)',
88
- }}
89
- />
90
-
91
- {/* Logo image */}
92
- <img
93
- src={logoUrl}
94
- alt="Success"
95
- className="relative w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48 object-contain"
96
- />
97
- </div>
98
- </div>
99
- </div>
100
- );
101
- };
@@ -1,322 +0,0 @@
1
- 'use client';
2
-
3
- import { Mail, Phone, Send, User } from 'lucide-react';
4
- import React, { useEffect, useState } from 'react';
5
-
6
- import {
7
- Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Checkbox, Input, Label,
8
- PhoneInput, Tabs, TabsContent, TabsList, TabsTrigger
9
- } from '@djangocfg/ui-core/components';
10
-
11
- import { useAuthFormContext } from '../context';
12
- import { AuthHelp } from './AuthHelp';
13
- import { OAuthProviders } from './oauth';
14
-
15
- export const IdentifierForm: React.FC = () => {
16
- const {
17
- identifier,
18
- channel,
19
- isLoading,
20
- acceptedTerms,
21
- termsUrl,
22
- privacyUrl,
23
- enablePhoneAuth,
24
- setIdentifier,
25
- setChannel,
26
- setAcceptedTerms,
27
- handleIdentifierSubmit,
28
- detectChannelFromIdentifier,
29
- validateIdentifier,
30
- error,
31
- } = useAuthFormContext();
32
-
33
- const [localChannel, setLocalChannel] = useState<'email' | 'phone'>(channel);
34
-
35
- // Sync localChannel with channel from context (for localStorage updates)
36
- useEffect(() => {
37
- setLocalChannel(channel);
38
- }, [channel]);
39
-
40
- // Force email channel if phone auth is disabled
41
- useEffect(() => {
42
- if (!enablePhoneAuth && localChannel === 'phone') {
43
- setLocalChannel('email');
44
- setChannel('email');
45
- // Clear identifier if it's a phone number
46
- if (identifier && detectChannelFromIdentifier(identifier) === 'phone') {
47
- setIdentifier('');
48
- }
49
- }
50
- }, [
51
- enablePhoneAuth,
52
- localChannel,
53
- identifier,
54
- setChannel,
55
- setIdentifier,
56
- detectChannelFromIdentifier,
57
- ]);
58
-
59
- // Handle identifier change with auto-detection
60
- const handleIdentifierChange = (value: string) => {
61
- setIdentifier(value);
62
-
63
- // Auto-detect channel if user is typing (only if phone auth is enabled)
64
- const detectedChannel = detectChannelFromIdentifier(value);
65
- if (detectedChannel && detectedChannel !== localChannel) {
66
- // Only switch to phone if phone auth is enabled
67
- if (detectedChannel === 'phone' && !enablePhoneAuth) {
68
- return; // Don't switch to phone channel if disabled
69
- }
70
- setLocalChannel(detectedChannel);
71
- setChannel(detectedChannel);
72
- }
73
- };
74
-
75
- // Handle manual channel switch
76
- const handleChannelChange = (newChannel: 'email' | 'phone') => {
77
- // Prevent switching to phone if phone auth is disabled
78
- if (newChannel === 'phone' && !enablePhoneAuth) {
79
- return;
80
- }
81
-
82
- setLocalChannel(newChannel);
83
- setChannel(newChannel);
84
- // Clear identifier when switching channels
85
- if (identifier && !validateIdentifier(identifier, newChannel)) {
86
- setIdentifier('');
87
- }
88
- };
89
-
90
- const getChannelDescription = () => {
91
- return localChannel === 'phone'
92
- ? 'Enter your phone number to receive a verification code via SMS'
93
- : 'Enter your email address to receive a verification code';
94
- };
95
-
96
- // Check if we have any links for terms/privacy - if not, we don't show the checkbox
97
- const hasAnyLinks = Boolean(termsUrl || privacyUrl);
98
-
99
- return (
100
- <Card className="w-full max-w-md mx-auto shadow-lg border border-border bg-card/50 backdrop-blur-sm">
101
- <CardHeader className="text-center pb-6">
102
- <div className="mx-auto w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center mb-4">
103
- <User className="w-6 h-6 text-primary" />
104
- </div>
105
- <CardTitle className="text-xl font-semibold">Sign In</CardTitle>
106
- <CardDescription className="text-muted-foreground">
107
- {getChannelDescription()}
108
- </CardDescription>
109
- </CardHeader>
110
- <CardContent className="space-y-6">
111
- {enablePhoneAuth ? (
112
- <Tabs
113
- value={localChannel}
114
- onValueChange={(value) => handleChannelChange(value as 'email' | 'phone')}
115
- >
116
- {/* Channel Selection Tabs */}
117
- <TabsList className="grid w-full grid-cols-2">
118
- <TabsTrigger value="email" className="flex items-center gap-2">
119
- <Mail className="w-4 h-4" />
120
- Email
121
- </TabsTrigger>
122
- <TabsTrigger value="phone" className="flex items-center gap-2">
123
- <Phone className="w-4 h-4" />
124
- Phone
125
- </TabsTrigger>
126
- </TabsList>
127
-
128
- <form onSubmit={handleIdentifierSubmit} className="space-y-6 mt-6">
129
- <TabsContent value="email" className="space-y-3 mt-0">
130
- <Label
131
- htmlFor="identifier"
132
- className="text-sm font-medium text-foreground flex items-center gap-2"
133
- >
134
- <Mail className="w-4 h-4" />
135
- Email Address
136
- </Label>
137
- <Input
138
- id="identifier"
139
- type="email"
140
- placeholder="Enter your email address"
141
- value={identifier}
142
- onChange={(e) => handleIdentifierChange(e.target.value)}
143
- disabled={isLoading}
144
- required
145
- className="h-11 text-base"
146
- />
147
- </TabsContent>
148
-
149
- <TabsContent value="phone" className="space-y-3 mt-0">
150
- <Label
151
- htmlFor="phone-identifier"
152
- className="text-sm font-medium text-foreground flex items-center gap-2"
153
- >
154
- <Phone className="w-4 h-4" />
155
- Phone Number
156
- </Label>
157
- <PhoneInput
158
- value={identifier}
159
- onChange={(value) => handleIdentifierChange(value || '')}
160
- disabled={isLoading}
161
- placeholder="Enter your phone number"
162
- defaultCountry="US"
163
- className="h-11 text-base"
164
- />
165
- </TabsContent>
166
-
167
- {/* Terms and Conditions - only show if we have links */}
168
- {hasAnyLinks && (
169
- <div className="flex items-start gap-3">
170
- <Checkbox
171
- id="terms"
172
- checked={acceptedTerms}
173
- onCheckedChange={setAcceptedTerms}
174
- disabled={isLoading}
175
- className="mt-1"
176
- />
177
- <div className="text-sm text-muted-foreground leading-5">
178
- <Label htmlFor="terms" className="cursor-pointer">
179
- I agree to the{' '}
180
- {termsUrl && (
181
- <>
182
- <a
183
- href={termsUrl}
184
- target="_blank"
185
- rel="noopener noreferrer"
186
- className="text-primary hover:underline font-medium"
187
- >
188
- Terms of Service
189
- </a>
190
- {privacyUrl && <>{' '}and{' '}</>}
191
- </>
192
- )}
193
- {privacyUrl && (
194
- <a
195
- href={privacyUrl}
196
- target="_blank"
197
- rel="noopener noreferrer"
198
- className="text-primary hover:underline font-medium"
199
- >
200
- Privacy Policy
201
- </a>
202
- )}
203
- </Label>
204
- </div>
205
- </div>
206
- )}
207
-
208
- {/* Error Message */}
209
- {error && (
210
- <div className="text-sm text-destructive bg-destructive/10 p-3 rounded-md border border-destructive/20">
211
- {error}
212
- </div>
213
- )}
214
-
215
- {/* Submit Button */}
216
- <Button
217
- type="submit"
218
- size="lg"
219
- className="w-full"
220
- disabled={!identifier || (hasAnyLinks && !acceptedTerms)}
221
- loading={isLoading}
222
- >
223
- <Send className="w-4 h-4" />
224
- Send verification code
225
- </Button>
226
- </form>
227
- </Tabs>
228
- ) : (
229
- <form onSubmit={handleIdentifierSubmit} className="space-y-6 mt-6">
230
- {/* Email-only input when phone auth is disabled */}
231
- <div className="space-y-3">
232
- <Label
233
- htmlFor="email-only"
234
- className="text-sm font-medium text-foreground flex items-center gap-2"
235
- >
236
- <Mail className="w-4 h-4" />
237
- Email Address
238
- </Label>
239
- <Input
240
- id="email-only"
241
- type="email"
242
- placeholder="Enter your email address"
243
- value={identifier}
244
- onChange={(e) => handleIdentifierChange(e.target.value)}
245
- disabled={isLoading}
246
- required
247
- className="h-11 text-base"
248
- />
249
- </div>
250
-
251
- {/* Terms and Conditions - only show if we have links */}
252
- {hasAnyLinks && (
253
- <div className="flex items-start gap-3">
254
- <Checkbox
255
- id="terms-email"
256
- checked={acceptedTerms}
257
- onCheckedChange={setAcceptedTerms}
258
- disabled={isLoading}
259
- className="mt-1"
260
- />
261
- <div className="text-sm text-muted-foreground leading-5">
262
- <Label htmlFor="terms-email" className="cursor-pointer">
263
- I agree to the{' '}
264
- {termsUrl && (
265
- <>
266
- <a
267
- href={termsUrl}
268
- target="_blank"
269
- rel="noopener noreferrer"
270
- className="text-primary hover:underline font-medium"
271
- >
272
- Terms of Service
273
- </a>
274
- {privacyUrl && <>{' '}and{' '}</>}
275
- </>
276
- )}
277
- {privacyUrl && (
278
- <a
279
- href={privacyUrl}
280
- target="_blank"
281
- rel="noopener noreferrer"
282
- className="text-primary hover:underline font-medium"
283
- >
284
- Privacy Policy
285
- </a>
286
- )}
287
- </Label>
288
- </div>
289
- </div>
290
- )}
291
-
292
- {/* Error Message */}
293
- {error && (
294
- <div className="text-sm text-destructive bg-destructive/10 p-3 rounded-md border border-destructive/20">
295
- {error}
296
- </div>
297
- )}
298
-
299
- {/* Submit Button */}
300
- <Button
301
- type="submit"
302
- size="lg"
303
- className="w-full"
304
- disabled={!identifier || (hasAnyLinks && !acceptedTerms)}
305
- loading={isLoading}
306
- >
307
- <Send className="w-4 h-4" />
308
- Send verification code
309
- </Button>
310
- </form>
311
- )}
312
-
313
- {/* OAuth Providers (GitHub, etc.) */}
314
- <OAuthProviders />
315
-
316
- {/* Help Section */}
317
- <AuthHelp />
318
- </CardContent>
319
- </Card>
320
- );
321
- };
322
-