astryx-dracula 0.2.0 → 0.3.0
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/BRAND.md +30 -18
- package/README.md +22 -11
- package/USAGE.md +29 -10
- package/astryx-dracula.js +19 -13
- package/astryx-dracula.variants.d.ts +15 -0
- package/package.json +59 -17
- package/shared/auth-chrome-config.ts +18 -0
- package/shared/auth-chrome.tsx +25 -0
- package/shared/auth-copy.ts +22 -0
- package/shared/chaptered-doc-config.ts +92 -0
- package/shared/chaptered-doc.tsx +332 -0
- package/shared/chart-hues.ts +27 -0
- package/shared/chart-labels.tsx +44 -0
- package/shared/chart-legend.tsx +100 -0
- package/shared/chart-panel-style.ts +13 -0
- package/shared/data-bar.tsx +160 -0
- package/shared/gallery-image.ts +20 -0
- package/shared/login-demo.ts +7 -0
- package/shared/metric-delta.tsx +36 -0
- package/shared/order-desk.tsx +273 -0
- package/shared/revenue-chart.tsx +174 -0
- package/shared/scene-castle.tsx +322 -0
- package/shared/scene-frame.tsx +88 -0
- package/shared/scene-hues.ts +49 -0
- package/shared/scene-tile.tsx +128 -0
- package/shared/settings-data.ts +125 -0
- package/shared/settings-rows.tsx +179 -0
- package/shared/sparkline.tsx +94 -0
- package/shared/sso-icons.tsx +30 -0
- package/templates/ai-chat-landing.tsx +67 -32
- package/templates/ai-chat.tsx +41 -20
- package/templates/blank.tsx +5 -2
- package/templates/centered-hero.tsx +15 -124
- package/templates/classic-gallery.tsx +21 -65
- package/templates/contact-form.tsx +62 -40
- package/templates/dashboard-portfolio.tsx +146 -227
- package/templates/dashboard.tsx +123 -121
- package/templates/detail-page.tsx +101 -95
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +85 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +33 -21
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +54 -74
- package/templates/editor.tsx +119 -89
- package/templates/file-explorer.tsx +133 -84
- package/templates/form-two-column.tsx +62 -124
- package/templates/gallery-hero.tsx +17 -171
- package/templates/ide.tsx +27 -21
- package/templates/incident-console.tsx +105 -69
- package/templates/kanban-board.tsx +100 -41
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +38 -41
- package/templates/login-card.tsx +59 -86
- package/templates/login-split.tsx +88 -128
- package/templates/login-sso.tsx +107 -68
- package/templates/login.tsx +108 -52
- package/templates/messaging-shell.tsx +172 -143
- package/templates/mixed-gallery.tsx +31 -130
- package/templates/payment-form.tsx +369 -289
- package/templates/product-detail.tsx +40 -57
- package/templates/product-gallery.tsx +31 -40
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +145 -286
- package/templates/settings-sidebar.tsx +179 -267
- package/templates/settings.tsx +42 -13
- package/templates/shell-nav.tsx +32 -23
- package/templates/shell-side-nav.tsx +30 -19
- package/templates/shell-top-nav.tsx +81 -43
- package/templates/side-gallery.tsx +39 -93
- package/templates/table-grouped.tsx +63 -33
- package/templates/table-page-chart.tsx +24 -297
- package/templates/table-page-heatmap-status.tsx +157 -91
- package/templates/table-page-shoe-store-heatmap.tsx +19 -288
- package/templates/table-page.tsx +24 -6
- package/templates/table.tsx +24 -7
- package/templates/tech-report.template.mjs +6 -0
- package/templates/tech-report.tsx +1486 -0
- package/templates/theme-showcase.template.mjs +1 -1
- package/templates/theme-showcase.tsx +313 -249
- package/theme.css +76 -14
- package/tokens.css +425 -30
package/templates/login-sso.tsx
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
+
// Ctr > C[p=4 mw=400] > V[g=4] > ((V[g=1 a=center] > Hd"Welcome back to the night"[level=1] + Tx"Whisper your details to enter the night"[t=body]) + TI"Work email"[t=email] + Lk"Having trouble signing in?" + B.primary"Continue" + D"Or continue with" + B.secondary"Continue with SSO" + (V[a=center] > Tx"New to the castle?"[t=supporting] + Lk"Request access") + (V[g=2 a=center] > Av + Hd"Sign in with Google Workspace"[level=1] + Tx"You will be redirected back after signing in."[t=body]) + (C[p=0] > S[p=4 muted] > (H[g=2 a=center] > Ic + (V[g=0] > Tx"Google Workspace"[t=label] + Tx"vlad@castle.ro"[t=supporting]))) + (V[g=3] > B.primary"Continue with Google Workspace" + B.ghost"Use a different email") + (V[g=1 a=center] > Hd"Welcome back to the night"[level=1] + Tx"vlad@castle.ro"[t=body]) + (V[g=4] > (V[g=1] > TI"Password"[t=password] + Lk"Forgot password?") + B.primary"Enter the night" + B.ghost"Use a different email") + (V[a=center] > Tx"By clicking continue, you agree to our Terms of service and Privacy policy"[t=supporting]))
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
// Step-conditional h1: the email and SSO-confirm steps each render their own
|
|
6
|
+
// h1 (email entry vs provider confirm); the password fallback reuses the
|
|
7
|
+
// family heading, so exactly one h1 shows per step.
|
|
8
|
+
import {useState, useTransition, type CSSProperties} from 'react';
|
|
9
|
+
import {demoLogin} from 'astryx-dracula/shared/login-demo';
|
|
10
|
+
import {
|
|
11
|
+
AUTH_HEADING,
|
|
12
|
+
AUTH_PRIMARY_CTA,
|
|
13
|
+
AUTH_SIGNUP_PROMPT,
|
|
14
|
+
AUTH_SSO_DIVIDER,
|
|
15
|
+
AUTH_FORGOT_PASSWORD,
|
|
16
|
+
AUTH_ERROR_MESSAGE,
|
|
17
|
+
AUTH_EMAIL_PLACEHOLDER,
|
|
18
|
+
AUTH_TERMS_PREFIX,
|
|
19
|
+
AUTH_TERMS_SERVICE,
|
|
20
|
+
AUTH_TERMS_PRIVACY,
|
|
21
|
+
} from 'astryx-dracula/shared/auth-copy';
|
|
4
22
|
import {VStack, HStack} from '@astryxdesign/core/Layout';
|
|
5
23
|
import {Center} from '@astryxdesign/core/Center';
|
|
6
24
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
@@ -16,6 +34,7 @@ import {Avatar} from '@astryxdesign/core/Avatar';
|
|
|
16
34
|
// ============= ICONS (verified lucide-react exports) =============
|
|
17
35
|
// ShieldCheck ← ShieldCheckIcon.
|
|
18
36
|
import {ShieldCheck} from 'lucide-react';
|
|
37
|
+
import {inputAutoComplete} from 'astryx-dracula/shared/auth-chrome-config';
|
|
19
38
|
|
|
20
39
|
// ---------------------------------------------------------------------------
|
|
21
40
|
// Styles
|
|
@@ -59,16 +78,21 @@ export default function LoginSso() {
|
|
|
59
78
|
const [step, setStep] = useState<Step>('email');
|
|
60
79
|
const [email, setEmail] = useState('');
|
|
61
80
|
const [password, setPassword] = useState('');
|
|
62
|
-
const [
|
|
63
|
-
const [isLoading,
|
|
81
|
+
const [error, setError] = useState<string | null>(null);
|
|
82
|
+
const [isLoading, startTransition] = useTransition();
|
|
64
83
|
|
|
65
84
|
const provider = getProvider(email);
|
|
66
85
|
const emailValid = isValidEmail(email);
|
|
67
86
|
|
|
68
87
|
const handleContinue = () => {
|
|
69
88
|
if (!emailValid) {
|
|
89
|
+
// The Continue buttons stay enabled and explain themselves. Disabling
|
|
90
|
+
// them greyed the buttons out with no stated reason, so a malformed
|
|
91
|
+
// address produced silence rather than guidance.
|
|
92
|
+
setError('Enter a valid email address to continue.');
|
|
70
93
|
return;
|
|
71
94
|
}
|
|
95
|
+
setError(null);
|
|
72
96
|
if (provider) {
|
|
73
97
|
setStep('sso-confirm');
|
|
74
98
|
} else {
|
|
@@ -78,88 +102,82 @@ export default function LoginSso() {
|
|
|
78
102
|
|
|
79
103
|
const handleBack = () => {
|
|
80
104
|
setStep('email');
|
|
81
|
-
|
|
82
|
-
setIsLoading(false);
|
|
105
|
+
setError(null);
|
|
83
106
|
};
|
|
84
107
|
|
|
85
108
|
const handleSignIn = () => {
|
|
86
109
|
if (!password) {
|
|
87
|
-
|
|
110
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
88
111
|
return;
|
|
89
112
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}, 2000);
|
|
113
|
+
setError(null);
|
|
114
|
+
startTransition(async () => {
|
|
115
|
+
await demoLogin();
|
|
116
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
117
|
+
});
|
|
96
118
|
};
|
|
97
119
|
|
|
98
120
|
return (
|
|
99
121
|
<Center axis="both" style={pageStyle}>
|
|
100
|
-
<Card padding={
|
|
122
|
+
<Card padding={4} width="100%" maxWidth={400}>
|
|
101
123
|
<VStack gap={4} hAlign="stretch">
|
|
102
|
-
{/* ── Step 1: Email entry ──
|
|
124
|
+
{/* ── Step 1: Email entry, route by domain ──
|
|
125
|
+
The password is collected on the fallback step below, never
|
|
126
|
+
here: a recognised domain goes straight to SSO. */}
|
|
103
127
|
{step === 'email' && (
|
|
104
128
|
<>
|
|
105
129
|
<VStack gap={1} hAlign="center">
|
|
106
|
-
<Heading level={
|
|
130
|
+
<Heading level={1} type="display-2">
|
|
131
|
+
{AUTH_HEADING}
|
|
132
|
+
</Heading>
|
|
107
133
|
<Text type="body" color="secondary">
|
|
108
134
|
Whisper your details to enter the night
|
|
109
135
|
</Text>
|
|
110
136
|
</VStack>
|
|
111
137
|
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
placeholder="Whisper your password"
|
|
132
|
-
value={password}
|
|
133
|
-
onChange={setPassword}
|
|
134
|
-
size="lg"
|
|
135
|
-
/>
|
|
136
|
-
</VStack>
|
|
138
|
+
<TextInput
|
|
139
|
+
label="Work email"
|
|
140
|
+
isLabelHidden
|
|
141
|
+
type="email"
|
|
142
|
+
{...inputAutoComplete('email')}
|
|
143
|
+
placeholder={AUTH_EMAIL_PLACEHOLDER}
|
|
144
|
+
value={email}
|
|
145
|
+
onChange={(v: string) => {
|
|
146
|
+
setEmail(v);
|
|
147
|
+
setError(null);
|
|
148
|
+
}}
|
|
149
|
+
size="lg"
|
|
150
|
+
onKeyDown={(e: React.KeyboardEvent) => {
|
|
151
|
+
if (e.key === 'Enter') {
|
|
152
|
+
handleContinue();
|
|
153
|
+
}
|
|
154
|
+
}}
|
|
155
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
156
|
+
/>
|
|
137
157
|
|
|
138
|
-
<Link href="#/templates/login-sso"
|
|
158
|
+
<Link href="#/templates/login-sso">
|
|
139
159
|
Having trouble signing in?
|
|
140
160
|
</Link>
|
|
141
161
|
|
|
142
162
|
<Button
|
|
143
|
-
label="
|
|
163
|
+
label="Continue"
|
|
144
164
|
variant="primary"
|
|
145
165
|
size="lg"
|
|
146
166
|
onClick={handleContinue}
|
|
147
|
-
isDisabled={!emailValid}
|
|
148
167
|
/>
|
|
149
168
|
|
|
150
|
-
<Divider label=
|
|
169
|
+
<Divider label={AUTH_SSO_DIVIDER} />
|
|
151
170
|
|
|
152
171
|
<Button
|
|
153
172
|
label="Continue with SSO"
|
|
154
173
|
variant="secondary"
|
|
155
174
|
size="lg"
|
|
156
175
|
onClick={handleContinue}
|
|
157
|
-
isDisabled={!emailValid}
|
|
158
176
|
/>
|
|
159
177
|
|
|
160
178
|
<VStack hAlign="center">
|
|
161
179
|
<Text type="supporting" color="secondary">
|
|
162
|
-
|
|
180
|
+
{AUTH_SIGNUP_PROMPT}{' '}
|
|
163
181
|
<Link href="#/templates/login-sso" type="supporting">
|
|
164
182
|
Request access
|
|
165
183
|
</Link>
|
|
@@ -173,14 +191,23 @@ export default function LoginSso() {
|
|
|
173
191
|
<>
|
|
174
192
|
<VStack gap={2} hAlign="center">
|
|
175
193
|
<Avatar name={provider.name} size={48} />
|
|
176
|
-
<Heading level={
|
|
194
|
+
<Heading level={1} type="display-2">
|
|
195
|
+
Sign in with {provider.name}
|
|
196
|
+
</Heading>
|
|
177
197
|
<Text type="body" color="secondary">
|
|
178
198
|
You will be redirected back after signing in.
|
|
179
199
|
</Text>
|
|
180
200
|
</VStack>
|
|
181
201
|
|
|
182
|
-
|
|
183
|
-
|
|
202
|
+
{/* The gutter lives on the Card, not the Section. A Section
|
|
203
|
+
escapes its container's --container-padding-* (Section.tsx:
|
|
204
|
+
56-71), but with Card padding={0} that published var is
|
|
205
|
+
--spacing-0, so the escape cancelled nothing and the muted
|
|
206
|
+
fill painted flush to the card border. 3 + 1 keeps the text
|
|
207
|
+
inset at the 16px it already had while giving the fill a
|
|
208
|
+
real one. */}
|
|
209
|
+
<Card padding={3}>
|
|
210
|
+
<Section variant="muted" padding={1}>
|
|
184
211
|
<HStack gap={2} vAlign="center">
|
|
185
212
|
<Icon icon={ShieldCheck} color="secondary" />
|
|
186
213
|
<VStack gap={0}>
|
|
@@ -199,7 +226,11 @@ export default function LoginSso() {
|
|
|
199
226
|
variant="primary"
|
|
200
227
|
size="lg"
|
|
201
228
|
isLoading={isLoading}
|
|
202
|
-
onClick={() =>
|
|
229
|
+
onClick={() => {
|
|
230
|
+
startTransition(async () => {
|
|
231
|
+
await demoLogin();
|
|
232
|
+
});
|
|
233
|
+
}}
|
|
203
234
|
/>
|
|
204
235
|
<Button
|
|
205
236
|
label="Use a different email"
|
|
@@ -215,7 +246,9 @@ export default function LoginSso() {
|
|
|
215
246
|
{step === 'password-fallback' && (
|
|
216
247
|
<>
|
|
217
248
|
<VStack gap={1} hAlign="center">
|
|
218
|
-
<Heading level={
|
|
249
|
+
<Heading level={1} type="display-2">
|
|
250
|
+
{AUTH_HEADING}
|
|
251
|
+
</Heading>
|
|
219
252
|
<Text type="body" color="secondary">
|
|
220
253
|
{email}
|
|
221
254
|
</Text>
|
|
@@ -226,36 +259,27 @@ export default function LoginSso() {
|
|
|
226
259
|
<TextInput
|
|
227
260
|
label="Password"
|
|
228
261
|
type="password"
|
|
262
|
+
{...inputAutoComplete('current-password')}
|
|
229
263
|
value={password}
|
|
230
264
|
size="lg"
|
|
231
265
|
onChange={(v: string) => {
|
|
232
266
|
setPassword(v);
|
|
233
|
-
|
|
267
|
+
setError(null);
|
|
234
268
|
}}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
? {
|
|
238
|
-
type: 'error',
|
|
239
|
-
message: 'Wrong incantation. Try again.',
|
|
240
|
-
}
|
|
241
|
-
: undefined
|
|
242
|
-
}
|
|
269
|
+
onEnter={handleSignIn}
|
|
270
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
243
271
|
/>
|
|
244
|
-
{
|
|
272
|
+
{error && (
|
|
245
273
|
<VStack hAlign="end">
|
|
246
|
-
<Link
|
|
247
|
-
|
|
248
|
-
size="sm"
|
|
249
|
-
color="secondary"
|
|
250
|
-
type="supporting">
|
|
251
|
-
Forgot password?
|
|
274
|
+
<Link href="#/templates/login-sso">
|
|
275
|
+
{AUTH_FORGOT_PASSWORD}
|
|
252
276
|
</Link>
|
|
253
277
|
</VStack>
|
|
254
278
|
)}
|
|
255
279
|
</VStack>
|
|
256
280
|
|
|
257
281
|
<Button
|
|
258
|
-
label=
|
|
282
|
+
label={AUTH_PRIMARY_CTA}
|
|
259
283
|
variant="primary"
|
|
260
284
|
size="lg"
|
|
261
285
|
isLoading={isLoading}
|
|
@@ -270,6 +294,21 @@ export default function LoginSso() {
|
|
|
270
294
|
</VStack>
|
|
271
295
|
</>
|
|
272
296
|
)}
|
|
297
|
+
|
|
298
|
+
{/* Terms — family line, shown on every step */}
|
|
299
|
+
<VStack hAlign="center" width="100%">
|
|
300
|
+
<Text type="supporting" color="secondary" justify="center">
|
|
301
|
+
{AUTH_TERMS_PREFIX}{' '}
|
|
302
|
+
<Link href="#/templates/login-sso" type="supporting">
|
|
303
|
+
{AUTH_TERMS_SERVICE}
|
|
304
|
+
</Link>{' '}
|
|
305
|
+
and{' '}
|
|
306
|
+
<Link href="#/templates/login-sso" type="supporting">
|
|
307
|
+
{AUTH_TERMS_PRIVACY}
|
|
308
|
+
</Link>
|
|
309
|
+
.
|
|
310
|
+
</Text>
|
|
311
|
+
</VStack>
|
|
273
312
|
</VStack>
|
|
274
313
|
</Card>
|
|
275
314
|
</Center>
|
package/templates/login.tsx
CHANGED
|
@@ -1,97 +1,153 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
+
// Ctr > V[g=4 a=center] > (V[g=2 a=center] > Ic + Tx"Castle Dracula"[t=body]) + (C[p=4] > V[g=4] > (V[g=1 a=center] > Hd"Welcome back to the night"[level=1] + Tx"Sign in to your crypt"[t=body]) + TI"Email"[t=email] + (V[g=1] > TI"Password"[t=password] + Lk"Forgot password?") + B.primary"Enter the night" + (V[a=center] > Tx"New to the castle?"[t=supporting] + Lk"Sign up")) + (V[a=center] > Tx"By clicking continue, you agree to our Terms of service and Privacy policy"[t=supporting])
|
|
2
4
|
|
|
3
|
-
import {useState,
|
|
4
|
-
import {
|
|
5
|
+
import {useState, useTransition} from 'react';
|
|
6
|
+
import {demoLogin} from 'astryx-dracula/shared/login-demo';
|
|
7
|
+
import {
|
|
8
|
+
AUTH_HEADING,
|
|
9
|
+
AUTH_SUBTITLE,
|
|
10
|
+
AUTH_PRIMARY_CTA,
|
|
11
|
+
AUTH_SIGNUP_PROMPT,
|
|
12
|
+
AUTH_SIGNUP_LINK,
|
|
13
|
+
AUTH_FORGOT_PASSWORD,
|
|
14
|
+
AUTH_ERROR_MESSAGE,
|
|
15
|
+
AUTH_EMAIL_PLACEHOLDER,
|
|
16
|
+
AUTH_PASSWORD_PLACEHOLDER,
|
|
17
|
+
AUTH_TERMS_PREFIX,
|
|
18
|
+
AUTH_TERMS_SERVICE,
|
|
19
|
+
AUTH_TERMS_PRIVACY,
|
|
20
|
+
} from 'astryx-dracula/shared/auth-copy';
|
|
5
21
|
import {VStack} from '@astryxdesign/core/Layout';
|
|
6
22
|
import {Center} from '@astryxdesign/core/Center';
|
|
7
23
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
8
24
|
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
9
25
|
import {Button} from '@astryxdesign/core/Button';
|
|
10
26
|
import {Card} from '@astryxdesign/core/Card';
|
|
11
|
-
import {
|
|
12
|
-
import {Banner} from '@astryxdesign/core/Banner';
|
|
27
|
+
import {Link} from '@astryxdesign/core/Link';
|
|
13
28
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
minHeight: '100%',
|
|
17
|
-
backgroundColor: 'var(--color-background-body)',
|
|
18
|
-
padding: 'var(--spacing-6)',
|
|
19
|
-
};
|
|
20
|
-
// Cap the column at 400px but let it shrink to fit narrow screens (Stack
|
|
21
|
-
// has no maxWidth prop, so it's set here).
|
|
22
|
-
const contentStyle: CSSProperties = {
|
|
23
|
-
width: '100%',
|
|
24
|
-
maxWidth: 400,
|
|
25
|
-
};
|
|
29
|
+
import {authPageStyle as pageStyle, authContentStyle as contentStyle, inputAutoComplete} from 'astryx-dracula/shared/auth-chrome-config';
|
|
30
|
+
import {LoginBrand} from 'astryx-dracula/shared/auth-chrome';
|
|
26
31
|
|
|
27
32
|
export default function LoginPage() {
|
|
28
33
|
const [email, setEmail] = useState('');
|
|
29
34
|
const [password, setPassword] = useState('');
|
|
30
|
-
const [isLoading,
|
|
31
|
-
const [error, setError] = useState(
|
|
35
|
+
const [isLoading, startTransition] = useTransition();
|
|
36
|
+
const [error, setError] = useState<string | null>(null);
|
|
32
37
|
|
|
33
38
|
const handleSignIn = () => {
|
|
34
|
-
setError(
|
|
39
|
+
setError(null);
|
|
35
40
|
if (!email || !password) {
|
|
36
|
-
setError(
|
|
41
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
startTransition(async () => {
|
|
45
|
+
await demoLogin();
|
|
46
|
+
});
|
|
41
47
|
};
|
|
42
48
|
|
|
43
49
|
return (
|
|
44
50
|
<Center axis="both" style={pageStyle}>
|
|
45
51
|
<VStack gap={4} hAlign="center" style={contentStyle}>
|
|
46
|
-
|
|
47
|
-
<VStack gap={2} hAlign="center">
|
|
48
|
-
<Icon icon={Moon} size="lg" color="accent" />
|
|
49
|
-
<Text type="body" weight="bold" size="lg">
|
|
50
|
-
Castle Dracula
|
|
51
|
-
</Text>
|
|
52
|
-
</VStack>
|
|
52
|
+
<LoginBrand />
|
|
53
53
|
|
|
54
54
|
{/* Card */}
|
|
55
|
-
<Card padding={
|
|
55
|
+
<Card padding={4} width="100%">
|
|
56
56
|
<VStack gap={4} hAlign="stretch">
|
|
57
57
|
<VStack gap={1} hAlign="center">
|
|
58
|
-
<Heading level={
|
|
59
|
-
|
|
58
|
+
<Heading level={1} type="display-2" justify="center">
|
|
59
|
+
{AUTH_HEADING}
|
|
60
60
|
</Heading>
|
|
61
61
|
<Text type="body" color="secondary">
|
|
62
|
-
|
|
62
|
+
{AUTH_SUBTITLE}
|
|
63
63
|
</Text>
|
|
64
64
|
</VStack>
|
|
65
65
|
|
|
66
|
-
{
|
|
66
|
+
{/* The field group, as its three siblings carry it
|
|
67
|
+
(login-card.tsx:76, login-split.tsx:160, login-sso.tsx:192).
|
|
68
|
+
Without it these two inputs sat 16px apart -- the outer
|
|
69
|
+
VStack's gap={4} -- where all three twins render 8px, so the
|
|
70
|
+
gap between the two fields was twice the gap between the
|
|
71
|
+
fields and the sign-up link below. */}
|
|
72
|
+
<VStack gap={2}>
|
|
73
|
+
<TextInput
|
|
74
|
+
label="Email"
|
|
75
|
+
isLabelHidden
|
|
76
|
+
value={email}
|
|
77
|
+
onChange={v => {
|
|
78
|
+
setEmail(v);
|
|
79
|
+
setError(null);
|
|
80
|
+
}}
|
|
81
|
+
placeholder={AUTH_EMAIL_PLACEHOLDER}
|
|
82
|
+
type="email"
|
|
83
|
+
{...inputAutoComplete('email')}
|
|
84
|
+
size="lg"
|
|
85
|
+
onEnter={handleSignIn}
|
|
86
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
87
|
+
/>
|
|
67
88
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
<VStack gap={1}>
|
|
90
|
+
<TextInput
|
|
91
|
+
label="Password"
|
|
92
|
+
isLabelHidden
|
|
93
|
+
value={password}
|
|
94
|
+
onChange={v => {
|
|
95
|
+
setPassword(v);
|
|
96
|
+
setError(null);
|
|
97
|
+
}}
|
|
98
|
+
placeholder={AUTH_PASSWORD_PLACEHOLDER}
|
|
99
|
+
type="password"
|
|
100
|
+
{...inputAutoComplete('current-password')}
|
|
101
|
+
size="lg"
|
|
102
|
+
onEnter={handleSignIn}
|
|
103
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
104
|
+
/>
|
|
105
|
+
{error && (
|
|
106
|
+
<VStack hAlign="end">
|
|
107
|
+
<Link
|
|
108
|
+
href="#/templates/login"
|
|
109
|
+
color="secondary">
|
|
110
|
+
{AUTH_FORGOT_PASSWORD}
|
|
111
|
+
</Link>
|
|
112
|
+
</VStack>
|
|
113
|
+
)}
|
|
114
|
+
</VStack>
|
|
115
|
+
</VStack>
|
|
85
116
|
|
|
86
117
|
<Button
|
|
87
|
-
label=
|
|
118
|
+
label={AUTH_PRIMARY_CTA}
|
|
88
119
|
variant="primary"
|
|
89
120
|
size="lg"
|
|
90
121
|
isLoading={isLoading}
|
|
91
122
|
onClick={handleSignIn}
|
|
92
123
|
/>
|
|
124
|
+
|
|
125
|
+
{/* Sign up link */}
|
|
126
|
+
<VStack hAlign="center">
|
|
127
|
+
<Text type="supporting" color="secondary">
|
|
128
|
+
{AUTH_SIGNUP_PROMPT}{' '}
|
|
129
|
+
<Link href="#/templates/login" type="supporting">
|
|
130
|
+
{AUTH_SIGNUP_LINK}
|
|
131
|
+
</Link>
|
|
132
|
+
</Text>
|
|
133
|
+
</VStack>
|
|
93
134
|
</VStack>
|
|
94
135
|
</Card>
|
|
136
|
+
|
|
137
|
+
{/* Terms */}
|
|
138
|
+
<VStack hAlign="center" width="100%">
|
|
139
|
+
<Text type="supporting" color="secondary" justify="center">
|
|
140
|
+
{AUTH_TERMS_PREFIX}{' '}
|
|
141
|
+
<Link href="#/templates/login" type="supporting">
|
|
142
|
+
{AUTH_TERMS_SERVICE}
|
|
143
|
+
</Link>{' '}
|
|
144
|
+
and{' '}
|
|
145
|
+
<Link href="#/templates/login" type="supporting">
|
|
146
|
+
{AUTH_TERMS_PRIVACY}
|
|
147
|
+
</Link>
|
|
148
|
+
.
|
|
149
|
+
</Text>
|
|
150
|
+
</VStack>
|
|
95
151
|
</VStack>
|
|
96
152
|
</Center>
|
|
97
153
|
);
|