astryx-dracula 0.2.1 → 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 +17 -12
- 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 +66 -33
- package/templates/ai-chat.tsx +36 -19
- package/templates/blank.tsx +4 -3
- package/templates/centered-hero.tsx +14 -125
- package/templates/classic-gallery.tsx +20 -66
- package/templates/contact-form.tsx +59 -39
- package/templates/dashboard-portfolio.tsx +143 -226
- package/templates/dashboard.tsx +122 -122
- package/templates/detail-page.tsx +95 -91
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +83 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +29 -19
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +52 -74
- package/templates/editor.tsx +112 -84
- package/templates/file-explorer.tsx +131 -84
- package/templates/form-two-column.tsx +61 -125
- package/templates/gallery-hero.tsx +15 -171
- package/templates/ide.tsx +22 -20
- package/templates/incident-console.tsx +104 -70
- package/templates/kanban-board.tsx +99 -42
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +37 -42
- package/templates/login-card.tsx +58 -87
- package/templates/login-split.tsx +88 -126
- package/templates/login-sso.tsx +105 -64
- package/templates/login.tsx +107 -53
- package/templates/messaging-shell.tsx +171 -144
- package/templates/mixed-gallery.tsx +29 -130
- package/templates/payment-form.tsx +368 -290
- package/templates/product-detail.tsx +39 -58
- package/templates/product-gallery.tsx +30 -41
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +135 -282
- package/templates/settings-sidebar.tsx +178 -268
- package/templates/settings.tsx +41 -14
- package/templates/shell-nav.tsx +31 -24
- package/templates/shell-side-nav.tsx +29 -20
- package/templates/shell-top-nav.tsx +80 -44
- package/templates/side-gallery.tsx +38 -94
- package/templates/table-grouped.tsx +62 -34
- package/templates/table-page-chart.tsx +23 -302
- package/templates/table-page-heatmap-status.tsx +156 -92
- package/templates/table-page-shoe-store-heatmap.tsx +18 -293
- package/templates/table-page.tsx +23 -7
- package/templates/table.tsx +23 -8
- 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 +299 -237
- package/theme.css +76 -14
- package/tokens.css +425 -30
package/templates/login-sso.tsx
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// Ctr > C[
|
|
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]))
|
|
4
4
|
|
|
5
|
-
|
|
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';
|
|
6
22
|
import {VStack, HStack} from '@astryxdesign/core/Layout';
|
|
7
23
|
import {Center} from '@astryxdesign/core/Center';
|
|
8
24
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
@@ -18,6 +34,7 @@ import {Avatar} from '@astryxdesign/core/Avatar';
|
|
|
18
34
|
// ============= ICONS (verified lucide-react exports) =============
|
|
19
35
|
// ShieldCheck ← ShieldCheckIcon.
|
|
20
36
|
import {ShieldCheck} from 'lucide-react';
|
|
37
|
+
import {inputAutoComplete} from 'astryx-dracula/shared/auth-chrome-config';
|
|
21
38
|
|
|
22
39
|
// ---------------------------------------------------------------------------
|
|
23
40
|
// Styles
|
|
@@ -61,16 +78,21 @@ export default function LoginSso() {
|
|
|
61
78
|
const [step, setStep] = useState<Step>('email');
|
|
62
79
|
const [email, setEmail] = useState('');
|
|
63
80
|
const [password, setPassword] = useState('');
|
|
64
|
-
const [
|
|
65
|
-
const [isLoading,
|
|
81
|
+
const [error, setError] = useState<string | null>(null);
|
|
82
|
+
const [isLoading, startTransition] = useTransition();
|
|
66
83
|
|
|
67
84
|
const provider = getProvider(email);
|
|
68
85
|
const emailValid = isValidEmail(email);
|
|
69
86
|
|
|
70
87
|
const handleContinue = () => {
|
|
71
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.');
|
|
72
93
|
return;
|
|
73
94
|
}
|
|
95
|
+
setError(null);
|
|
74
96
|
if (provider) {
|
|
75
97
|
setStep('sso-confirm');
|
|
76
98
|
} else {
|
|
@@ -80,89 +102,83 @@ export default function LoginSso() {
|
|
|
80
102
|
|
|
81
103
|
const handleBack = () => {
|
|
82
104
|
setStep('email');
|
|
83
|
-
|
|
84
|
-
setIsLoading(false);
|
|
105
|
+
setError(null);
|
|
85
106
|
};
|
|
86
107
|
|
|
87
108
|
const handleSignIn = () => {
|
|
88
109
|
if (!password) {
|
|
89
|
-
|
|
110
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
90
111
|
return;
|
|
91
112
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}, 2000);
|
|
113
|
+
setError(null);
|
|
114
|
+
startTransition(async () => {
|
|
115
|
+
await demoLogin();
|
|
116
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
117
|
+
});
|
|
98
118
|
};
|
|
99
119
|
|
|
100
120
|
return (
|
|
101
121
|
<Center axis="both" style={pageStyle}>
|
|
102
|
-
<Card padding={
|
|
122
|
+
<Card padding={4} width="100%" maxWidth={400}>
|
|
103
123
|
<VStack gap={4} hAlign="stretch">
|
|
104
|
-
{/* ── 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. */}
|
|
105
127
|
{step === 'email' && (
|
|
106
128
|
<>
|
|
107
129
|
<VStack gap={1} hAlign="center">
|
|
108
|
-
<Heading level={
|
|
130
|
+
<Heading level={1} type="display-2">
|
|
131
|
+
{AUTH_HEADING}
|
|
132
|
+
</Heading>
|
|
109
133
|
<Text type="body" color="secondary">
|
|
110
134
|
Whisper your details to enter the night
|
|
111
135
|
</Text>
|
|
112
136
|
</VStack>
|
|
113
137
|
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
placeholder="Whisper your password"
|
|
134
|
-
value={password}
|
|
135
|
-
onChange={setPassword}
|
|
136
|
-
size="lg"
|
|
137
|
-
/>
|
|
138
|
-
</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
|
+
/>
|
|
139
157
|
|
|
140
158
|
<Link href="#/templates/login-sso">
|
|
141
159
|
Having trouble signing in?
|
|
142
160
|
</Link>
|
|
143
161
|
|
|
144
162
|
<Button
|
|
145
|
-
label="
|
|
163
|
+
label="Continue"
|
|
146
164
|
variant="primary"
|
|
147
165
|
size="lg"
|
|
148
166
|
onClick={handleContinue}
|
|
149
|
-
isDisabled={!emailValid}
|
|
150
167
|
/>
|
|
151
168
|
|
|
152
|
-
<Divider label=
|
|
169
|
+
<Divider label={AUTH_SSO_DIVIDER} />
|
|
153
170
|
|
|
154
171
|
<Button
|
|
155
172
|
label="Continue with SSO"
|
|
156
173
|
variant="secondary"
|
|
157
174
|
size="lg"
|
|
158
175
|
onClick={handleContinue}
|
|
159
|
-
isDisabled={!emailValid}
|
|
160
176
|
/>
|
|
161
177
|
|
|
162
178
|
<VStack hAlign="center">
|
|
163
179
|
<Text type="supporting" color="secondary">
|
|
164
|
-
|
|
165
|
-
<Link href="#/templates/login-sso">
|
|
180
|
+
{AUTH_SIGNUP_PROMPT}{' '}
|
|
181
|
+
<Link href="#/templates/login-sso" type="supporting">
|
|
166
182
|
Request access
|
|
167
183
|
</Link>
|
|
168
184
|
</Text>
|
|
@@ -175,14 +191,23 @@ export default function LoginSso() {
|
|
|
175
191
|
<>
|
|
176
192
|
<VStack gap={2} hAlign="center">
|
|
177
193
|
<Avatar name={provider.name} size={48} />
|
|
178
|
-
<Heading level={
|
|
194
|
+
<Heading level={1} type="display-2">
|
|
195
|
+
Sign in with {provider.name}
|
|
196
|
+
</Heading>
|
|
179
197
|
<Text type="body" color="secondary">
|
|
180
198
|
You will be redirected back after signing in.
|
|
181
199
|
</Text>
|
|
182
200
|
</VStack>
|
|
183
201
|
|
|
184
|
-
|
|
185
|
-
|
|
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}>
|
|
186
211
|
<HStack gap={2} vAlign="center">
|
|
187
212
|
<Icon icon={ShieldCheck} color="secondary" />
|
|
188
213
|
<VStack gap={0}>
|
|
@@ -201,7 +226,11 @@ export default function LoginSso() {
|
|
|
201
226
|
variant="primary"
|
|
202
227
|
size="lg"
|
|
203
228
|
isLoading={isLoading}
|
|
204
|
-
onClick={() =>
|
|
229
|
+
onClick={() => {
|
|
230
|
+
startTransition(async () => {
|
|
231
|
+
await demoLogin();
|
|
232
|
+
});
|
|
233
|
+
}}
|
|
205
234
|
/>
|
|
206
235
|
<Button
|
|
207
236
|
label="Use a different email"
|
|
@@ -217,7 +246,9 @@ export default function LoginSso() {
|
|
|
217
246
|
{step === 'password-fallback' && (
|
|
218
247
|
<>
|
|
219
248
|
<VStack gap={1} hAlign="center">
|
|
220
|
-
<Heading level={
|
|
249
|
+
<Heading level={1} type="display-2">
|
|
250
|
+
{AUTH_HEADING}
|
|
251
|
+
</Heading>
|
|
221
252
|
<Text type="body" color="secondary">
|
|
222
253
|
{email}
|
|
223
254
|
</Text>
|
|
@@ -228,32 +259,27 @@ export default function LoginSso() {
|
|
|
228
259
|
<TextInput
|
|
229
260
|
label="Password"
|
|
230
261
|
type="password"
|
|
262
|
+
{...inputAutoComplete('current-password')}
|
|
231
263
|
value={password}
|
|
232
264
|
size="lg"
|
|
233
265
|
onChange={(v: string) => {
|
|
234
266
|
setPassword(v);
|
|
235
|
-
|
|
267
|
+
setError(null);
|
|
236
268
|
}}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
? {
|
|
240
|
-
type: 'error',
|
|
241
|
-
message: 'Wrong incantation. Try again.',
|
|
242
|
-
}
|
|
243
|
-
: undefined
|
|
244
|
-
}
|
|
269
|
+
onEnter={handleSignIn}
|
|
270
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
245
271
|
/>
|
|
246
|
-
{
|
|
272
|
+
{error && (
|
|
247
273
|
<VStack hAlign="end">
|
|
248
274
|
<Link href="#/templates/login-sso">
|
|
249
|
-
|
|
275
|
+
{AUTH_FORGOT_PASSWORD}
|
|
250
276
|
</Link>
|
|
251
277
|
</VStack>
|
|
252
278
|
)}
|
|
253
279
|
</VStack>
|
|
254
280
|
|
|
255
281
|
<Button
|
|
256
|
-
label=
|
|
282
|
+
label={AUTH_PRIMARY_CTA}
|
|
257
283
|
variant="primary"
|
|
258
284
|
size="lg"
|
|
259
285
|
isLoading={isLoading}
|
|
@@ -268,6 +294,21 @@ export default function LoginSso() {
|
|
|
268
294
|
</VStack>
|
|
269
295
|
</>
|
|
270
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>
|
|
271
312
|
</VStack>
|
|
272
313
|
</Card>
|
|
273
314
|
</Center>
|
package/templates/login.tsx
CHANGED
|
@@ -1,99 +1,153 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
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=
|
|
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])
|
|
4
4
|
|
|
5
|
-
import {useState,
|
|
6
|
-
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';
|
|
7
21
|
import {VStack} from '@astryxdesign/core/Layout';
|
|
8
22
|
import {Center} from '@astryxdesign/core/Center';
|
|
9
23
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
10
24
|
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
11
25
|
import {Button} from '@astryxdesign/core/Button';
|
|
12
26
|
import {Card} from '@astryxdesign/core/Card';
|
|
13
|
-
import {
|
|
14
|
-
import {Banner} from '@astryxdesign/core/Banner';
|
|
27
|
+
import {Link} from '@astryxdesign/core/Link';
|
|
15
28
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
minHeight: '100%',
|
|
19
|
-
backgroundColor: 'var(--color-background-body)',
|
|
20
|
-
padding: 'var(--spacing-6)',
|
|
21
|
-
};
|
|
22
|
-
// Cap the column at 400px but let it shrink to fit narrow screens (Stack
|
|
23
|
-
// has no maxWidth prop, so it's set here).
|
|
24
|
-
const contentStyle: CSSProperties = {
|
|
25
|
-
width: '100%',
|
|
26
|
-
maxWidth: 400,
|
|
27
|
-
};
|
|
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';
|
|
28
31
|
|
|
29
32
|
export default function LoginPage() {
|
|
30
33
|
const [email, setEmail] = useState('');
|
|
31
34
|
const [password, setPassword] = useState('');
|
|
32
|
-
const [isLoading,
|
|
33
|
-
const [error, setError] = useState(
|
|
35
|
+
const [isLoading, startTransition] = useTransition();
|
|
36
|
+
const [error, setError] = useState<string | null>(null);
|
|
34
37
|
|
|
35
38
|
const handleSignIn = () => {
|
|
36
|
-
setError(
|
|
39
|
+
setError(null);
|
|
37
40
|
if (!email || !password) {
|
|
38
|
-
setError(
|
|
41
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
startTransition(async () => {
|
|
45
|
+
await demoLogin();
|
|
46
|
+
});
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
return (
|
|
46
50
|
<Center axis="both" style={pageStyle}>
|
|
47
51
|
<VStack gap={4} hAlign="center" style={contentStyle}>
|
|
48
|
-
|
|
49
|
-
<VStack gap={2} hAlign="center">
|
|
50
|
-
<Icon icon={Moon} size="lg" color="accent" />
|
|
51
|
-
<Text type="body" weight="bold" size="lg">
|
|
52
|
-
Castle Dracula
|
|
53
|
-
</Text>
|
|
54
|
-
</VStack>
|
|
52
|
+
<LoginBrand />
|
|
55
53
|
|
|
56
54
|
{/* Card */}
|
|
57
|
-
<Card padding={
|
|
55
|
+
<Card padding={4} width="100%">
|
|
58
56
|
<VStack gap={4} hAlign="stretch">
|
|
59
57
|
<VStack gap={1} hAlign="center">
|
|
60
|
-
<Heading level={
|
|
61
|
-
|
|
58
|
+
<Heading level={1} type="display-2" justify="center">
|
|
59
|
+
{AUTH_HEADING}
|
|
62
60
|
</Heading>
|
|
63
61
|
<Text type="body" color="secondary">
|
|
64
|
-
|
|
62
|
+
{AUTH_SUBTITLE}
|
|
65
63
|
</Text>
|
|
66
64
|
</VStack>
|
|
67
65
|
|
|
68
|
-
{
|
|
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
|
+
/>
|
|
69
88
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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>
|
|
87
116
|
|
|
88
117
|
<Button
|
|
89
|
-
label=
|
|
118
|
+
label={AUTH_PRIMARY_CTA}
|
|
90
119
|
variant="primary"
|
|
91
120
|
size="lg"
|
|
92
121
|
isLoading={isLoading}
|
|
93
122
|
onClick={handleSignIn}
|
|
94
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>
|
|
95
134
|
</VStack>
|
|
96
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>
|
|
97
151
|
</VStack>
|
|
98
152
|
</Center>
|
|
99
153
|
);
|