astryx-dracula 0.1.3 → 0.2.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 (94) hide show
  1. package/BRAND.md +1 -1
  2. package/README.md +2 -0
  3. package/USAGE.md +13 -0
  4. package/astryx-dracula.js +4 -4
  5. package/astryx.integration.mjs +3 -0
  6. package/package.json +4 -2
  7. package/templates/ai-chat-landing.template.mjs +6 -0
  8. package/templates/ai-chat-landing.tsx +476 -0
  9. package/templates/ai-chat.template.mjs +6 -0
  10. package/templates/ai-chat.tsx +762 -0
  11. package/templates/blank.template.mjs +6 -0
  12. package/templates/blank.tsx +16 -0
  13. package/templates/centered-hero.template.mjs +6 -0
  14. package/templates/centered-hero.tsx +188 -0
  15. package/templates/classic-gallery.template.mjs +6 -0
  16. package/templates/classic-gallery.tsx +181 -0
  17. package/templates/contact-form.template.mjs +6 -0
  18. package/templates/contact-form.tsx +292 -0
  19. package/templates/dashboard-portfolio.template.mjs +6 -0
  20. package/templates/dashboard-portfolio.tsx +800 -0
  21. package/templates/dashboard.template.mjs +6 -0
  22. package/templates/dashboard.tsx +774 -0
  23. package/templates/detail-page.template.mjs +6 -0
  24. package/templates/detail-page.tsx +695 -0
  25. package/templates/documentation-design.template.mjs +6 -0
  26. package/templates/documentation-design.tsx +751 -0
  27. package/templates/documentation-technical.template.mjs +6 -0
  28. package/templates/documentation-technical.tsx +292 -0
  29. package/templates/documentation.template.mjs +6 -0
  30. package/templates/documentation.tsx +347 -0
  31. package/templates/editor.template.mjs +6 -0
  32. package/templates/editor.tsx +959 -0
  33. package/templates/file-explorer.template.mjs +6 -0
  34. package/templates/file-explorer.tsx +577 -0
  35. package/templates/form-two-column.template.mjs +6 -0
  36. package/templates/form-two-column.tsx +314 -0
  37. package/templates/gallery-hero.template.mjs +6 -0
  38. package/templates/gallery-hero.tsx +287 -0
  39. package/templates/ide.template.mjs +6 -0
  40. package/templates/ide.tsx +533 -0
  41. package/templates/incident-console.template.mjs +6 -0
  42. package/templates/incident-console.tsx +584 -0
  43. package/templates/kanban-board.template.mjs +6 -0
  44. package/templates/kanban-board.tsx +746 -0
  45. package/templates/library.template.mjs +6 -0
  46. package/templates/library.tsx +489 -0
  47. package/templates/login-card.template.mjs +6 -0
  48. package/templates/login-card.tsx +200 -0
  49. package/templates/login-split.template.mjs +6 -0
  50. package/templates/login-split.tsx +318 -0
  51. package/templates/login-sso.template.mjs +6 -0
  52. package/templates/login-sso.tsx +277 -0
  53. package/templates/login.template.mjs +6 -0
  54. package/templates/login.tsx +98 -0
  55. package/templates/messaging-shell.template.mjs +6 -0
  56. package/templates/messaging-shell.tsx +675 -0
  57. package/templates/mixed-gallery.template.mjs +6 -0
  58. package/templates/mixed-gallery.tsx +190 -0
  59. package/templates/payment-form.template.mjs +6 -0
  60. package/templates/payment-form.tsx +990 -0
  61. package/templates/product-detail.template.mjs +6 -0
  62. package/templates/product-detail.tsx +366 -0
  63. package/templates/product-gallery.template.mjs +6 -0
  64. package/templates/product-gallery.tsx +173 -0
  65. package/templates/settings-dialog.template.mjs +6 -0
  66. package/templates/settings-dialog.tsx +920 -0
  67. package/templates/settings-sidebar.template.mjs +6 -0
  68. package/templates/settings-sidebar.tsx +952 -0
  69. package/templates/settings.template.mjs +6 -0
  70. package/templates/settings.tsx +235 -0
  71. package/templates/shell-nav.template.mjs +6 -0
  72. package/templates/shell-nav.tsx +306 -0
  73. package/templates/shell-side-nav.template.mjs +6 -0
  74. package/templates/shell-side-nav.tsx +238 -0
  75. package/templates/shell-top-nav.template.mjs +6 -0
  76. package/templates/shell-top-nav.tsx +243 -0
  77. package/templates/side-gallery.template.mjs +6 -0
  78. package/templates/side-gallery.tsx +179 -0
  79. package/templates/table-grouped.template.mjs +6 -0
  80. package/templates/table-grouped.tsx +1174 -0
  81. package/templates/table-page-chart.template.mjs +6 -0
  82. package/templates/table-page-chart.tsx +693 -0
  83. package/templates/table-page-heatmap-status.template.mjs +6 -0
  84. package/templates/table-page-heatmap-status.tsx +547 -0
  85. package/templates/table-page-shoe-store-heatmap.template.mjs +6 -0
  86. package/templates/table-page-shoe-store-heatmap.tsx +1038 -0
  87. package/templates/table-page.template.mjs +6 -0
  88. package/templates/table-page.tsx +445 -0
  89. package/templates/table.template.mjs +6 -0
  90. package/templates/table.tsx +84 -0
  91. package/templates/theme-showcase.template.mjs +6 -0
  92. package/templates/theme-showcase.tsx +1282 -0
  93. package/theme.css +4 -4
  94. package/tokens.css +4 -4
