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