@stevederico/skateboard-ui 2.9.3 → 2.9.5

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.
@@ -8,7 +8,9 @@
8
8
  "Bash(node --check:*)",
9
9
  "Bash(echo:*)",
10
10
  "Bash(fi)",
11
- "Bash(done)"
11
+ "Bash(done)",
12
+ "Bash(git commit:*)",
13
+ "Bash(git push:*)"
12
14
  ]
13
15
  }
14
16
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # CHANGELOG
2
2
 
3
+ 2.9.5
4
+
5
+ Reuse SignInView in AuthOverlay
6
+ Reuse SignUpView in AuthOverlay
7
+ Add embedded prop to views
8
+
9
+ 2.9.4
10
+
11
+ Remove unused Card imports
12
+ Fix CardHeader indentation
13
+
3
14
  2.9.3
4
15
 
5
16
  Simplify sidebar brand styling
@@ -1,12 +1,9 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '../shadcn/ui/dialog.jsx';
3
- import { Input } from '../shadcn/ui/input.jsx';
4
- import { Label } from '../shadcn/ui/label.jsx';
5
- import { Button } from '../shadcn/ui/button.jsx';
6
- import { Alert, AlertDescription } from '../shadcn/ui/alert.jsx';
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../shadcn/ui/dialog.jsx';
7
3
  import DynamicIcon from '../core/DynamicIcon.jsx';
8
4
  import { getState } from '../core/Context.jsx';
9
- import { getBackendURL } from '../core/Utilities.js';
5
+ import SignInView from '../views/SignInView.jsx';
6
+ import SignUpView from '../views/SignUpView.jsx';
10
7
 
11
8
  /**
12
9
  * Modal authentication overlay with sign-in and sign-up forms.
@@ -29,109 +26,25 @@ export default function AuthOverlay() {
29
26
  const { visible } = state.authOverlay;
30
27
 
31
28
  const [mode, setMode] = useState('signin');
32
- const [email, setEmail] = useState('');
33
- const [password, setPassword] = useState('');
34
- const [name, setName] = useState('');
35
- const [errorMessage, setErrorMessage] = useState('');
36
- const [isSubmitting, setIsSubmitting] = useState(false);
37
- const firstInputRef = useRef(null);
38
29
 
39
- // Reset form when dialog opens
30
+ // Reset mode when dialog opens
40
31
  useEffect(() => {
41
32
  if (visible) {
42
33
  setMode('signin');
43
- setEmail('');
44
- setPassword('');
45
- setName('');
46
- setErrorMessage('');
47
- setIsSubmitting(false);
48
34
  }
49
35
  }, [visible]);
50
36
 
51
- // Focus first input when dialog opens or mode changes
52
- useEffect(() => {
53
- if (visible && firstInputRef.current) {
54
- const t = setTimeout(() => firstInputRef.current?.focus(), 100);
55
- return () => clearTimeout(t);
56
- }
57
- }, [visible, mode]);
58
-
59
37
  function handleClose() {
60
38
  dispatch({ type: 'HIDE_AUTH_OVERLAY' });
61
39
  }
62
40
 
63
- async function handleSignIn(e) {
64
- e.preventDefault();
65
- if (isSubmitting) return;
66
- setIsSubmitting(true);
67
- try {
68
- const response = await fetch(`${getBackendURL()}/signin`, {
69
- method: 'POST',
70
- credentials: 'include',
71
- headers: { 'Content-Type': 'application/json' },
72
- body: JSON.stringify({ email, password })
73
- });
74
-
75
- if (response.ok) {
76
- const data = await response.json();
77
- dispatch({ type: 'SET_USER', payload: data });
78
- dispatch({ type: 'AUTH_OVERLAY_SUCCESS' });
79
- } else {
80
- setErrorMessage('Invalid Credentials');
81
- }
82
- } catch (error) {
83
- setErrorMessage('Server Error');
84
- } finally {
85
- setIsSubmitting(false);
86
- }
87
- }
88
-
89
- async function handleSignUp(e) {
90
- e.preventDefault();
91
- if (isSubmitting) return;
92
- if (password.length < 6) {
93
- setErrorMessage('Password must be at least 6 characters');
94
- return;
95
- }
96
- if (password.length > 72) {
97
- setErrorMessage('Password must be 72 characters or less');
98
- return;
99
- }
100
- setIsSubmitting(true);
101
- try {
102
- const response = await fetch(`${getBackendURL()}/signup`, {
103
- method: 'POST',
104
- credentials: 'include',
105
- headers: { 'Content-Type': 'application/json' },
106
- body: JSON.stringify({ email, password, name })
107
- });
108
-
109
- if (response.ok) {
110
- const data = await response.json();
111
- // Save CSRF token
112
- const csrfCookie = document.cookie.split('; ').find(row => row.startsWith('csrf_token='));
113
- const csrfToken = csrfCookie ? csrfCookie.split('=')[1] : data.csrfToken;
114
- if (csrfToken) {
115
- const appName = constants.appName || 'skateboard';
116
- const csrfKey = `${appName.toLowerCase().replace(/\s+/g, '-')}_csrf`;
117
- localStorage.setItem(csrfKey, csrfToken);
118
- }
119
- dispatch({ type: 'SET_USER', payload: data });
120
- dispatch({ type: 'AUTH_OVERLAY_SUCCESS' });
121
- } else {
122
- setErrorMessage('Invalid Credentials');
123
- }
124
- } catch (error) {
125
- setErrorMessage('Server Error');
126
- } finally {
127
- setIsSubmitting(false);
128
- }
41
+ function handleSuccess() {
42
+ dispatch({ type: 'AUTH_OVERLAY_SUCCESS' });
129
43
  }
130
44
 
131
45
  return (
132
46
  <Dialog open={visible} onOpenChange={(open) => { if (!open) handleClose(); }}>
133
47
  <DialogContent>
134
- {/* Branding */}
135
48
  <DialogHeader className="items-center text-center">