@@ -0,0 +1,952 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {useState, type CSSProperties} from 'react';
4
+ import {useMediaQuery} from '@astryxdesign/core/hooks';
5
+ import {
6
+ VStack,
7
+ HStack,
8
+ StackItem,
9
+ Layout,
10
+ LayoutContent,
11
+ LayoutPanel,
12
+ } from '@astryxdesign/core/Layout';
13
+ import {List, ListItem} from '@astryxdesign/core/List';
14
+ import {Toolbar} from '@astryxdesign/core/Toolbar';
15
+ import {Text, Heading} from '@astryxdesign/core/Text';
16
+ import {Link} from '@astryxdesign/core/Link';
17
+ import {Button} from '@astryxdesign/core/Button';
18
+ import {Selector} from '@astryxdesign/core/Selector';
19
+ import {TextInput} from '@astryxdesign/core/TextInput';
20
+ import {Card} from '@astryxdesign/core/Card';
21
+ import {Switch} from '@astryxdesign/core/Switch';
22
+ import {Divider} from '@astryxdesign/core/Divider';
23
+ import {TabList, Tab} from '@astryxdesign/core/TabList';
24
+ import {Badge} from '@astryxdesign/core/Badge';
25
+ import {Icon} from '@astryxdesign/core/Icon';
26
+ import {Center} from '@astryxdesign/core/Center';
27
+ import {
28
+ User,
29
+ Lock,
30
+ ShieldCheck,
31
+ Bell,
32
+ FileText,
33
+ CreditCard,
34
+ Globe,
35
+ Briefcase,
36
+ Wrench,
37
+ Monitor,
38
+ SquarePen,
39
+ Share2,
40
+ ArrowLeft,
41
+ ChevronRight,
42
+ } from 'lucide-react';
43
+
44
+ // Anchor the page to the viewport height so the sidebar + content fill the
45
+ // screen. Layout height="fill" is min-height:100% which collapses when the
46
+ // host container is content-sized; Layout has no viewport-height prop.
47
+ const fillViewport: CSSProperties = {
48
+ minHeight: '100dvh',
49
+ };
50
+ const iconBox: CSSProperties = {
51
+ borderRadius: 'var(--radius-container)',
52
+ backgroundColor: 'var(--color-background-surface)',
53
+ flexShrink: 0,
54
+ };
55
+ const rowPadding: CSSProperties = {
56
+ paddingBlock: 'var(--spacing-4)',
57
+ };
58
+ const sideNavPadding: CSSProperties = {
59
+ paddingBlock: 'var(--spacing-4)',
60
+ paddingInline: 'var(--spacing-3)',
61
+ };
62
+ const sideNavHeading: CSSProperties = {
63
+ marginInline: 'var(--spacing-4)',
64
+ };
65
+
66
+ const NAV_ITEMS = [
67
+ {label: 'Personal information', icon: User},
68
+ {label: 'Login & security', icon: Lock},
69
+ {label: 'Privacy', icon: ShieldCheck},
70
+ {label: 'Notifications', icon: Bell},
71
+ {label: 'Taxes', icon: FileText},
72
+ {label: 'Payments', icon: CreditCard},
73
+ {label: 'Languages & currency', icon: Globe},
74
+ {label: 'Travel for work', icon: Briefcase},
75
+ ];
76
+
77
+ // Section title shown beside the mobile back button (matches each section's
78
+ // in-content heading, which is hidden on mobile to avoid a duplicate).
79
+ const SECTION_TITLES: Record<string, string> = {
80
+ 'Personal information': 'Personal info',
81
+ 'Login & security': 'Login & security',
82
+ Privacy: 'Privacy',
83
+ Notifications: 'Notifications',
84
+ Taxes: 'Taxes',
85
+ Payments: 'Payments',
86
+ 'Languages & currency': 'Languages & currency',
87
+ 'Travel for work': 'Travel for work',
88
+ 'Professional hosting tools': 'Hosting tools',
89
+ };
90
+
91
+ interface InfoRow {
92
+ label: string;
93
+ value: string;
94
+ action: string;
95
+ }
96
+
97
+ const LOGIN_ROWS: InfoRow[] = [
98
+ {label: 'Password', value: 'Not created', action: 'Create'},
99
+ ];
100
+
101
+ const SOCIAL_ROWS: InfoRow[] = [
102
+ {label: 'Google', value: 'Connected', action: 'Disconnect'},
103
+ ];
104
+
105
+ const TAX_ROWS: InfoRow[] = [
106
+ {label: 'Taxpayer information', value: 'Not provided', action: 'Add'},
107
+ {label: 'Tax documents', value: 'No documents yet', action: 'View'},
108
+ ];
109
+
110
+ const PAYOUT_ROWS: InfoRow[] = [
111
+ {label: 'Payout method', value: 'Not set up', action: 'Add'},
112
+ {label: 'Past payments', value: 'No payments yet', action: 'View'},
113
+ ];
114
+
115
+ const DEVICE_ROWS: {
116
+ label: string;
117
+ badge?: string;
118
+ location: string;
119
+ action?: string;
120
+ }[] = [
121
+ {
122
+ label: 'OS X 10.15.7 · Chrome',
123
+ badge: 'CURRENT SESSION',
124
+ location: 'Brașov, Transylvania · March 30, 2026 at 19:31',
125
+ },
126
+ {label: 'Session', location: 'August 9, 2023 at 04:19', action: 'Log out'},
127
+ {
128
+ label: 'OS X 10.15.7 · unknown',
129
+ location: 'Whitby, England · April 14, 2023 at 17:47',
130
+ action: 'Log out',
131
+ },
132
+ ];
133
+
134
+ function InfoRowItem({label, value, action}: InfoRow) {
135
+ return (
136
+ <>
137
+ <HStack hAlign="between" vAlign="start" style={rowPadding}>
138
+ <VStack gap={0}>
139
+ <Text type="body" weight="semibold" display="block">
140
+ {label}
141
+ </Text>
142
+ <Text type="supporting" color="secondary" display="block">
143
+ {value}
144
+ </Text>
145
+ </VStack>
146
+ {action && <Link href="#/templates/settings-sidebar">{action}</Link>}
147
+ </HStack>
148
+ <Divider />
149
+ </>
150
+ );
151
+ }
152
+
153
+ interface ExpandableRowProps {
154
+ label: string;
155
+ value: string;
156
+ children: React.ReactNode;
157
+ isExpanded: boolean;
158
+ onEdit: () => void;
159
+ onCancel: () => void;
160
+ onSave: () => void;
161
+ }
162
+
163
+ function ExpandableRow({
164
+ label,
165
+ value,
166
+ children,
167
+ isExpanded,
168
+ onEdit,
169
+ onCancel,
170
+ onSave,
171
+ }: ExpandableRowProps) {
172
+ return (
173
+ <>
174
+ {isExpanded ? (
175
+ <VStack gap={4} style={rowPadding}>
176
+ <Text type="body" weight="semibold" display="block">
177
+ {label}
178
+ </Text>
179
+ {children}
180
+ <HStack gap={2}>
181
+ <Button label="Save" variant="primary" onClick={onSave} />
182
+ <Button label="Cancel" variant="ghost" onClick={onCancel} />
183
+ </HStack>
184
+ </VStack>
185
+ ) : (
186
+ <HStack hAlign="between" vAlign="start" style={rowPadding}>
187
+ <VStack gap={0}>
188
+ <Text type="body" weight="semibold" display="block">
189
+ {label}
190
+ </Text>
191
+ <Text type="supporting" color="secondary" display="block">
192
+ {value}
193
+ </Text>
194
+ </VStack>
195
+ <Link
196
+ href="#/templates/settings-sidebar"
197
+ onClick={(e: React.MouseEvent) => {
198
+ e.preventDefault();
199
+ onEdit();
200
+ }}>
201
+ Edit
202
+ </Link>
203
+ </HStack>
204
+ )}
205
+ <Divider />
206
+ </>
207
+ );
208
+ }
209
+
210
+ const LANGUAGES = [
211
+ {label: 'English (Canada)', value: 'en-CA'},
212
+ {label: 'English (US)', value: 'en-US'},
213
+ {label: 'French', value: 'fr'},
214
+ {label: 'Spanish', value: 'es'},
215
+ {label: 'German', value: 'de'},
216
+ {label: 'Japanese', value: 'ja'},
217
+ ];
218
+
219
+ const CURRENCIES = [
220
+ {label: 'Canadian dollar (CAD)', value: 'CAD'},
221
+ {label: 'US dollar (USD)', value: 'USD'},
222
+ {label: 'Euro (EUR)', value: 'EUR'},
223
+ {label: 'British pound (GBP)', value: 'GBP'},
224
+ {label: 'Japanese yen (JPY)', value: 'JPY'},
225
+ ];
226
+
227
+ const TIMEZONES = [
228
+ {label: '(GMT-05:00) Eastern Time (US & Canada)', value: 'ET'},
229
+ {label: '(GMT-06:00) Central Time (US & Canada)', value: 'CT'},
230
+ {label: '(GMT-07:00) Mountain Time (US & Canada)', value: 'MT'},
231
+ {label: '(GMT-08:00) Pacific Time (US & Canada)', value: 'PT'},
232
+ {label: '(GMT+00:00) UTC', value: 'UTC'},
233
+ {label: '(GMT+01:00) London', value: 'GMT+1'},
234
+ ];
235
+
236
+ export default function SettingsSidebar() {
237
+ const isNarrow = useMediaQuery('(max-width: 768px)');
238
+ // Mobile is a master→detail drill-down: 'nav' shows the menu, 'detail' shows
239
+ // the selected section with a back button. Desktop shows both side-by-side.
240
+ const [mobileView, setMobileView] = useState<'nav' | 'detail'>('nav');
241
+ const [activeNav, setActiveNav] = useState('Personal information');
242
+ const [activeTab, setActiveTab] = useState('login');
243
+ const [readReceipts, setReadReceipts] = useState(true);
244
+ const [searchEngines, setSearchEngines] = useState(true);
245
+ const [showCity, setShowCity] = useState(true);
246
+ const [showTripType, setShowTripType] = useState(true);
247
+ const [showStayLength, setShowStayLength] = useState(true);
248
+ const [showServices, setShowServices] = useState(true);
249
+ const [aiFeatures, setAiFeatures] = useState(true);
250
+ const [emailNotif, setEmailNotif] = useState(true);
251
+ const [pushNotif, setPushNotif] = useState(true);
252
+ const [workTrips, setWorkTrips] = useState(false);
253
+
254
+ const [expandedRow, setExpandedRow] = useState<string | null>(null);
255
+ const [language, setLanguage] = useState('en-CA');
256
+ const [currency, setCurrency] = useState('CAD');
257
+ const [timezone, setTimezone] = useState('ET');
258
+
259
+ const [legalName, setLegalName] = useState('Vlad Dracul');
260
+ const [preferredName, setPreferredName] = useState('');
261
+ const [email, setEmail] = useState('v***d@castle-dracula.ro');
262
+ const [phone, setPhone] = useState('+1 ***-***-0123');
263
+ const [address, setAddress] = useState('');
264
+ const [mailingAddress, setMailingAddress] = useState('');
265
+ const [emergencyContact, setEmergencyContact] = useState('Provided');
266
+
267
+ // Selecting a nav item also drills into the detail view on mobile.
268
+ const selectNav = (label: string) => {
269
+ setActiveNav(label);
270
+ setMobileView('detail');
271
+ };
272
+
273
+ const navList = (
274
+ <VStack gap={4} style={sideNavPadding}>
275
+ <Heading level={2} style={sideNavHeading}>
276
+ Account settings
277
+ </Heading>
278
+ <List density="spacious">
279
+ {NAV_ITEMS.map(item => (
280
+ <ListItem
281
+ key={item.label}
282
+ label={item.label}
283
+ startContent={<Icon icon={item.icon} />}
284
+ endContent={
285
+ isNarrow ? (
286
+ <Icon icon={ChevronRight} size="sm" color="secondary" />
287
+ ) : undefined
288
+ }
289
+ isSelected={!isNarrow && activeNav === item.label}
290
+ onClick={() => selectNav(item.label)}
291
+ />
292
+ ))}
293
+ </List>
294
+ <Divider />
295
+ <List density="spacious">
296
+ <ListItem
297
+ label="Professional hosting tools"
298
+ startContent={<Icon icon={Wrench} />}
299
+ onClick={() => {}}
300
+ />
301
+ </List>
302
+ </VStack>
303
+ );
304
+
305
+ // Mobile, nav view: show only the menu (full width, no sidebar slot).
306
+ if (isNarrow && mobileView === 'nav') {
307
+ return (
308
+ <Layout
309
+ height="fill"
310
+ style={fillViewport}
311
+ content={<LayoutContent padding={2}>{navList}</LayoutContent>}
312
+ />
313
+ );
314
+ }
315
+
316
+ return (
317
+ <Layout
318
+ height="fill"
319
+ contentWidth={1200}
320
+ style={fillViewport}
321
+ start={
322
+ isNarrow ? undefined : (
323
+ <LayoutPanel hasDivider padding={0}>
324
+ {navList}
325
+ </LayoutPanel>
326
+ )
327
+ }
328
+ content={
329
+ <LayoutContent padding={4}>
330
+ <VStack gap={0}>
331
+ {/* Mobile detail view: a back button sits beside the section title
332
+ (the per-section headings below are hidden on mobile). Toolbar's
333
+ start slot edge-compensates the ghost button so its icon aligns
334
+ flush with the content edge. */}
335
+ {isNarrow && (
336
+ <Toolbar
337
+ label={`Back to Account settings — ${SECTION_TITLES[activeNav]}`}
338
+ gap={2}
339
+ startContent={
340
+ <>
341
+ <Button
342
+ label="Back to Account settings"
343
+ variant="ghost"
344
+ size="sm"
345
+ isIconOnly
346
+ icon={<Icon icon={ArrowLeft} size="sm" />}
347
+ onClick={() => setMobileView('nav')}
348
+ />
349
+ <Heading level={2}>{SECTION_TITLES[activeNav]}</Heading>
350
+ </>
351
+ }
352
+ />
353
+ )}
354
+ {activeNav === 'Login & security' && (
355
+ <VStack gap={6}>
356
+ {!isNarrow && <Heading level={2}>Login &amp; security</Heading>}
357
+
358
+ <TabList value={activeTab} onChange={setActiveTab} hasDivider>
359
+ <Tab value="login" label="Login" />
360
+ <Tab value="shared" label="Shared access" />
361
+ </TabList>
362
+
363
+ {activeTab === 'login' && (
364
+ <VStack gap={8}>
365
+ <VStack gap={0}>
366
+ <Heading level={3}>Login</Heading>
367
+ <Divider />
368
+ {LOGIN_ROWS.map(row => (
369
+ <InfoRowItem key={row.label} {...row} />
370
+ ))}
371
+ </VStack>
372
+
373
+ <VStack gap={0}>
374
+ <Heading level={3}>Social accounts</Heading>
375
+ <Divider />
376
+ {SOCIAL_ROWS.map(row => (
377
+ <InfoRowItem key={row.label} {...row} />
378
+ ))}
379
+ </VStack>
380
+
381
+ <VStack gap={0}>
382
+ <Heading level={3}>Device history</Heading>
383
+ <Divider />
384
+ {DEVICE_ROWS.map((device, i) => (
385
+ <HStack
386
+ key={i}
387
+ gap={3}
388
+ vAlign="start"
389
+ style={rowPadding}>
390
+ <Icon icon={Monitor} />
391
+ <StackItem size="fill">
392
+ <VStack gap={0}>
393
+ <HStack gap={2} vAlign="center">
394
+ <Text type="body" weight="semibold">
395
+ {device.label}
396
+ </Text>
397
+ {device.badge && <Badge label={device.badge} />}
398
+ </HStack>
399
+ <Text
400
+ type="supporting"
401
+ color="secondary"
402
+ display="block"
403
+ hasTabularNumbers>
404
+ {device.location}
405
+ </Text>
406
+ </VStack>
407
+ </StackItem>
408
+ {device.action && (
409
+ <Link href="#/templates/settings-sidebar">{device.action}</Link>
410
+ )}
411
+ </HStack>
412
+ ))}
413
+ <Divider />
414
+ </VStack>
415
+
416
+ <VStack gap={0}>
417
+ <Heading level={3}>Account</Heading>
418
+ <Divider />
419
+ <HStack
420
+ hAlign="between"
421
+ vAlign="start"
422
+ style={rowPadding}>
423
+ <VStack gap={0}>
424
+ <Text type="body" weight="semibold" display="block">
425
+ Deactivate your account
426
+ </Text>
427
+ <Text
428
+ type="supporting"
429
+ color="secondary"
430
+ display="block">
431
+ This action cannot be undone
432
+ </Text>
433
+ </VStack>
434
+ <Link href="#/templates/settings-sidebar">Deactivate</Link>
435
+ </HStack>
436
+ <Divider />
437
+ </VStack>
438
+ </VStack>
439
+ )}
440
+
441
+ {activeTab === 'shared' && (
442
+ <VStack gap={8}>
443
+ <VStack gap={2}>
444
+ <Heading level={3}>Shared access</Heading>
445
+ <Divider />
446
+ <Text type="body" color="secondary">
447
+ Review each request carefully before approving access.
448
+ We&apos;ll email your employee or co-worker a 4-digit
449
+ code that lets them log into your account with their
450
+ trusted device.
451
+ </Text>
452
+ </VStack>
453
+
454
+ <Card variant="muted">
455
+ <HStack gap={4} vAlign="start">
456
+ <Center width={48} height={48} style={iconBox}>
457
+ <Icon icon={Lock} />
458
+ </Center>
459
+ <VStack gap={1}>
460
+ <Text type="body" weight="bold">
461
+ Adding devices from people you trust
462
+ </Text>
463
+ <Text type="body" color="secondary">
464
+ When you approve a request, you grant someone full
465
+ access to your account. They&apos;ll be able to
466
+ change reservations and send messages on your
467
+ behalf.
468
+ </Text>
469
+ </VStack>
470
+ </HStack>
471
+ </Card>
472
+ </VStack>
473
+ )}
474
+ </VStack>
475
+ )}
476
+
477
+ {activeNav === 'Languages & currency' && (
478
+ <VStack gap={6}>
479
+ {!isNarrow && (
480
+ <Heading level={2}>Languages &amp; currency</Heading>
481
+ )}
482
+ <VStack gap={0}>
483
+ <ExpandableRow
484
+ label="Preferred language"
485
+ value={
486
+ LANGUAGES.find(l => l.value === language)?.label ??
487
+ language
488
+ }
489
+ isExpanded={expandedRow === 'language'}
490
+ onEdit={() => setExpandedRow('language')}
491
+ onCancel={() => setExpandedRow(null)}
492
+ onSave={() => setExpandedRow(null)}>
493
+ <Selector
494
+ label="Language"
495
+ isLabelHidden
496
+ size="lg"
497
+ value={language}
498
+ onChange={setLanguage}
499
+ options={LANGUAGES}
500
+ />
501
+ </ExpandableRow>
502
+ <ExpandableRow
503
+ label="Preferred currency"
504
+ value={
505
+ CURRENCIES.find(c => c.value === currency)?.label ??
506
+ currency
507
+ }
508
+ isExpanded={expandedRow === 'currency'}
509
+ onEdit={() => setExpandedRow('currency')}
510
+ onCancel={() => setExpandedRow(null)}
511
+ onSave={() => setExpandedRow(null)}>
512
+ <Selector
513
+ label="Currency"
514
+ isLabelHidden
515
+ size="lg"
516
+ value={currency}
517
+ onChange={setCurrency}
518
+ options={CURRENCIES}
519
+ />
520
+ </ExpandableRow>
521
+ <ExpandableRow
522
+ label="Time zone"
523
+ value={
524
+ TIMEZONES.find(t => t.value === timezone)?.label ??
525
+ timezone
526
+ }
527
+ isExpanded={expandedRow === 'timezone'}
528
+ onEdit={() => setExpandedRow('timezone')}
529
+ onCancel={() => setExpandedRow(null)}
530
+ onSave={() => setExpandedRow(null)}>
531
+ <Selector
532
+ label="Time zone"
533
+ isLabelHidden
534
+ size="lg"
535
+ value={timezone}
536
+ onChange={setTimezone}
537
+ options={TIMEZONES}
538
+ />
539
+ </ExpandableRow>
540
+ </VStack>
541
+ </VStack>
542
+ )}
543
+
544
+ {activeNav === 'Personal information' && (
545
+ <VStack gap={6}>
546
+ {!isNarrow && <Heading level={2}>Personal info</Heading>}
547
+ <VStack gap={0}>
548
+ <ExpandableRow
549
+ label="Legal name"
550
+ value={legalName}
551
+ isExpanded={expandedRow === 'legalName'}
552
+ onEdit={() => setExpandedRow('legalName')}
553
+ onCancel={() => setExpandedRow(null)}
554
+ onSave={() => setExpandedRow(null)}>
555
+ <TextInput
556
+ label="Legal name"
557
+ isLabelHidden
558
+ value={legalName}
559
+ onChange={setLegalName}
560
+ />
561
+ </ExpandableRow>
562
+ <ExpandableRow
563
+ label="Preferred first name"
564
+ value={preferredName || 'Not provided'}
565
+ isExpanded={expandedRow === 'preferredName'}
566
+ onEdit={() => setExpandedRow('preferredName')}
567
+ onCancel={() => setExpandedRow(null)}
568
+ onSave={() => setExpandedRow(null)}>
569
+ <TextInput
570
+ label="Preferred first name"
571
+ isLabelHidden
572
+ value={preferredName}
573
+ onChange={setPreferredName}
574
+ />
575
+ </ExpandableRow>
576
+ <ExpandableRow
577
+ label="Email address"
578
+ value={email}
579
+ isExpanded={expandedRow === 'email'}
580
+ onEdit={() => setExpandedRow('email')}
581
+ onCancel={() => setExpandedRow(null)}
582
+ onSave={() => setExpandedRow(null)}>
583
+ <TextInput
584
+ label="Email address"
585
+ isLabelHidden
586
+ value={email}
587
+ onChange={setEmail}
588
+ />
589
+ </ExpandableRow>
590
+ <ExpandableRow
591
+ label="Phone number"
592
+ value={phone}
593
+ isExpanded={expandedRow === 'phone'}
594
+ onEdit={() => setExpandedRow('phone')}
595
+ onCancel={() => setExpandedRow(null)}
596
+ onSave={() => setExpandedRow(null)}>
597
+ <TextInput
598
+ label="Phone number"
599
+ isLabelHidden
600
+ value={phone}
601
+ onChange={setPhone}
602
+ />
603
+ </ExpandableRow>
604
+ <InfoRowItem
605
+ label="Identity verification"
606
+ value="Verified"
607
+ action=""
608
+ />
609
+ <ExpandableRow
610
+ label="Residential address"
611
+ value={address || 'Not provided'}
612
+ isExpanded={expandedRow === 'address'}
613
+ onEdit={() => setExpandedRow('address')}
614
+ onCancel={() => setExpandedRow(null)}
615
+ onSave={() => setExpandedRow(null)}>
616
+ <TextInput
617
+ label="Residential address"
618
+ isLabelHidden
619
+ value={address}
620
+ onChange={setAddress}
621
+ />
622
+ </ExpandableRow>
623
+ <ExpandableRow
624
+ label="Mailing address"
625
+ value={mailingAddress || 'Not provided'}
626
+ isExpanded={expandedRow === 'mailingAddress'}
627
+ onEdit={() => setExpandedRow('mailingAddress')}
628
+ onCancel={() => setExpandedRow(null)}
629
+ onSave={() => setExpandedRow(null)}>
630
+ <TextInput
631
+ label="Mailing address"
632
+ isLabelHidden
633
+ value={mailingAddress}
634
+ onChange={setMailingAddress}
635
+ />
636
+ </ExpandableRow>
637
+ <ExpandableRow
638
+ label="Emergency contact"
639
+ value={emergencyContact}
640
+ isExpanded={expandedRow === 'emergencyContact'}
641
+ onEdit={() => setExpandedRow('emergencyContact')}
642
+ onCancel={() => setExpandedRow(null)}
643
+ onSave={() => setExpandedRow(null)}>
644
+ <TextInput
645
+ label="Emergency contact"
646
+ isLabelHidden
647
+ value={emergencyContact}
648
+ onChange={setEmergencyContact}
649
+ />
650
+ </ExpandableRow>
651
+ </VStack>
652
+
653
+ <Card padding={4}>
654
+ <VStack gap={4}>
655
+ <HStack gap={3} vAlign="start">
656
+ <Center width={48} height={48} style={iconBox}>
657
+ <Icon icon={Lock} />
658
+ </Center>
659
+ <VStack gap={0}>
660
+ <Text type="body" weight="semibold" display="block">
661
+ Why isn&apos;t my info shown here?
662
+ </Text>
663
+ <Text
664
+ type="supporting"
665
+ color="secondary"
666
+ display="block">
667
+ We&apos;re hiding some account details to protect your
668
+ identity.
669
+ </Text>
670
+ </VStack>
671
+ </HStack>
672
+ <Divider />
673
+ <HStack gap={3} vAlign="start">
674
+ <Center width={48} height={48} style={iconBox}>
675
+ <Icon icon={SquarePen} />
676
+ </Center>
677
+ <VStack gap={0}>
678
+ <Text type="body" weight="semibold" display="block">
679
+ Which details can be edited?
680
+ </Text>
681
+ <Text
682
+ type="supporting"
683
+ color="secondary"
684
+ display="block">
685
+ Contact info and personal details can be edited. If
686
+ this info was used to verify your identity,
687
+ you&apos;ll need to get verified again the next time
688
+ you book—or to continue hosting.
689
+ </Text>
690
+ </VStack>
691
+ </HStack>
692
+ <Divider />
693
+ <HStack gap={3} vAlign="start">
694
+ <Center width={48} height={48} style={iconBox}>
695
+ <Icon icon={Share2} />
696
+ </Center>
697
+ <VStack gap={0}>
698
+ <Text type="body" weight="semibold" display="block">
699
+ What info is shared with others?
700
+ </Text>
701
+ <Text
702
+ type="supporting"
703
+ color="secondary"
704
+ display="block">
705
+ We only release contact information after a
706
+ reservation is confirmed.
707
+ </Text>
708
+ </VStack>
709
+ </HStack>
710
+ </VStack>
711
+ </Card>
712
+ </VStack>
713
+ )}
714
+
715
+ {activeNav === 'Privacy' && (
716
+ <VStack gap={6}>
717
+ {!isNarrow && <Heading level={2}>Privacy</Heading>}
718
+
719
+ <VStack gap={8}>
720
+ <VStack gap={0}>
721
+ <Heading level={3}>Messages</Heading>
722
+ <VStack style={rowPadding}>
723
+ <Switch
724
+ label="Show people when I've read their messages."
725
+ value={readReceipts}
726
+ onChange={setReadReceipts}
727
+ labelPosition="start"
728
+ labelSpacing="spread"
729
+ />
730
+ </VStack>
731
+ <HStack hAlign="between" vAlign="center" style={rowPadding}>
732
+ <Text type="body" weight="semibold">
733
+ Blocked people
734
+ </Text>
735
+ <Link href="#/templates/settings-sidebar">View</Link>
736
+ </HStack>
737
+ <Divider />
738
+ </VStack>
739
+
740
+ <VStack gap={0}>
741
+ <Heading level={3}>Listings</Heading>
742
+ <VStack style={rowPadding}>
743
+ <Switch
744
+ label="Include my listing(s) in search engines"
745
+ description="Turning this on means search engines, like Google, will display your listing page(s) in search results."
746
+ value={searchEngines}
747
+ onChange={setSearchEngines}
748
+ labelPosition="start"
749
+ labelSpacing="spread"
750
+ />
751
+ </VStack>
752
+ <Divider />
753
+ </VStack>
754
+
755
+ <VStack gap={4}>
756
+ <Heading level={3}>Reviews</Heading>
757
+ <Text type="body" color="secondary">
758
+ Choose what&apos;s shared when you write a review.{' '}
759
+ <Link href="#/templates/settings-sidebar" type="supporting">
760
+ Learn more
761
+ </Link>
762
+ </Text>
763
+ <VStack gap={4}>
764
+ <Switch
765
+ label="Show my home city and country"
766
+ description="Ex: City and country"
767
+ value={showCity}
768
+ onChange={setShowCity}
769
+ labelPosition="start"
770
+ labelSpacing="spread"
771
+ />
772
+ <Switch
773
+ label="Show my trip type"
774
+ description="Ex: Stayed with kids or pets"
775
+ value={showTripType}
776
+ onChange={setShowTripType}
777
+ labelPosition="start"
778
+ labelSpacing="spread"
779
+ />
780
+ <Switch
781
+ label="Show my length of stay"
782
+ description="Ex: A few nights, about a week, etc."
783
+ value={showStayLength}
784
+ onChange={setShowStayLength}
785
+ labelPosition="start"
786
+ labelSpacing="spread"
787
+ />
788
+ <Switch
789
+ label="Show my booked services"
790
+ description="Ex: Gourmet brunch or tasting menu"
791
+ value={showServices}
792
+ onChange={setShowServices}
793
+ labelPosition="start"
794
+ labelSpacing="spread"
795
+ />
796
+ </VStack>
797
+ <Divider />
798
+ </VStack>
799
+
800
+ <VStack gap={4}>
801
+ <Heading level={3}>Data privacy</Heading>
802
+ <Card>
803
+ <HStack hAlign="between" vAlign="center">
804
+ <Text type="body">Request my personal data</Text>
805
+ <Link href="#/templates/settings-sidebar">Request</Link>
806
+ </HStack>
807
+ </Card>
808
+ <Switch
809
+ label="Help improve AI-powered features"
810
+ description="When this is on, we use your data to develop and improve AI models."
811
+ value={aiFeatures}
812
+ onChange={setAiFeatures}
813
+ labelPosition="start"
814
+ labelSpacing="spread"
815
+ />
816
+ <Card>
817
+ <HStack hAlign="between" vAlign="center">
818
+ <Text type="body">Delete my account</Text>
819
+ <Link href="#/templates/settings-sidebar">Delete</Link>
820
+ </HStack>
821
+ </Card>
822
+ <Card variant="muted">
823
+ <HStack gap={4} vAlign="start">
824
+ <Center width={48} height={48} style={iconBox}>
825
+ <Icon icon={ShieldCheck} />
826
+ </Center>
827
+ <VStack gap={1}>
828
+ <Text type="body" weight="bold">
829
+ Committed to privacy
830
+ </Text>
831
+ <Text type="supporting" color="secondary">
832
+ We&apos;re committed to keeping your data protected.
833
+ See details in our{' '}
834
+ <Link href="#/templates/settings-sidebar" type="supporting">
835
+ Privacy Policy
836
+ </Link>
837
+ .
838
+ </Text>
839
+ </VStack>
840
+ </HStack>
841
+ </Card>
842
+ </VStack>
843
+ </VStack>
844
+ </VStack>
845
+ )}
846
+
847
+ {activeNav === 'Notifications' && (
848
+ <VStack gap={6}>
849
+ {!isNarrow && <Heading level={2}>Notifications</Heading>}
850
+ <VStack gap={0}>
851
+ <Heading level={3}>Messages</Heading>
852
+ <VStack style={rowPadding}>
853
+ <Switch
854
+ label="Email notifications"
855
+ description="Listing updates, trip reminders, and account activity."
856
+ value={emailNotif}
857
+ onChange={setEmailNotif}
858
+ labelPosition="start"
859
+ labelSpacing="spread"
860
+ />
861
+ </VStack>
862
+ <Divider />
863
+ <VStack style={rowPadding}>
864
+ <Switch
865
+ label="Push notifications"
866
+ description="Instant alerts for messages and reservation requests."
867
+ value={pushNotif}
868
+ onChange={setPushNotif}
869
+ labelPosition="start"
870
+ labelSpacing="spread"
871
+ />
872
+ </VStack>
873
+ <Divider />
874
+ </VStack>
875
+ </VStack>
876
+ )}
877
+
878
+ {activeNav === 'Taxes' && (
879
+ <VStack gap={6}>
880
+ {!isNarrow && <Heading level={2}>Taxes</Heading>}
881
+ <VStack gap={0}>
882
+ <Heading level={3}>Tax documents</Heading>
883
+ <Divider />
884
+ {TAX_ROWS.map(row => (
885
+ <InfoRowItem key={row.label} {...row} />
886
+ ))}
887
+ </VStack>
888
+ </VStack>
889
+ )}
890
+
891
+ {activeNav === 'Payments' && (
892
+ <VStack gap={6}>
893
+ {!isNarrow && <Heading level={2}>Payments</Heading>}
894
+ <VStack gap={0}>
895
+ <Heading level={3}>Payouts</Heading>
896
+ <Divider />
897
+ {PAYOUT_ROWS.map(row => (
898
+ <InfoRowItem key={row.label} {...row} />
899
+ ))}
900
+ </VStack>
901
+ </VStack>
902
+ )}
903
+
904
+ {activeNav === 'Travel for work' && (
905
+ <VStack gap={6}>
906
+ {!isNarrow && <Heading level={2}>Travel for work</Heading>}
907
+ <VStack gap={0}>
908
+ <Heading level={3}>Work trips</Heading>
909
+ <VStack style={rowPadding}>
910
+ <Switch
911
+ label="Show work-trip options at checkout"
912
+ description="Add a work email to expense trips and unlock business-ready listings."
913
+ value={workTrips}
914
+ onChange={setWorkTrips}
915
+ labelPosition="start"
916
+ labelSpacing="spread"
917
+ />
918
+ </VStack>
919
+ <Divider />
920
+ </VStack>
921
+ </VStack>
922
+ )}
923
+
924
+ {activeNav === 'Professional hosting tools' && (
925
+ <VStack gap={6}>
926
+ {!isNarrow && (
927
+ <Heading level={2}>Professional hosting tools</Heading>
928
+ )}
929
+ <Card variant="muted">
930
+ <HStack gap={4} vAlign="start">
931
+ <Center width={48} height={48} style={iconBox}>
932
+ <Icon icon={Wrench} />
933
+ </Center>
934
+ <VStack gap={1}>
935
+ <Text type="body" weight="bold">
936
+ Tools for professional hosts
937
+ </Text>
938
+ <Text type="supporting" color="secondary">
939
+ Manage multiple listings, route tasks to co-hosts, and
940
+ review consolidated payouts from one place.
941
+ </Text>
942
+ </VStack>
943
+ </HStack>
944
+ </Card>
945
+ </VStack>
946
+ )}
947
+ </VStack>
948
+ </LayoutContent>
949
+ }
950
+ />
951
+ );
952
+ }