astryx-dracula 0.3.0 → 0.4.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/USAGE.md +27 -1
- package/astryx-dracula.d.ts +2 -2
- package/astryx-dracula.js +384 -3
- package/astryx-dracula.variants.d.ts +2 -14
- package/icons.mjs +125 -0
- package/package.json +16 -8
- package/shared/revenue-chart.tsx +72 -9
- package/shared/settings-data.ts +43 -1
- package/shared/settings-rows.tsx +81 -30
- package/shared/sparkline.tsx +9 -1
- package/templates/ai-chat-landing.tsx +17 -9
- package/templates/ai-chat.tsx +2 -2
- package/templates/centered-hero.tsx +2 -2
- package/templates/contact-form.tsx +11 -10
- package/templates/dashboard.tsx +7 -3
- package/templates/detail-page.tsx +11 -4
- package/templates/documentation-design.tsx +43 -29
- package/templates/documentation-technical.tsx +11 -9
- package/templates/file-explorer.tsx +82 -70
- package/templates/form-two-column.tsx +1 -1
- package/templates/ide.tsx +6 -6
- package/templates/incident-console.tsx +27 -4
- package/templates/login-card.tsx +8 -5
- package/templates/login-split.tsx +21 -12
- package/templates/login-sso.tsx +57 -41
- package/templates/login.tsx +23 -20
- package/templates/payment-form.tsx +39 -45
- package/templates/product-detail.tsx +4 -6
- package/templates/product-tour.tsx +2 -2
- package/templates/settings-dialog.tsx +84 -87
- package/templates/settings-sidebar.tsx +92 -80
- package/templates/table-grouped.tsx +126 -29
- package/templates/table.tsx +21 -2
- package/templates/tech-report.tsx +1 -1
- package/theme.css +211 -119
- package/tokens.css +15 -7
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// L > LC > Ctr[h=80vh] > B.primary"Open settings"[opens=#settings] ;; Dlg#settings > L > (LP[w=280 divider p=3] > V[g=4] > Tx"Account settings"[t=label] + (UL > LI"Personal information"*8)) + (LC[p=6] > V[g=6] > DH"Account" + SE"Settings section" + (V[g=0] > TabList + (V[g=0] > Hd"Login"[level=3] +
|
|
3
|
+
// L > LC > Ctr[h=80vh] > B.primary"Open settings"[opens=#settings] ;; Dlg#settings > L > (LP[w=280 divider p=3] > V[g=4] > Tx"Account settings"[t=label] + (UL > LI"Personal information"*8)) + (LC[p=6] > V[g=6] > DH"Account" + SE"Settings section" + (V[g=0] > TabList + (V[g=0] > Hd"Login"[level=3] + (H[j=between a=start] > (V[g=0] > Tx"Password"[weight=semibold] + Tx"Not created"[t=supporting]) + B"Create") + (H[g=3 a=start] > Ic + (V[g=0] > (H[g=2 a=center wrap] > Tx"OS X 10.15.7 Chrome"[weight=semibold] + SD) + Tx"March 30, 2026"[t=supporting])))))
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Settings Dialog — account sections inside one modal. Frame: the trigger page, then a Dialog that sizes to (Frame/responsive/container: see XLE header above.)
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import {useState, type CSSProperties} from 'react';
|
|
10
10
|
import {
|
|
11
11
|
VStack,
|
|
12
12
|
HStack,
|
|
@@ -19,7 +19,6 @@ import {Text, Heading} from '@astryxdesign/core/Text';
|
|
|
19
19
|
import {Button} from '@astryxdesign/core/Button';
|
|
20
20
|
import {Dialog, DialogHeader} from '@astryxdesign/core/Dialog';
|
|
21
21
|
import {List, ListItem} from '@astryxdesign/core/List';
|
|
22
|
-
import {Divider} from '@astryxdesign/core/Divider';
|
|
23
22
|
import {Selector} from '@astryxdesign/core/Selector';
|
|
24
23
|
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
25
24
|
import {Card} from '@astryxdesign/core/Card';
|
|
@@ -31,7 +30,7 @@ import {Icon} from '@astryxdesign/core/Icon';
|
|
|
31
30
|
import {Center} from '@astryxdesign/core/Center';
|
|
32
31
|
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
33
32
|
import {Lock, ShieldCheck, Monitor} from 'lucide-react';
|
|
34
|
-
import {NAV_ITEMS, LOGIN_ROWS, SOCIAL_ROWS, DEVICE_ROWS, INFO_TILES, iconBox,
|
|
33
|
+
import {NAV_ITEMS, LOGIN_ROWS, SOCIAL_ROWS, DEVICE_ROWS, INFO_TILES, iconBox, sideNavHeading} from 'astryx-dracula/shared/settings-data';
|
|
35
34
|
import {InfoRowItem, ExpandableRow} from 'astryx-dracula/shared/settings-rows';
|
|
36
35
|
import type {DeviceRow, InfoTileData} from 'astryx-dracula/shared/settings-data';
|
|
37
36
|
|
|
@@ -89,7 +88,7 @@ function InfoTile({icon, title, body}: InfoTileData) {
|
|
|
89
88
|
<Center width={48} height={48} style={iconBox}>
|
|
90
89
|
<Icon icon={icon} />
|
|
91
90
|
</Center>
|
|
92
|
-
<VStack gap={
|
|
91
|
+
<VStack gap={1}>
|
|
93
92
|
<Text type="body" weight="semibold" display="block">
|
|
94
93
|
{title}
|
|
95
94
|
</Text>
|
|
@@ -104,43 +103,37 @@ function InfoTile({icon, title, body}: InfoTileData) {
|
|
|
104
103
|
|
|
105
104
|
function DeviceRowItem({label, isCurrent, location, action}: DeviceRow) {
|
|
106
105
|
return (
|
|
107
|
-
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
{label}
|
|
117
|
-
</Text>
|
|
118
|
-
{isCurrent && (
|
|
119
|
-
<HStack gap={1} vAlign="center">
|
|
120
|
-
<StatusDot variant="success" label="Current session" />
|
|
121
|
-
<Text type="supporting" color="secondary">
|
|
122
|
-
Current session
|
|
123
|
-
</Text>
|
|
124
|
-
</HStack>
|
|
125
|
-
)}
|
|
126
|
-
</HStack>
|
|
127
|
-
<Text
|
|
128
|
-
type="supporting"
|
|
129
|
-
color="secondary"
|
|
130
|
-
display="block"
|
|
131
|
-
hasTabularNumbers>
|
|
132
|
-
{location}
|
|
106
|
+
<HStack gap={3} vAlign="start">
|
|
107
|
+
<Icon icon={Monitor} />
|
|
108
|
+
<StackItem size="fill">
|
|
109
|
+
<VStack gap={1}>
|
|
110
|
+
{/* wrap: the label plus the session status exceed the compact
|
|
111
|
+
content width (~280px). */}
|
|
112
|
+
<HStack gap={2} vAlign="center" wrap="wrap">
|
|
113
|
+
<Text type="body" weight="semibold">
|
|
114
|
+
{label}
|
|
133
115
|
</Text>
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
116
|
+
{isCurrent && (
|
|
117
|
+
<HStack gap={1} vAlign="center">
|
|
118
|
+
<StatusDot variant="success" label="Current session" />
|
|
119
|
+
<Text type="supporting" color="secondary">
|
|
120
|
+
Current session
|
|
121
|
+
</Text>
|
|
122
|
+
</HStack>
|
|
123
|
+
)}
|
|
124
|
+
</HStack>
|
|
125
|
+
<Text
|
|
126
|
+
type="supporting"
|
|
127
|
+
color="secondary"
|
|
128
|
+
display="block"
|
|
129
|
+
hasTabularNumbers>
|
|
130
|
+
{location}
|
|
131
|
+
</Text>
|
|
132
|
+
</VStack>
|
|
133
|
+
</StackItem>
|
|
134
|
+
{/* Ending a session changes state in place — a Button, not a Link. */}
|
|
135
|
+
{action && <Button label={action} variant="secondary" size="sm" />}
|
|
136
|
+
</HStack>
|
|
144
137
|
);
|
|
145
138
|
}
|
|
146
139
|
|
|
@@ -274,7 +267,7 @@ export default function SettingsDialog() {
|
|
|
274
267
|
onEdit={() => handleEdit('legalName')}
|
|
275
268
|
onCancel={handleCancel}
|
|
276
269
|
onSave={handleSave}
|
|
277
|
-
|
|
270
|
+
hasDivider={false}>
|
|
278
271
|
<TextInput
|
|
279
272
|
label="Legal name"
|
|
280
273
|
isLabelHidden
|
|
@@ -289,7 +282,7 @@ export default function SettingsDialog() {
|
|
|
289
282
|
onEdit={() => handleEdit('preferredName')}
|
|
290
283
|
onCancel={handleCancel}
|
|
291
284
|
onSave={handleSave}
|
|
292
|
-
|
|
285
|
+
hasDivider={false}>
|
|
293
286
|
<TextInput
|
|
294
287
|
label="Preferred first name"
|
|
295
288
|
isLabelHidden
|
|
@@ -304,7 +297,7 @@ export default function SettingsDialog() {
|
|
|
304
297
|
onEdit={() => handleEdit('email')}
|
|
305
298
|
onCancel={handleCancel}
|
|
306
299
|
onSave={handleSave}
|
|
307
|
-
|
|
300
|
+
hasDivider={false}>
|
|
308
301
|
<TextInput
|
|
309
302
|
label="Email address"
|
|
310
303
|
isLabelHidden
|
|
@@ -319,7 +312,7 @@ export default function SettingsDialog() {
|
|
|
319
312
|
onEdit={() => handleEdit('phone')}
|
|
320
313
|
onCancel={handleCancel}
|
|
321
314
|
onSave={handleSave}
|
|
322
|
-
|
|
315
|
+
hasDivider={false}>
|
|
323
316
|
<TextInput
|
|
324
317
|
label="Phone number"
|
|
325
318
|
isLabelHidden
|
|
@@ -331,7 +324,7 @@ export default function SettingsDialog() {
|
|
|
331
324
|
label="Identity verification"
|
|
332
325
|
value="Verified"
|
|
333
326
|
action=""
|
|
334
|
-
|
|
327
|
+
hasDivider={false}
|
|
335
328
|
/>
|
|
336
329
|
<ExpandableRow
|
|
337
330
|
label="Residential address"
|
|
@@ -340,7 +333,7 @@ export default function SettingsDialog() {
|
|
|
340
333
|
onEdit={() => handleEdit('address')}
|
|
341
334
|
onCancel={handleCancel}
|
|
342
335
|
onSave={handleSave}
|
|
343
|
-
|
|
336
|
+
hasDivider={false}>
|
|
344
337
|
<TextInput
|
|
345
338
|
label="Residential address"
|
|
346
339
|
isLabelHidden
|
|
@@ -355,7 +348,7 @@ export default function SettingsDialog() {
|
|
|
355
348
|
onEdit={() => handleEdit('mailingAddress')}
|
|
356
349
|
onCancel={handleCancel}
|
|
357
350
|
onSave={handleSave}
|
|
358
|
-
|
|
351
|
+
hasDivider={false}>
|
|
359
352
|
<TextInput
|
|
360
353
|
label="Mailing address"
|
|
361
354
|
isLabelHidden
|
|
@@ -370,7 +363,7 @@ export default function SettingsDialog() {
|
|
|
370
363
|
onEdit={() => handleEdit('emergencyContact')}
|
|
371
364
|
onCancel={handleCancel}
|
|
372
365
|
onSave={handleSave}
|
|
373
|
-
|
|
366
|
+
hasDivider={false}>
|
|
374
367
|
<TextInput
|
|
375
368
|
label="Emergency contact"
|
|
376
369
|
isLabelHidden
|
|
@@ -380,13 +373,12 @@ export default function SettingsDialog() {
|
|
|
380
373
|
</ExpandableRow>
|
|
381
374
|
</VStack>
|
|
382
375
|
|
|
376
|
+
{/* One divider at most per panel: the tiles are one
|
|
377
|
+
group, so `gap` carries the rhythm. */}
|
|
383
378
|
<Card padding={4}>
|
|
384
379
|
<VStack gap={4}>
|
|
385
|
-
{INFO_TILES.map(
|
|
386
|
-
<
|
|
387
|
-
{i > 0 && <Divider />}
|
|
388
|
-
<InfoTile {...tile} />
|
|
389
|
-
</React.Fragment>
|
|
380
|
+
{INFO_TILES.map(tile => (
|
|
381
|
+
<InfoTile key={tile.title} {...tile} />
|
|
390
382
|
))}
|
|
391
383
|
</VStack>
|
|
392
384
|
</Card>
|
|
@@ -403,27 +395,27 @@ export default function SettingsDialog() {
|
|
|
403
395
|
<Tab value="shared" label="Shared access" />
|
|
404
396
|
</TabList>
|
|
405
397
|
|
|
398
|
+
{/* Four genuinely different groups, each with its own
|
|
399
|
+
heading: `gap` is the boundary, so no rule between
|
|
400
|
+
them and none after the last row. */}
|
|
406
401
|
{activeTab === 'login' && (
|
|
407
402
|
<VStack gap={8}>
|
|
408
|
-
<VStack gap={
|
|
403
|
+
<VStack gap={2}>
|
|
409
404
|
<Heading level={3}>Login</Heading>
|
|
410
|
-
<Divider />
|
|
411
405
|
{LOGIN_ROWS.map(row => (
|
|
412
|
-
<InfoRowItem key={row.label} {...row}
|
|
406
|
+
<InfoRowItem key={row.label} {...row} hasDivider={false} />
|
|
413
407
|
))}
|
|
414
408
|
</VStack>
|
|
415
409
|
|
|
416
|
-
<VStack gap={
|
|
410
|
+
<VStack gap={2}>
|
|
417
411
|
<Heading level={3}>Social accounts</Heading>
|
|
418
|
-
<Divider />
|
|
419
412
|
{SOCIAL_ROWS.map(row => (
|
|
420
|
-
<InfoRowItem key={row.label} {...row}
|
|
413
|
+
<InfoRowItem key={row.label} {...row} hasDivider={false} />
|
|
421
414
|
))}
|
|
422
415
|
</VStack>
|
|
423
416
|
|
|
424
|
-
<VStack gap={
|
|
417
|
+
<VStack gap={2}>
|
|
425
418
|
<Heading level={3}>Device history</Heading>
|
|
426
|
-
<Divider />
|
|
427
419
|
{DEVICE_ROWS.map(device => (
|
|
428
420
|
<DeviceRowItem
|
|
429
421
|
key={device.location}
|
|
@@ -432,11 +424,10 @@ export default function SettingsDialog() {
|
|
|
432
424
|
))}
|
|
433
425
|
</VStack>
|
|
434
426
|
|
|
435
|
-
<VStack gap={
|
|
427
|
+
<VStack gap={2}>
|
|
436
428
|
<Heading level={3}>Account</Heading>
|
|
437
|
-
<Divider />
|
|
438
429
|
<HStack hAlign="between" vAlign="start">
|
|
439
|
-
<VStack gap={
|
|
430
|
+
<VStack gap={1}>
|
|
440
431
|
<Text
|
|
441
432
|
type="body"
|
|
442
433
|
weight="semibold"
|
|
@@ -450,9 +441,13 @@ export default function SettingsDialog() {
|
|
|
450
441
|
This action cannot be undone
|
|
451
442
|
</Text>
|
|
452
443
|
</VStack>
|
|
453
|
-
|
|
444
|
+
{/* Deactivating changes state in place. */}
|
|
445
|
+
<Button
|
|
446
|
+
label="Deactivate"
|
|
447
|
+
variant="destructive"
|
|
448
|
+
size="sm"
|
|
449
|
+
/>
|
|
454
450
|
</HStack>
|
|
455
|
-
<Divider />
|
|
456
451
|
</VStack>
|
|
457
452
|
</VStack>
|
|
458
453
|
)}
|
|
@@ -461,7 +456,6 @@ export default function SettingsDialog() {
|
|
|
461
456
|
<VStack gap={8}>
|
|
462
457
|
<VStack gap={2}>
|
|
463
458
|
<Heading level={3}>Shared access</Heading>
|
|
464
|
-
<Divider />
|
|
465
459
|
<Text type="body" color="secondary">
|
|
466
460
|
Review each request carefully before approving
|
|
467
461
|
access. We'll email your kin or coven-mate
|
|
@@ -506,7 +500,7 @@ export default function SettingsDialog() {
|
|
|
506
500
|
onEdit={() => handleEdit('language')}
|
|
507
501
|
onCancel={handleCancel}
|
|
508
502
|
onSave={handleSave}
|
|
509
|
-
|
|
503
|
+
hasDivider={false}>
|
|
510
504
|
<Selector
|
|
511
505
|
label="Language"
|
|
512
506
|
isLabelHidden
|
|
@@ -526,7 +520,7 @@ export default function SettingsDialog() {
|
|
|
526
520
|
onEdit={() => handleEdit('currency')}
|
|
527
521
|
onCancel={handleCancel}
|
|
528
522
|
onSave={handleSave}
|
|
529
|
-
|
|
523
|
+
hasDivider={false}>
|
|
530
524
|
<Selector
|
|
531
525
|
label="Currency"
|
|
532
526
|
isLabelHidden
|
|
@@ -546,7 +540,7 @@ export default function SettingsDialog() {
|
|
|
546
540
|
onEdit={() => handleEdit('timezone')}
|
|
547
541
|
onCancel={handleCancel}
|
|
548
542
|
onSave={handleSave}
|
|
549
|
-
|
|
543
|
+
hasDivider={false}>
|
|
550
544
|
<Selector
|
|
551
545
|
label="Time zone"
|
|
552
546
|
isLabelHidden
|
|
@@ -572,7 +566,6 @@ export default function SettingsDialog() {
|
|
|
572
566
|
labelPosition="start"
|
|
573
567
|
labelSpacing="spread"
|
|
574
568
|
/>
|
|
575
|
-
<Divider />
|
|
576
569
|
<Switch
|
|
577
570
|
label="Push notifications"
|
|
578
571
|
description="Time-sensitive alerts, delivered by raven."
|
|
@@ -581,7 +574,6 @@ export default function SettingsDialog() {
|
|
|
581
574
|
labelPosition="start"
|
|
582
575
|
labelSpacing="spread"
|
|
583
576
|
/>
|
|
584
|
-
<Divider />
|
|
585
577
|
</VStack>
|
|
586
578
|
</VStack>
|
|
587
579
|
)}
|
|
@@ -590,18 +582,17 @@ export default function SettingsDialog() {
|
|
|
590
582
|
<VStack gap={6}>
|
|
591
583
|
<VStack gap={4}>
|
|
592
584
|
<Heading level={3}>Payments</Heading>
|
|
593
|
-
<Divider />
|
|
594
585
|
<InfoRowItem
|
|
595
586
|
label="Payout crypt"
|
|
596
587
|
value="Visa ending in 4821"
|
|
597
588
|
action=""
|
|
598
|
-
|
|
589
|
+
hasDivider={false}
|
|
599
590
|
/>
|
|
600
591
|
<InfoRowItem
|
|
601
592
|
label="Past payouts"
|
|
602
593
|
value="No tributes yet"
|
|
603
594
|
action=""
|
|
604
|
-
|
|
595
|
+
hasDivider={false}
|
|
605
596
|
/>
|
|
606
597
|
</VStack>
|
|
607
598
|
</VStack>
|
|
@@ -611,18 +602,17 @@ export default function SettingsDialog() {
|
|
|
611
602
|
<VStack gap={6}>
|
|
612
603
|
<VStack gap={4}>
|
|
613
604
|
<Heading level={3}>Taxes</Heading>
|
|
614
|
-
<Divider />
|
|
615
605
|
<InfoRowItem
|
|
616
606
|
label="Tax scrolls"
|
|
617
607
|
value="Not submitted"
|
|
618
608
|
action=""
|
|
619
|
-
|
|
609
|
+
hasDivider={false}
|
|
620
610
|
/>
|
|
621
611
|
<InfoRowItem
|
|
622
612
|
label="Past scrolls"
|
|
623
613
|
value="Available after your first tribute"
|
|
624
614
|
action=""
|
|
625
|
-
|
|
615
|
+
hasDivider={false}
|
|
626
616
|
/>
|
|
627
617
|
</VStack>
|
|
628
618
|
</VStack>
|
|
@@ -640,7 +630,6 @@ export default function SettingsDialog() {
|
|
|
640
630
|
labelPosition="start"
|
|
641
631
|
labelSpacing="spread"
|
|
642
632
|
/>
|
|
643
|
-
<Divider />
|
|
644
633
|
</VStack>
|
|
645
634
|
</VStack>
|
|
646
635
|
)}
|
|
@@ -663,7 +652,6 @@ export default function SettingsDialog() {
|
|
|
663
652
|
</Text>
|
|
664
653
|
<Link href={SELF_HASH}>View</Link>
|
|
665
654
|
</HStack>
|
|
666
|
-
<Divider />
|
|
667
655
|
</VStack>
|
|
668
656
|
|
|
669
657
|
<VStack gap={4}>
|
|
@@ -676,14 +664,15 @@ export default function SettingsDialog() {
|
|
|
676
664
|
labelPosition="start"
|
|
677
665
|
labelSpacing="spread"
|
|
678
666
|
/>
|
|
679
|
-
<Divider />
|
|
680
667
|
</VStack>
|
|
681
668
|
|
|
682
669
|
<VStack gap={4}>
|
|
683
670
|
<Heading level={3}>Reviews</Heading>
|
|
684
671
|
<Text type="body" color="secondary">
|
|
685
672
|
Choose what's shared when you write a review.{' '}
|
|
686
|
-
<Link href={SELF_HASH}
|
|
673
|
+
<Link href={SELF_HASH} hasUnderline>
|
|
674
|
+
Learn more
|
|
675
|
+
</Link>
|
|
687
676
|
</Text>
|
|
688
677
|
<VStack gap={4}>
|
|
689
678
|
<Switch
|
|
@@ -719,7 +708,6 @@ export default function SettingsDialog() {
|
|
|
719
708
|
labelSpacing="spread"
|
|
720
709
|
/>
|
|
721
710
|
</VStack>
|
|
722
|
-
<Divider />
|
|
723
711
|
</VStack>
|
|
724
712
|
|
|
725
713
|
<VStack gap={4}>
|
|
@@ -727,7 +715,12 @@ export default function SettingsDialog() {
|
|
|
727
715
|
<Card>
|
|
728
716
|
<HStack hAlign="between" vAlign="center">
|
|
729
717
|
<Text type="body">Request my personal data</Text>
|
|
730
|
-
|
|
718
|
+
{/* A request is submitted in place, so it acts. */}
|
|
719
|
+
<Button
|
|
720
|
+
label="Request"
|
|
721
|
+
variant="secondary"
|
|
722
|
+
size="sm"
|
|
723
|
+
/>
|
|
731
724
|
</HStack>
|
|
732
725
|
</Card>
|
|
733
726
|
<Switch
|
|
@@ -741,7 +734,11 @@ export default function SettingsDialog() {
|
|
|
741
734
|
<Card>
|
|
742
735
|
<HStack hAlign="between" vAlign="center">
|
|
743
736
|
<Text type="body">Delete my account</Text>
|
|
744
|
-
<
|
|
737
|
+
<Button
|
|
738
|
+
label="Delete"
|
|
739
|
+
variant="destructive"
|
|
740
|
+
size="sm"
|
|
741
|
+
/>
|
|
745
742
|
</HStack>
|
|
746
743
|
</Card>
|
|
747
744
|
<Card variant="muted">
|
|
@@ -756,7 +753,7 @@ export default function SettingsDialog() {
|
|
|
756
753
|
<Text type="body" color="secondary">
|
|
757
754
|
We're committed to keeping your data
|
|
758
755
|
protected. See details in our{' '}
|
|
759
|
-
<Link href={SELF_HASH}>
|
|
756
|
+
<Link href={SELF_HASH} hasUnderline>
|
|
760
757
|
Privacy Policy
|
|
761
758
|
</Link>
|
|
762
759
|
.
|