136
49
  <div className="flex items-center justify-center gap-3">
137
50
  <div className="bg-app rounded-2xl flex aspect-square size-10 items-center justify-center">
@@ -139,133 +52,21 @@ export default function AuthOverlay() {
139
52
  </div>
140
53
  <span className="text-2xl font-bold">{constants.appName}</span>
141
54
  </div>
142
- <DialogTitle>{mode === 'signin' ? 'Welcome back' : 'Create an account'}</DialogTitle>
143
- <DialogDescription>
144
- {mode === 'signin' ? 'Sign in to your account' : 'Enter your details to get started'}
145
- </DialogDescription>
55
+ <DialogTitle className="sr-only">{mode === 'signin' ? 'Sign In' : 'Sign Up'}</DialogTitle>
146
56
  </DialogHeader>
147
57
 
148
- {errorMessage && (
149
- <Alert variant="destructive">
150
- <AlertDescription className="text-center">{errorMessage}</AlertDescription>
151
- </Alert>
152
- )}
153
-
154
58
  {mode === 'signin' ? (
155
- <form onSubmit={handleSignIn} className="flex flex-col gap-4">
156
- <div className="flex flex-col gap-2">
157
- <Label htmlFor="overlay-email">Email</Label>
158
- <Input
159
- ref={firstInputRef}
160
- id="overlay-email"
161
- type="email"
162
- placeholder="john@example.com"
163
- required
164
- value={email}
165
- onChange={(e) => { setEmail(e.target.value); setErrorMessage(''); }}
166
- />
167
- </div>
168
- <div className="flex flex-col gap-2">
169
- <Label htmlFor="overlay-password">Password</Label>
170
- <Input
171
- id="overlay-password"
172
- type="password"
173
- placeholder="••••••••"
174
- required
175
- value={password}
176
- onChange={(e) => setPassword(e.target.value)}
177
- />
178
- </div>
179
- <Button
180
- type="submit"
181
- variant="gradient"
182
- size="cta"
183
- className="w-full"
184
- disabled={isSubmitting}
185
- >
186
- <span className="relative z-20 flex items-center justify-center gap-2 drop-shadow-sm">
187
- <DynamicIcon name="sparkles" size={16} color="currentColor" strokeWidth={2} className="animate-pulse" />
188
- {isSubmitting ? 'Signing in...' : 'Sign In'}
189
- </span>
190
- </Button>
191
- <div className="text-center text-sm">
192
- <span className="text-muted-foreground">Don't have an account?</span>{' '}
193
- <Button
194
- variant="link"
195
- className="p-0 h-auto"
196
- onClick={() => { setMode('signup'); setErrorMessage(''); }}
197
- >
198
- Sign Up
199
- </Button>
200
- </div>
201
- </form>
59
+ <SignInView
60
+ embedded
61
+ onSuccess={handleSuccess}
62
+ onSwitchMode={() => setMode('signup')}
63
+ />
202
64
  ) : (
203
- <form onSubmit={handleSignUp} className="flex flex-col gap-4">
204
- <div className="flex flex-col gap-2">
205
- <Label htmlFor="overlay-name">Name</Label>
206
- <Input
207
- ref={firstInputRef}
208
- id="overlay-name"
209
- placeholder="John Doe"
210
- required
211
- value={name}
212
- onChange={(e) => { setName(e.target.value); setErrorMessage(''); }}
213
- />
214
- </div>
215
- <div className="flex flex-col gap-2">
216
- <Label htmlFor="overlay-signup-email">Email</Label>
217
- <Input
218
- id="overlay-signup-email"
219
- type="email"
220
- placeholder="john@example.com"
221
- required
222
- value={email}
223
- onChange={(e) => { setEmail(e.target.value); setErrorMessage(''); }}
224
- />
225
- </div>
226
- <div className="flex flex-col gap-2">
227
- <Label htmlFor="overlay-signup-password">Password</Label>
228
- <Input
229
- id="overlay-signup-password"
230
- type="password"
231
- placeholder="••••••••"
232
- required
233
- minLength={6}
234
- maxLength={72}
235
- value={password}
236
- onChange={(e) => { setPassword(e.target.value); setErrorMessage(''); }}
237
- />
238
- <p className="text-xs text-muted-foreground">Minimum 6 characters</p>
239
- </div>
240
- <Button
241
- type="submit"
242
- variant="gradient"
243
- size="cta"
244
- className="w-full"
245
- disabled={isSubmitting}
246
- >
247
- <span className="relative z-20 flex items-center justify-center gap-2 drop-shadow-sm">
248
- <DynamicIcon name="sparkles" size={16} color="currentColor" strokeWidth={2} className="animate-pulse" />
249
- {isSubmitting ? 'Signing up...' : 'Sign Up'}
250
- </span>
251
- </Button>
252
- <div className="text-center text-sm">
253
- <span className="text-muted-foreground">Already have an account?</span>{' '}
254
- <Button
255
- variant="link"
256
- className="p-0 h-auto"
257
- onClick={() => { setMode('signin'); setErrorMessage(''); }}
258
- >
259
- Sign In
260
- </Button>
261
- </div>
262
- <div className="text-center text-xs text-muted-foreground">
263
- By registering you agree to our{" "}
264
- <a href="/terms" target="_blank" rel="noopener noreferrer" className="underline underline-offset-4 hover:text-foreground">Terms of Service</a>,{" "}
265
- <a href="/eula" target="_blank" rel="noopener noreferrer" className="underline underline-offset-4 hover:text-foreground">EULA</a>,{" "}
266
- <a href="/privacy" target="_blank" rel="noopener noreferrer" className="underline underline-offset-4 hover:text-foreground">Privacy Policy</a>
267
- </div>
268
- </form>
65
+ <SignUpView
66
+ embedded
67
+ onSuccess={handleSuccess}
68
+ onSwitchMode={() => setMode('signin')}
69
+ />
269
70
  )}
270
71
  </DialogContent>
271
72
  </Dialog>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stevederico/skateboard-ui",
3
3
  "private": false,
4
- "version": "2.9.3",
4
+ "version": "2.9.5",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  "./Sidebar": {
@@ -3,7 +3,7 @@ import { cn } from "../shadcn/lib/utils"
3
3
  import { Button } from "../shadcn/ui/button"
4
4
  import { Input } from "../shadcn/ui/input"
5
5
  import { Label } from "../shadcn/ui/label"
6
- import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../shadcn/ui/card"
6
+ import { Card, CardContent, CardHeader } from "../shadcn/ui/card"
7
7
  import { Alert, AlertDescription } from "../shadcn/ui/alert"
8
8
  import DynamicIcon from '../core/DynamicIcon';
9
9
  import { useNavigate } from 'react-router-dom';
@@ -11,22 +11,31 @@ import { getState } from "../core/Context.jsx";
11
11
  import { getBackendURL } from '../core/Utilities'
12
12
 
13
13
  /**
14
- * Full-page sign-in form.
14
+ * Sign-in form component.
15
15
  *
16
- * Authenticates via POST to /signin, dispatches SET_USER on success,
17
- * and navigates to /app. Shows app branding and a link to sign up.
16
+ * Authenticates via POST to /signin, dispatches SET_USER on success.
17
+ * In full-page mode, navigates to /app. In embedded mode, calls onSuccess.
18
18
  *
19
19
  * @param {Object} props
20
20
  * @param {string} [props.className] - Additional CSS classes
21
- * @returns {JSX.Element} Sign-in page
21
+ * @param {boolean} [props.embedded=false] - Render without page wrapper (for dialogs)
22
+ * @param {function} [props.onSuccess] - Called after successful sign-in (embedded mode)
23
+ * @param {function} [props.onSwitchMode] - Called when user clicks "Sign Up" (embedded mode)
24
+ * @returns {JSX.Element} Sign-in form
22
25
  *
23
26
  * @example
24
- * import SignInView from '@stevederico/skateboard-ui/SignInView';
25
- *
27
+ * // Full page
26
28
  * <Route path="/signin" element={<SignInView />} />
29
+ *
30
+ * @example
31
+ * // Embedded in dialog
32
+ * <SignInView embedded onSuccess={handleSuccess} onSwitchMode={() => setMode('signup')} />
27
33
  */
28
34
  export default function LoginForm({
29
35
  className,
36
+ embedded = false,
37
+ onSuccess,
38
+ onSwitchMode,
30
39
  ...props
31
40
  }) {
32
41
  const { state, dispatch } = getState();
@@ -62,7 +71,11 @@ export default function LoginForm({
62
71
  if (response.ok) {
63
72
  const data = await response.json();
64
73
  dispatch({ type: 'SET_USER', payload: data });
65
- navigate('/app');
74
+ if (embedded && onSuccess) {
75
+ onSuccess();
76
+ } else {
77
+ navigate('/app');
78
+ }
66
79
  } else {
67
80
  setErrorMessage('Invalid Credentials');
68
81
  }
@@ -73,6 +86,70 @@ export default function LoginForm({
73
86
  }
74
87
  }
75
88
 
89
+ const formContent = (
90
+ <>
91
+ {errorMessage && (
92
+ <Alert variant="destructive" className="mb-4">
93
+ <AlertDescription className="text-center">{errorMessage}</AlertDescription>
94
+ </Alert>
95
+ )}
96
+
97
+ <form onSubmit={signInClicked} className="flex flex-col gap-4">
98
+ <div className="flex flex-col gap-2">
99
+ <Label htmlFor="email">Email</Label>
100
+ <Input
101
+ ref={emailInputRef}
102
+ id="email"
103
+ type="email"
104
+ placeholder="john@example.com"
105
+ required
106
+ value={email}
107
+ onChange={(e) => {
108
+ setEmail(e.target.value);
109
+ setErrorMessage('');
110
+ }}
111
+ />
112
+ </div>
113
+
114
+ <div className="flex flex-col gap-2">
115
+ <Label htmlFor="password">Password</Label>
116
+ <Input
117
+ id="password"
118
+ type="password"
119
+ placeholder="••••••••"
120
+ required
121
+ value={password}
122
+ onChange={(e) => setPassword(e.target.value)}
123
+ />
124
+ </div>
125
+
126
+ <Button
127
+ type="submit"
128
+ variant="gradient"
129
+ size="cta"
130
+ className="w-full"
131
+ disabled={isSubmitting}
132
+ >
133
+ <span className="relative z-20 flex items-center justify-center gap-2 drop-shadow-sm">
134
+ <DynamicIcon name="sparkles" size={16} color="currentColor" strokeWidth={2} className="animate-pulse" />
135
+ {isSubmitting ? "Signing in..." : "Sign In"}
136
+ </span>
137
+ </Button>
138
+
139
+ <div className="text-center text-sm">
140
+ <span className="text-muted-foreground">Don't have an account?</span>{" "}
141
+ <Button variant="link" className="p-0 h-auto" onClick={() => embedded && onSwitchMode ? onSwitchMode() : navigate('/signup')}>
142
+ Sign Up
143
+ </Button>
144
+ </div>
145
+ </form>
146
+ </>
147
+ );
148
+
149
+ if (embedded) {
150
+ return formContent;
151
+ }
152
+
76
153
  return (
77
154
  <div className="fixed inset-0 bg-background overflow-auto">
78
155
  <div className={cn("flex flex-col items-center justify-center min-h-screen p-4", className)} {...props}>
@@ -84,63 +161,9 @@ export default function LoginForm({
84
161
  </div>
85
162
  <span className="text-3xl font-bold">{constants.appName}</span>
86
163
  </div>
87
- </CardHeader>
164
+ </CardHeader>
88
165
  <CardContent>
89
- {errorMessage && (
90
- <Alert variant="destructive" className="mb-4">
91
- <AlertDescription className="text-center">{errorMessage}</AlertDescription>
92
- </Alert>
93
- )}
94
-
95
- <form onSubmit={signInClicked} className="flex flex-col gap-4">
96
- <div className="flex flex-col gap-2">
97
- <Label htmlFor="email">Email</Label>
98
- <Input
99
- ref={emailInputRef}
100
- id="email"
101
- type="email"
102
- placeholder="john@example.com"
103
- required
104
- value={email}
105
- onChange={(e) => {
106
- setEmail(e.target.value);
107
- setErrorMessage('');
108
- }}
109
- />
110
- </div>
111
-
112
- <div className="flex flex-col gap-2">
113
- <Label htmlFor="password">Password</Label>
114
- <Input
115
- id="password"
116
- type="password"
117
- placeholder="••••••••"
118
- required
119
- value={password}
120
- onChange={(e) => setPassword(e.target.value)}
121
- />
122
- </div>
123
-
124
- <Button
125
- type="submit"
126
- variant="gradient"
127
- size="cta"
128
- className="w-full"
129
- disabled={isSubmitting}
130
- >
131
- <span className="relative z-20 flex items-center justify-center gap-2 drop-shadow-sm">
132
- <DynamicIcon name="sparkles" size={16} color="currentColor" strokeWidth={2} className="animate-pulse" />
133
- {isSubmitting ? "Signing in..." : "Sign In"}
134
- </span>
135
- </Button>
136
-
137
- <div className="text-center text-sm">
138
- <span className="text-muted-foreground">Don't have an account?</span>{" "}
139
- <Button variant="link" className="p-0 h-auto" onClick={() => navigate('/signup')}>
140
- Sign Up
141
- </Button>
142
- </div>
143
- </form>
166
+ {formContent}
144
167
  </CardContent>
145
168
  </Card>
146
169
  </div>
@@ -3,7 +3,7 @@ import { cn } from "../shadcn/lib/utils"
3
3
  import { Button } from "../shadcn/ui/button"
4
4
  import { Input } from "../shadcn/ui/input"
5
5
  import { Label } from "../shadcn/ui/label"
6
- import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "../shadcn/ui/card"
6
+ import { Card, CardContent, CardHeader } from "../shadcn/ui/card"
7
7
  import { Alert, AlertDescription } from "../shadcn/ui/alert"
8
8
  import DynamicIcon from '../core/DynamicIcon';
9
9
  import { useNavigate } from 'react-router-dom';
@@ -11,23 +11,32 @@ import { getState } from "../core/Context.jsx";
11
11
  import { getBackendURL } from '../core/Utilities'
12
12
 
13
13
  /**
14
- * Full-page sign-up form.
14
+ * Sign-up form component.
15
15
  *
16
16
  * Creates account via POST to /signup with name, email, and password.
17
- * Validates password length (6-72 chars), dispatches SET_USER on success,
18
- * and navigates to /app. Includes legal agreement links.
17
+ * Validates password length (6-72 chars), dispatches SET_USER on success.
18
+ * In full-page mode, navigates to /app. In embedded mode, calls onSuccess.
19
19
  *
20
20
  * @param {Object} props
21
21
  * @param {string} [props.className] - Additional CSS classes
22
- * @returns {JSX.Element} Sign-up page
22
+ * @param {boolean} [props.embedded=false] - Render without page wrapper (for dialogs)
23
+ * @param {function} [props.onSuccess] - Called after successful sign-up (embedded mode)
24
+ * @param {function} [props.onSwitchMode] - Called when user clicks "Sign In" (embedded mode)
25
+ * @returns {JSX.Element} Sign-up form
23
26
  *
24
27
  * @example
25
- * import SignUpView from '@stevederico/skateboard-ui/SignUpView';
26
- *
28
+ * // Full page
27
29
  * <Route path="/signup" element={<SignUpView />} />
30
+ *
31
+ * @example
32
+ * // Embedded in dialog
33
+ * <SignUpView embedded onSuccess={handleSuccess} onSwitchMode={() => setMode('signin')} />
28
34
  */
29
35
  export default function LoginForm({
30
36
  className,
37
+ embedded = false,
38
+ onSuccess,
39
+ onSwitchMode,
31
40
  ...props
32
41
  }) {
33
42
  const { state, dispatch } = getState();
@@ -76,7 +85,11 @@ export default function LoginForm({
76
85
  localStorage.setItem(csrfKey, csrfToken);
77
86
  }
78
87
  dispatch({ type: 'SET_USER', payload: data });
79
- navigate('/app');
88
+ if (embedded && onSuccess) {
89
+ onSuccess();
90
+ } else {
91
+ navigate('/app');
92
+ }
80
93
  } else {
81
94
  setErrorMessage('Invalid Credentials')
82
95
  }
@@ -86,6 +99,96 @@ export default function LoginForm({
86
99
  }
87
100
  }
88
101
 
102
+ const formContent = (
103
+ <>
104
+ {errorMessage !== '' && (
105
+ <Alert variant="destructive" className="mb-4">
106
+ <AlertDescription className="text-center">{errorMessage}</AlertDescription>
107
+ </Alert>
108
+ )}
109
+
110
+ <form onSubmit={signUpClicked} className="flex flex-col gap-4">
111
+ <div className="flex flex-col gap-2">
112
+ <Label htmlFor="name">Name</Label>
113
+ <Input
114
+ ref={nameInputRef}
115
+ id="name"
116
+ placeholder="John Doe"
117
+ required
118
+ value={name}
119
+ onChange={(e) => {
120
+ setName(e.target.value);
121
+ setErrorMessage('');
122
+ }}
123
+ />
124
+ </div>
125
+
126
+ <div className="flex flex-col gap-2">
127
+ <Label htmlFor="email">Email</Label>
128
+ <Input
129
+ id="email"
130
+ type="email"
131
+ placeholder="john@example.com"
132
+ required
133
+ value={email}
134
+ onChange={(e) => {
135
+ setEmail(e.target.value);
136
+ setErrorMessage('');
137
+ }}
138
+ />
139
+ </div>
140
+
141
+ <div className="flex flex-col gap-2">
142
+ <Label htmlFor="password">Password</Label>
143
+ <Input
144
+ id="password"
145
+ type="password"
146
+ placeholder="••••••••"
147
+ required
148
+ minLength={6}
149
+ maxLength={72}
150
+ value={password}
151
+ onChange={(e) => {
152
+ setPassword(e.target.value);
153
+ setErrorMessage('');
154
+ }}
155
+ />
156
+ <p className="text-xs text-muted-foreground">Minimum 6 characters</p>
157
+ </div>
158
+
159
+ <Button
160
+ type="submit"
161
+ variant="gradient"
162
+ size="cta"
163
+ className="w-full"
164
+ >
165
+ <span className="relative z-20 flex items-center justify-center gap-2 drop-shadow-sm">
166
+ <DynamicIcon name="sparkles" size={16} color="currentColor" strokeWidth={2} className="animate-pulse" />
167
+ Sign Up
168
+ </span>
169
+ </Button>
170
+
171
+ <div className="text-center text-sm">
172
+ <span className="text-muted-foreground">Already have an account?</span>{" "}
173
+ <Button variant="link" className="p-0 h-auto" onClick={(e) => { e.preventDefault(); embedded && onSwitchMode ? onSwitchMode() : navigate('/signin'); }}>
174
+ Sign In
175
+ </Button>
176
+ </div>
177
+ </form>
178
+
179
+ <div className="mt-4 text-center text-xs text-muted-foreground">
180
+ By registering you agree to our{" "}
181
+ <a href="/terms" className="underline underline-offset-4 hover:text-foreground">Terms of Service</a>,{" "}
182
+ <a href="/eula" className="underline underline-offset-4 hover:text-foreground">EULA</a>,{" "}
183
+ <a href="/privacy" className="underline underline-offset-4 hover:text-foreground">Privacy Policy</a>
184
+ </div>
185
+ </>
186
+ );
187
+
188
+ if (embedded) {
189
+ return formContent;
190
+ }
191
+
89
192
  return (
90
193
  <div className="fixed inset-0 bg-background overflow-auto">
91
194
  <div className={cn("flex flex-col items-center justify-center min-h-screen p-4", className)} {...props}>
@@ -97,89 +200,9 @@ export default function LoginForm({
97
200
  </div>
98
201
  <span className="text-3xl font-bold">{constants.appName}</span>
99
202
  </div>
100
- </CardHeader>
203
+ </CardHeader>
101
204
  <CardContent>
102
- {errorMessage !== '' && (
103
- <Alert variant="destructive" className="mb-4">
104
- <AlertDescription className="text-center">{errorMessage}</AlertDescription>
105
- </Alert>
106
- )}
107
-
108
- <form onSubmit={signUpClicked} className="flex flex-col gap-4">
109
- <div className="flex flex-col gap-2">
110
- <Label htmlFor="name">Name</Label>
111
- <Input
112
- ref={nameInputRef}
113
- id="name"
114
- placeholder="John Doe"
115
- required
116
- value={name}
117
- onChange={(e) => {
118
- setName(e.target.value);
119
- setErrorMessage('');
120
- }}
121
- />
122
- </div>
123
-
124
- <div className="flex flex-col gap-2">
125
- <Label htmlFor="email">Email</Label>
126
- <Input
127
- id="email"
128
- type="email"
129
- placeholder="john@example.com"
130
- required
131
- value={email}
132
- onChange={(e) => {
133
- setEmail(e.target.value);
134
- setErrorMessage('');
135
- }}
136
- />
137
- </div>
138
-
139
- <div className="flex flex-col gap-2">
140
- <Label htmlFor="password">Password</Label>
141
- <Input
142
- id="password"
143
- type="password"
144
- placeholder="••••••••"
145
- required
146
- minLength={6}
147
- maxLength={72}
148
- value={password}
149
- onChange={(e) => {
150
- setPassword(e.target.value);
151
- setErrorMessage('');
152
- }}
153
- />
154
- <p className="text-xs text-muted-foreground">Minimum 6 characters</p>
155
- </div>
156
-
157
- <Button
158
- type="submit"
159
- variant="gradient"
160
- size="cta"
161
- className="w-full"
162
- >
163
- <span className="relative z-20 flex items-center justify-center gap-2 drop-shadow-sm">
164
- <DynamicIcon name="sparkles" size={16} color="currentColor" strokeWidth={2} className="animate-pulse" />
165
- Sign Up
166
- </span>
167
- </Button>
168
-
169
- <div className="text-center text-sm">
170
- <span className="text-muted-foreground">Already have an account?</span>{" "}
171
- <Button variant="link" className="p-0 h-auto" onClick={(e) => { e.preventDefault(); navigate('/signin'); }}>
172
- Sign In
173
- </Button>
174
- </div>
175
- </form>
176
-
177
- <div className="mt-4 text-center text-xs text-muted-foreground">
178
- By registering you agree to our{" "}
179
- <a href="/terms" className="underline underline-offset-4 hover:text-foreground">Terms of Service</a>,{" "}
180
- <a href="/eula" className="underline underline-offset-4 hover:text-foreground">EULA</a>,{" "}
181
- <a href="/privacy" className="underline underline-offset-4 hover:text-foreground">Privacy Policy</a>
182
- </div>
205
+ {formContent}
183
206
  </CardContent>
184
207
  </Card>
185
208
  </div>