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.
Files changed (83) hide show
  1. package/BRAND.md +30 -18
  2. package/README.md +22 -11
  3. package/USAGE.md +29 -10
  4. package/astryx-dracula.js +19 -13
  5. package/astryx-dracula.variants.d.ts +15 -0
  6. package/package.json +59 -17
  7. package/shared/auth-chrome-config.ts +18 -0
  8. package/shared/auth-chrome.tsx +25 -0
  9. package/shared/auth-copy.ts +22 -0
  10. package/shared/chaptered-doc-config.ts +92 -0
  11. package/shared/chaptered-doc.tsx +332 -0
  12. package/shared/chart-hues.ts +27 -0
  13. package/shared/chart-labels.tsx +44 -0
  14. package/shared/chart-legend.tsx +100 -0
  15. package/shared/chart-panel-style.ts +13 -0
  16. package/shared/data-bar.tsx +160 -0
  17. package/shared/gallery-image.ts +20 -0
  18. package/shared/login-demo.ts +7 -0
  19. package/shared/metric-delta.tsx +36 -0
  20. package/shared/order-desk.tsx +273 -0
  21. package/shared/revenue-chart.tsx +174 -0
  22. package/shared/scene-castle.tsx +322 -0
  23. package/shared/scene-frame.tsx +88 -0
  24. package/shared/scene-hues.ts +49 -0
  25. package/shared/scene-tile.tsx +128 -0
  26. package/shared/settings-data.ts +125 -0
  27. package/shared/settings-rows.tsx +179 -0
  28. package/shared/sparkline.tsx +94 -0
  29. package/shared/sso-icons.tsx +30 -0
  30. package/templates/ai-chat-landing.tsx +67 -32
  31. package/templates/ai-chat.tsx +41 -20
  32. package/templates/blank.tsx +5 -2
  33. package/templates/centered-hero.tsx +15 -124
  34. package/templates/classic-gallery.tsx +21 -65
  35. package/templates/contact-form.tsx +62 -40
  36. package/templates/dashboard-portfolio.tsx +146 -227
  37. package/templates/dashboard.tsx +123 -121
  38. package/templates/detail-page.tsx +101 -95
  39. package/templates/documentation-design.template.mjs +1 -1
  40. package/templates/documentation-design.tsx +85 -96
  41. package/templates/documentation-technical.template.mjs +1 -1
  42. package/templates/documentation-technical.tsx +33 -21
  43. package/templates/documentation.template.mjs +1 -1
  44. package/templates/documentation.tsx +54 -74
  45. package/templates/editor.tsx +119 -89
  46. package/templates/file-explorer.tsx +133 -84
  47. package/templates/form-two-column.tsx +62 -124
  48. package/templates/gallery-hero.tsx +17 -171
  49. package/templates/ide.tsx +27 -21
  50. package/templates/incident-console.tsx +105 -69
  51. package/templates/kanban-board.tsx +100 -41
  52. package/templates/library.template.mjs +2 -2
  53. package/templates/library.tsx +38 -41
  54. package/templates/login-card.tsx +59 -86
  55. package/templates/login-split.tsx +88 -128
  56. package/templates/login-sso.tsx +107 -68
  57. package/templates/login.tsx +108 -52
  58. package/templates/messaging-shell.tsx +172 -143
  59. package/templates/mixed-gallery.tsx +31 -130
  60. package/templates/payment-form.tsx +369 -289
  61. package/templates/product-detail.tsx +40 -57
  62. package/templates/product-gallery.tsx +31 -40
  63. package/templates/product-tour.template.mjs +6 -0
  64. package/templates/product-tour.tsx +916 -0
  65. package/templates/settings-dialog.tsx +145 -286
  66. package/templates/settings-sidebar.tsx +179 -267
  67. package/templates/settings.tsx +42 -13
  68. package/templates/shell-nav.tsx +32 -23
  69. package/templates/shell-side-nav.tsx +30 -19
  70. package/templates/shell-top-nav.tsx +81 -43
  71. package/templates/side-gallery.tsx +39 -93
  72. package/templates/table-grouped.tsx +63 -33
  73. package/templates/table-page-chart.tsx +24 -297
  74. package/templates/table-page-heatmap-status.tsx +157 -91
  75. package/templates/table-page-shoe-store-heatmap.tsx +19 -288
  76. package/templates/table-page.tsx +24 -6
  77. package/templates/table.tsx +24 -7
  78. package/templates/tech-report.template.mjs +6 -0
  79. package/templates/tech-report.tsx +1486 -0
  80. package/templates/theme-showcase.template.mjs +1 -1
  81. package/templates/theme-showcase.tsx +313 -249
  82. package/theme.css +76 -14
  83. package/tokens.css +425 -30
@@ -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
- import {useState, type CSSProperties} from 'react';
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 [loginFailed, setLoginFailed] = useState(false);
63
- const [isLoading, setIsLoading] = useState(false);
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
- setLoginFailed(false);
82
- setIsLoading(false);
105
+ setError(null);
83
106
  };
84
107
 
85
108
  const handleSignIn = () => {
86
109
  if (!password) {
87
- setLoginFailed(true);
110
+ setError(AUTH_ERROR_MESSAGE);
88
111
  return;
89
112
  }
90
- setIsLoading(true);
91
- setLoginFailed(false);
92
- setTimeout(() => {
93
- setIsLoading(false);
94
- setLoginFailed(true);
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={8} width="100%" maxWidth={400}>
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={2}>Welcome back to the night</Heading>
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
- <VStack gap={2}>
113
- <TextInput
114
- label="Work email"
115
- isLabelHidden
116
- type="email"
117
- placeholder="you@castle.dracula"
118
- value={email}
119
- onChange={setEmail}
120
- size="lg"
121
- onKeyDown={(e: React.KeyboardEvent) => {
122
- if (e.key === 'Enter') {
123
- handleContinue();
124
- }
125
- }}
126
- />
127
- <TextInput
128
- label="Password"
129
- isLabelHidden
130
- type="password"
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" size="sm" color="secondary" type="supporting">
158
+ <Link href="#/templates/login-sso">
139
159
  Having trouble signing in?
140
160
  </Link>
141
161
 
142
162
  <Button
143
- label="Enter the night"
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="Or sign in with" />
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
- New to the castle?{' '}
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={2}>Sign in with {provider.name}</Heading>
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
- <Card padding={0}>
183
- <Section variant="muted" padding={4}>
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={() => setIsLoading(true)}
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={2}>Welcome back to the night</Heading>
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
- setLoginFailed(false);
267
+ setError(null);
234
268
  }}
235
- status={
236
- loginFailed
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
- {loginFailed && (
272
+ {error && (
245
273
  <VStack hAlign="end">
246
- <Link
247
- href="#/templates/login-sso"
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="Enter the night"
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>
@@ -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, type CSSProperties} from 'react';
4
- import {Moon} from 'lucide-react';
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 {Icon} from '@astryxdesign/core/Icon';
12
- import {Banner} from '@astryxdesign/core/Banner';
27
+ import {Link} from '@astryxdesign/core/Link';
13
28
 
14
- // Standalone auth page paints its own body background (no host shell).
15
- const pageStyle: CSSProperties = {
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, setIsLoading] = useState(false);
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('Whisper both your email and password to enter the night.');
41
+ setError(AUTH_ERROR_MESSAGE);
37
42
  return;
38
43
  }
39
- setIsLoading(true);
40
- setTimeout(() => setIsLoading(false), 2000);
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
- {/* Logo */}
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={8} width="100%">
55
+ <Card padding={4} width="100%">
56
56
  <VStack gap={4} hAlign="stretch">
57
57
  <VStack gap={1} hAlign="center">
58
- <Heading level={2} justify="center">
59
- Welcome back to the night
58
+ <Heading level={1} type="display-2" justify="center">
59
+ {AUTH_HEADING}
60
60
  </Heading>
61
61
  <Text type="body" color="secondary">
62
- Sign in to your crypt
62
+ {AUTH_SUBTITLE}
63
63
  </Text>
64
64
  </VStack>
65
65
 
66
- {error && <Banner status="error" title={error} container="card" />}
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
- <TextInput
69
- label="Email"
70
- value={email}
71
- onChange={setEmail}
72
- placeholder="you@castle.dracula"
73
- type="email"
74
- size="lg"
75
- />
76
-
77
- <TextInput
78
- label="Password"
79
- value={password}
80
- onChange={setPassword}
81
- placeholder="Whisper your password"
82
- type="password"
83
- size="lg"
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="Enter the night"
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
  );