@svadmin/ui 0.42.3 → 0.44.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 (79) hide show
  1. package/dist/app.css +462 -825
  2. package/dist/app.theme.css +462 -825
  3. package/dist/components/ErrorPage.svelte +4 -56
  4. package/dist/components/ResourceOperationsPage.svelte +84 -223
  5. package/dist/components/ResourceOperationsPage.svelte.d.ts +1 -1
  6. package/dist/components/TwoFactorAuthPage.svelte +44 -232
  7. package/dist/components/account/CompanyProfilePage.svelte +21 -142
  8. package/dist/components/account/GetStartedPage.svelte +45 -106
  9. package/dist/components/account/GetStartedPage.svelte.d.ts +1 -2
  10. package/dist/components/account/MembersStarterPage.svelte +49 -92
  11. package/dist/components/account/SecurityLogPage.svelte +16 -131
  12. package/dist/components/account/SettingsEnterprisePage.svelte +15 -275
  13. package/dist/components/account/SettingsPlainPage.svelte +10 -175
  14. package/dist/components/account/SettingsSidebarPage.svelte +11 -304
  15. package/dist/components/account/TeamMembersPage.svelte +18 -187
  16. package/dist/components/account/UserProfilePage.svelte +19 -196
  17. package/dist/components/account/UserProfilePage.svelte.d.ts +1 -2
  18. package/dist/components/content/ApiKeyList.svelte +13 -0
  19. package/dist/components/content/ApiKeyList.svelte.d.ts +16 -0
  20. package/dist/components/content/ContentPageHeader.svelte +31 -0
  21. package/dist/components/content/ContentPageHeader.svelte.d.ts +12 -0
  22. package/dist/components/content/ContentPageShell.svelte +39 -0
  23. package/dist/components/content/ContentPageShell.svelte.d.ts +14 -0
  24. package/dist/components/content/DataState.svelte +44 -0
  25. package/dist/components/content/DataState.svelte.d.ts +13 -0
  26. package/dist/components/content/DescriptionList.svelte +27 -0
  27. package/dist/components/content/DescriptionList.svelte.d.ts +14 -0
  28. package/dist/components/content/FileList.svelte +13 -0
  29. package/dist/components/content/FileList.svelte.d.ts +16 -0
  30. package/dist/components/content/FilterToolbar.svelte +24 -0
  31. package/dist/components/content/FilterToolbar.svelte.d.ts +11 -0
  32. package/dist/components/content/ImportDropzone.svelte +19 -0
  33. package/dist/components/content/ImportDropzone.svelte.d.ts +10 -0
  34. package/dist/components/content/IntegrationCard.svelte +16 -0
  35. package/dist/components/content/IntegrationCard.svelte.d.ts +15 -0
  36. package/dist/components/content/MemberList.svelte +16 -0
  37. package/dist/components/content/MemberList.svelte.d.ts +17 -0
  38. package/dist/components/content/MetricBlock.svelte +27 -0
  39. package/dist/components/content/MetricBlock.svelte.d.ts +13 -0
  40. package/dist/components/content/NetworkTable.svelte +18 -0
  41. package/dist/components/content/NetworkTable.svelte.d.ts +31 -0
  42. package/dist/components/content/NetworkTable.types.d.ts +4 -0
  43. package/dist/components/content/NetworkTable.types.js +1 -0
  44. package/dist/components/content/NetworkUserCard.svelte +17 -0
  45. package/dist/components/content/NetworkUserCard.svelte.d.ts +17 -0
  46. package/dist/components/content/OtpInput.svelte +22 -0
  47. package/dist/components/content/OtpInput.svelte.d.ts +10 -0
  48. package/dist/components/content/PageToolbar.svelte +18 -0
  49. package/dist/components/content/PageToolbar.svelte.d.ts +10 -0
  50. package/dist/components/content/ProjectCard.svelte +39 -0
  51. package/dist/components/content/ProjectCard.svelte.d.ts +20 -0
  52. package/dist/components/content/SectionHeader.svelte +18 -0
  53. package/dist/components/content/SectionHeader.svelte.d.ts +10 -0
  54. package/dist/components/content/SecurityEventTable.svelte +18 -0
  55. package/dist/components/content/SecurityEventTable.svelte.d.ts +15 -0
  56. package/dist/components/content/StatusBadge.svelte +19 -0
  57. package/dist/components/content/StatusBadge.svelte.d.ts +9 -0
  58. package/dist/components/content/SystemErrorState.svelte +18 -0
  59. package/dist/components/content/SystemErrorState.svelte.d.ts +10 -0
  60. package/dist/components/content/TeamCard.svelte +26 -0
  61. package/dist/components/content/TeamCard.svelte.d.ts +23 -0
  62. package/dist/components/content/TwoFactorStepper.svelte +11 -0
  63. package/dist/components/content/TwoFactorStepper.svelte.d.ts +8 -0
  64. package/dist/components/content/index.d.ts +32 -0
  65. package/dist/components/content/index.js +22 -0
  66. package/dist/components/network/TeamCrewTablePage.svelte +29 -181
  67. package/dist/components/network/UserCardsNFTPage.svelte +24 -137
  68. package/dist/components/profile/ActivityTimeline.svelte +17 -91
  69. package/dist/components/profile/ProfileCard.svelte +4 -4
  70. package/dist/components/profile/ProjectsGrid.svelte +28 -106
  71. package/dist/components/profile/ProjectsGrid.svelte.d.ts +2 -12
  72. package/dist/components/profile/PublicProfilePage.svelte +34 -186
  73. package/dist/components/profile/TeamsShowcase.svelte +12 -114
  74. package/dist/components/profile/TeamsShowcase.svelte.d.ts +2 -12
  75. package/dist/index.d.ts +36 -0
  76. package/dist/index.js +24 -0
  77. package/dist/reference-data.d.ts +171 -0
  78. package/dist/reference-data.js +47 -0
  79. package/package.json +2 -2
@@ -1,286 +1,26 @@
1
1
  <script lang="ts">
2
2
  import { useTranslation } from '@svadmin/core/i18n';
3
- import * as Card from '../ui/card/index.js';
4
3
  import { Button } from '../ui/button/index.js';
5
4
  import { Input } from '../ui/input/index.js';
6
5
  import { Label } from '../ui/label/index.js';
7
6
  import { Switch } from '../ui/switch/index.js';
8
- import { Badge } from '../ui/badge/index.js';
9
- import { Loader2, Shield, Building2, Lock, Globe, Calendar, Link2, Database } from '@lucide/svelte';
10
-
7
+ import * as Card from '../ui/card/index.js';
8
+ import ContentPageShell from '../content/ContentPageShell.svelte';
9
+ import ContentPageHeader from '../content/ContentPageHeader.svelte';
10
+ import IntegrationCard from '../content/IntegrationCard.svelte';
11
+ import type { IntegrationSummary } from '../content/IntegrationCard.svelte';
12
+ import SecurityEventTable from '../content/SecurityEventTable.svelte';
13
+ import type { SecurityEvent } from '../content/SecurityEventTable.svelte';
11
14
  const i18n = useTranslation();
12
-
13
- const trustedDevices = [
14
- { name: 'MacBook Pro 16"', detail: 'macOS · Shanghai, CN', lastSeen: '2026-08-04 09:12', current: true },
15
- { name: 'iPhone 17', detail: 'iOS · Shanghai, CN', lastSeen: '2026-08-03 22:41', current: false },
16
- { name: 'Windows Workstation', detail: 'Windows · Beijing, CN', lastSeen: '2026-07-28 18:05', current: false },
17
- ];
18
-
19
- const paymentHistory = [
20
- { date: '2026-08-01', type: 'Enterprise · Monthly', amount: '$299.00' },
21
- { date: '2026-07-01', type: 'Enterprise · Monthly', amount: '$299.00' },
22
- { date: '2026-06-01', type: 'Enterprise · Monthly', amount: '$299.00' },
23
- ];
24
-
25
- const calendarAccounts = [
26
- { name: 'Work Calendar', account: 'alex@acme.com', active: true },
27
- { name: 'Personal Calendar', account: 'alex@example.com', active: false },
28
- ];
29
-
30
- const connections = [
31
- { name: 'Source Control', account: 'alexchen', linked: true },
32
- { name: 'ChatOps', account: '@alex.chen', linked: true },
33
- { name: 'Cloud Drive', account: 'alex@acme.com', linked: false },
34
- ];
35
-
36
15
  let ssoEnabled = $state(false);
37
- let ssoProvider = $state('');
38
- let domainRestriction = $state('');
39
- let passwordPolicy = $state('strong');
40
- let sessionTimeout = $state('30');
41
- let ipWhitelist = $state('');
42
16
  let auditLogging = $state(true);
43
- let dataRetention = $state('365');
44
- let saving = $state(false);
45
-
46
- async function handleSave() {
47
- saving = true;
48
- await new Promise(r => setTimeout(r, 800));
49
- saving = false;
50
- }
17
+ const identity: IntegrationSummary[] = [{ id: 'sso', name: 'Enterprise identity provider', description: i18n.t('account.ssoDescription'), account: 'SAML / OIDC', connected: false }];
18
+ const events: SecurityEvent[] = [{ id: '1', event: 'SSO policy updated', actor: 'Alex Chen', location: 'Shanghai, CN', createdAt: 'Today, 09:12', severity: 'info' }, { id: '2', event: 'Audit log retention changed', actor: 'Alex Chen', location: 'Shanghai, CN', createdAt: 'Yesterday, 16:40', severity: 'warning' }];
51
19
  </script>
52
20
 
53
- <div class="space-y-6" data-svadmin-content-page="account">
54
- <div class="flex items-start justify-between gap-4">
55
- <div>
56
- <h2 class="text-xl font-semibold text-foreground">{i18n.t('account.settingsEnterprise')}</h2>
57
- <p class="mt-1 text-sm text-muted-foreground">{i18n.t('account.settingsEnterpriseDescription')}</p>
58
- </div>
59
- <Badge variant="secondary"><Building2 class="h-3 w-3 mr-1" />Enterprise</Badge>
60
- </div>
61
-
62
- <!-- SSO -->
63
- <Card.Card class="border-border/60">
64
- <Card.CardHeader>
65
- <Card.CardTitle class="flex items-center gap-2 text-base">
66
- <Shield class="h-4 w-4 text-muted-foreground" />
67
- {i18n.t('account.singleSignOn')}
68
- </Card.CardTitle>
69
- <Card.CardDescription>{i18n.t('account.ssoDescription')}</Card.CardDescription>
70
- </Card.CardHeader>
71
- <Card.CardContent class="space-y-4">
72
- <div class="flex items-center justify-between gap-4">
73
- <div class="space-y-1">
74
- <Label for="sso-enabled">{i18n.t('account.enableSso')}</Label>
75
- <p class="text-xs text-muted-foreground">{i18n.t('account.enableSsoDesc')}</p>
76
- </div>
77
- <Switch id="sso-enabled" bind:checked={ssoEnabled} />
78
- </div>
79
- {#if ssoEnabled}
80
- <div class="grid gap-4 sm:grid-cols-2">
81
- <div class="space-y-2">
82
- <Label for="sso-provider">{i18n.t('account.ssoProvider')}</Label>
83
- <Input id="sso-provider" bind:value={ssoProvider} placeholder="Okta, Azure AD, etc." />
84
- </div>
85
- <div class="space-y-2">
86
- <Label for="domain-restriction">{i18n.t('account.domainRestriction')}</Label>
87
- <Input id="domain-restriction" bind:value={domainRestriction} placeholder="@company.com" />
88
- </div>
89
- </div>
90
- {/if}
91
- </Card.CardContent>
92
- </Card.Card>
93
-
94
- <!-- Security Policy -->
95
- <Card.Card class="border-border/60">
96
- <Card.CardHeader>
97
- <Card.CardTitle class="flex items-center gap-2 text-base">
98
- <Lock class="h-4 w-4 text-muted-foreground" />
99
- {i18n.t('account.securityPolicy')}
100
- </Card.CardTitle>
101
- </Card.CardHeader>
102
- <Card.CardContent class="space-y-4">
103
- <div class="grid gap-4 sm:grid-cols-2">
104
- <div class="space-y-2">
105
- <Label for="password-policy">{i18n.t('account.passwordPolicy')}</Label>
106
- <select id="password-policy" bind:value={passwordPolicy} class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring">
107
- <option value="basic">{i18n.t('account.policyBasic')}</option>
108
- <option value="strong">{i18n.t('account.policyStrong')}</option>
109
- <option value="very-strong">{i18n.t('account.policyVeryStrong')}</option>
110
- </select>
111
- </div>
112
- <div class="space-y-2">
113
- <Label for="session-timeout">{i18n.t('account.sessionTimeout')}</Label>
114
- <Input id="session-timeout" bind:value={sessionTimeout} type="number" />
115
- </div>
116
- </div>
117
-
118
- <div class="space-y-2">
119
- <Label for="ip-whitelist">{i18n.t('account.ipWhitelist')}</Label>
120
- <Input id="ip-whitelist" bind:value={ipWhitelist} placeholder="10.0.0.0/8, 192.168.1.0/24" />
121
- <p class="text-xs text-muted-foreground">{i18n.t('account.ipWhitelistHint')}</p>
122
- </div>
123
- </Card.CardContent>
124
- </Card.Card>
125
-
126
- <!-- Data & Compliance -->
127
- <Card.Card class="border-border/60">
128
- <Card.CardHeader>
129
- <Card.CardTitle class="flex items-center gap-2 text-base">
130
- <Globe class="h-4 w-4 text-muted-foreground" />
131
- {i18n.t('account.dataCompliance')}
132
- </Card.CardTitle>
133
- </Card.CardHeader>
134
- <Card.CardContent class="space-y-4">
135
- <div class="flex items-center justify-between gap-4">
136
- <div class="space-y-1">
137
- <Label for="audit-logging">{i18n.t('account.auditLogging')}</Label>
138
- <p class="text-xs text-muted-foreground">{i18n.t('account.auditLoggingDesc')}</p>
139
- </div>
140
- <Switch id="audit-logging" bind:checked={auditLogging} />
141
- </div>
142
-
143
- <div class="space-y-2">
144
- <Label for="data-retention">{i18n.t('account.dataRetention')}</Label>
145
- <Input id="data-retention" bind:value={dataRetention} type="number" />
146
- </div>
147
-
148
- <div class="flex justify-end">
149
- <Button onclick={handleSave} disabled={saving}>
150
- {#if saving}<Loader2 class="h-4 w-4 animate-spin mr-2" />{/if}
151
- {i18n.t('common.save')}
152
- </Button>
153
- </div>
154
- </Card.CardContent>
155
- </Card.Card>
156
-
157
- <div class="grid gap-4 lg:grid-cols-2">
158
- <!-- Trusted Devices -->
159
- <Card.Card class="border-border/60">
160
- <Card.CardHeader>
161
- <Card.CardTitle class="text-base">{i18n.t('account.trustedDevices')}</Card.CardTitle>
162
- </Card.CardHeader>
163
- <Card.CardContent class="p-0">
164
- <div class="divide-y">
165
- {#each trustedDevices as device (device.name)}
166
- <div class="flex items-center justify-between gap-3 px-6 py-3">
167
- <div class="min-w-0">
168
- <p class="truncate text-sm font-medium text-foreground">{device.name}</p>
169
- <p class="truncate text-xs text-muted-foreground">{device.detail} · {device.lastSeen}</p>
170
- </div>
171
- {#if device.current}
172
- <Badge variant="secondary" class="shrink-0 text-[10px]">{i18n.t('security.currentSession')}</Badge>
173
- {/if}
174
- </div>
175
- {/each}
176
- </div>
177
- </Card.CardContent>
178
- </Card.Card>
179
-
180
- <!-- Current Plan + Payment History -->
181
- <div class="space-y-4">
182
- <Card.Card class="border-primary/30 bg-primary/5">
183
- <Card.CardHeader class="pb-3">
184
- <div class="flex items-center justify-between">
185
- <Card.CardTitle class="text-base">{i18n.t('account.currentPlan')}</Card.CardTitle>
186
- <Badge variant="secondary">Enterprise</Badge>
187
- </div>
188
- </Card.CardHeader>
189
- <Card.CardContent>
190
- <div class="flex items-baseline gap-1">
191
- <span class="text-2xl font-bold text-foreground">$299</span>
192
- <span class="text-sm text-muted-foreground">{i18n.t('account.perMonth')}</span>
193
- </div>
194
- <p class="mt-1 text-xs text-muted-foreground">Unlimited seats · SSO · Audit retention 365d</p>
195
- </Card.CardContent>
196
- </Card.Card>
197
-
198
- <Card.Card class="border-border/60">
199
- <Card.CardHeader class="pb-3">
200
- <Card.CardTitle class="text-base">{i18n.t('account.paymentHistory')}</Card.CardTitle>
201
- </Card.CardHeader>
202
- <Card.CardContent class="p-0">
203
- <div class="divide-y">
204
- <div class="grid grid-cols-3 px-6 py-2 text-xs font-medium text-muted-foreground">
205
- <span>{i18n.t('account.date')}</span>
206
- <span>{i18n.t('account.type')}</span>
207
- <span class="text-right">{i18n.t('account.amount')}</span>
208
- </div>
209
- {#each paymentHistory as row (row.date)}
210
- <div class="grid grid-cols-3 px-6 py-2.5 text-sm">
211
- <span class="text-muted-foreground">{row.date}</span>
212
- <span class="text-foreground">{row.type}</span>
213
- <span class="text-right font-medium text-foreground">{row.amount}</span>
214
- </div>
215
- {/each}
216
- </div>
217
- </Card.CardContent>
218
- </Card.Card>
219
- </div>
220
- </div>
221
-
222
- <div class="grid gap-4 lg:grid-cols-3">
223
- <!-- Calendar Accounts -->
224
- <Card.Card class="border-border/60">
225
- <Card.CardHeader>
226
- <Card.CardTitle class="flex items-center gap-2 text-base">
227
- <Calendar class="h-4 w-4 text-muted-foreground" />{i18n.t('account.calendarAccounts')}
228
- </Card.CardTitle>
229
- </Card.CardHeader>
230
- <Card.CardContent class="space-y-3">
231
- {#each calendarAccounts as cal (cal.account)}
232
- <div class="flex items-center justify-between gap-3">
233
- <div class="min-w-0">
234
- <p class="truncate text-sm font-medium text-foreground">{cal.name}</p>
235
- <p class="truncate text-xs text-muted-foreground">{cal.account}</p>
236
- </div>
237
- <Badge variant={cal.active ? 'secondary' : 'outline'} class="shrink-0 text-[10px]">
238
- {cal.active ? i18n.t('account.active') : i18n.t('account.inactive')}
239
- </Badge>
240
- </div>
241
- {/each}
242
- </Card.CardContent>
243
- </Card.Card>
244
-
245
- <!-- Connections -->
246
- <Card.Card class="border-border/60">
247
- <Card.CardHeader>
248
- <Card.CardTitle class="flex items-center gap-2 text-base">
249
- <Link2 class="h-4 w-4 text-muted-foreground" />{i18n.t('account.connections')}
250
- </Card.CardTitle>
251
- </Card.CardHeader>
252
- <Card.CardContent class="space-y-3">
253
- {#each connections as conn (conn.name)}
254
- <div class="flex items-center justify-between gap-3">
255
- <div class="min-w-0">
256
- <p class="truncate text-sm font-medium text-foreground">{conn.name}</p>
257
- <p class="truncate text-xs text-muted-foreground">{conn.account}</p>
258
- </div>
259
- <Badge variant={conn.linked ? 'secondary' : 'outline'} class="shrink-0 text-[10px]">
260
- {conn.linked ? i18n.t('integrations.statusConnected') : i18n.t('integrations.statusDisconnected')}
261
- </Badge>
262
- </div>
263
- {/each}
264
- </Card.CardContent>
265
- </Card.Card>
266
-
267
- <!-- Data import -->
268
- <Card.Card class="border-border/60">
269
- <Card.CardHeader>
270
- <Card.CardTitle class="flex items-center gap-2 text-base">
271
- <Database class="h-4 w-4 text-muted-foreground" />{i18n.t('account.dataImport')}
272
- </Card.CardTitle>
273
- <Card.CardDescription>{i18n.t('account.dataImportDescription')}</Card.CardDescription>
274
- </Card.CardHeader>
275
- <Card.CardContent>
276
- <div class="flex items-center justify-between gap-3 rounded-lg border border-border/60 p-3">
277
- <div class="min-w-0">
278
- <p class="truncate text-sm font-medium text-foreground">Google Analytics</p>
279
- <p class="truncate text-xs text-muted-foreground">analytics.google.com</p>
280
- </div>
281
- <Button size="sm" variant="outline">{i18n.t('network.connect')}</Button>
282
- </div>
283
- </Card.CardContent>
284
- </Card.Card>
285
- </div>
286
- </div>
21
+ <ContentPageShell pageId="account-settings-enterprise" width="wide">
22
+ <ContentPageHeader title={i18n.t('account.settingsEnterprise')} description={i18n.t('account.settingsEnterpriseDescription')} />
23
+ <div class="grid gap-4 lg:grid-cols-2"><Card.Card><Card.CardContent class="space-y-4 p-5"><div><h2 class="text-sm font-semibold text-foreground">{i18n.t('account.securityPolicy')}</h2><p class="mt-1 text-sm text-muted-foreground">Set organization-wide controls for authentication and sessions.</p></div><div class="space-y-2"><Label for="session-timeout">{i18n.t('account.sessionTimeout')}</Label><Input id="session-timeout" value="30" /></div><div class="space-y-2"><Label for="retention">{i18n.t('account.dataRetention')}</Label><Input id="retention" value="365" /></div><div class="flex items-center justify-between border-t border-border pt-4"><span class="text-sm text-foreground">{i18n.t('account.auditLogging')}</span><Switch bind:checked={auditLogging} /></div><Button size="sm">{i18n.t('common.save')}</Button></Card.CardContent></Card.Card><Card.Card><Card.CardContent class="space-y-4 p-5"><div><h2 class="text-sm font-semibold text-foreground">{i18n.t('account.singleSignOn')}</h2><p class="mt-1 text-sm text-muted-foreground">{i18n.t('account.ssoDescription')}</p></div><div class="flex items-center justify-between border-t border-border pt-4"><span class="text-sm text-foreground">{i18n.t('account.enableSso')}</span><Switch bind:checked={ssoEnabled} /></div><Input value="https://idp.example.com/metadata" aria-label={i18n.t('account.ssoProvider')} /><Button variant="outline" size="sm">{i18n.t('common.test')}</Button></Card.CardContent></Card.Card></div>
24
+ <section class="space-y-3"><h2 class="text-base font-semibold text-foreground">{i18n.t('account.connections')}</h2>{#each identity as integration (integration.id)}<IntegrationCard {integration} />{/each}</section>
25
+ <section class="space-y-3"><h2 class="text-base font-semibold text-foreground">{i18n.t('account.securityLog')}</h2><SecurityEventTable events={events} /></section>
26
+ </ContentPageShell>
@@ -1,187 +1,22 @@
1
1
  <script lang="ts">
2
2
  import { useTranslation } from '@svadmin/core/i18n';
3
- import * as Card from '../ui/card/index.js';
4
3
  import { Button } from '../ui/button/index.js';
5
4
  import { Input } from '../ui/input/index.js';
6
5
  import { Label } from '../ui/label/index.js';
7
6
  import { Switch } from '../ui/switch/index.js';
8
- import { Separator } from '../ui/separator/index.js';
9
- import { Loader2, Camera, Trash2 } from '@lucide/svelte';
10
-
7
+ import * as Card from '../ui/card/index.js';
8
+ import ContentPageShell from '../content/ContentPageShell.svelte';
9
+ import ContentPageHeader from '../content/ContentPageHeader.svelte';
11
10
  const i18n = useTranslation();
12
-
13
11
  let displayName = $state('Alex Chen');
14
12
  let email = $state('alex@example.com');
15
- let phone = $state('+86 138 **** 9527');
16
- let dateOfBirth = $state('1995-04-18');
17
- let bio = $state('Full-stack developer focused on design systems and developer tooling.');
18
- let country = $state('China');
19
- let region = $state('Shanghai');
20
- let city = $state('Shanghai');
21
- let postcode = $state('200120');
22
- let currentPassword = $state('');
23
- let newPassword = $state('');
24
- let confirmPassword = $state('');
25
- let emailNotifications = $state(true);
26
- let twoFactor = $state(false);
27
- let confirmDelete = $state(false);
13
+ let notifications = $state(true);
14
+ let twoFactor = $state(true);
28
15
  let saving = $state(false);
29
-
30
- async function handleSave() {
31
- saving = true;
32
- await new Promise((r) => setTimeout(r, 800));
33
- saving = false;
34
- }
16
+ async function save() { saving = true; await new Promise((resolve) => setTimeout(resolve, 400)); saving = false; }
35
17
  </script>
36
18
 
37
- <div class="space-y-6" data-svadmin-content-page="account">
38
- <div class="flex items-start justify-between gap-4">
39
- <div>
40
- <h2 class="text-xl font-semibold text-foreground">{i18n.t('account.settingsPlain')}</h2>
41
- <p class="mt-1 text-sm text-muted-foreground">{i18n.t('account.settingsPlainDescription')}</p>
42
- </div>
43
- <Button onclick={handleSave} disabled={saving} size="sm">
44
- {#if saving}<Loader2 class="h-4 w-4 animate-spin mr-2" />{/if}
45
- {i18n.t('common.save')}
46
- </Button>
47
- </div>
48
-
49
- <!-- General Settings -->
50
- <Card.Card class="border-border/60">
51
- <Card.CardHeader>
52
- <Card.CardTitle class="text-base">{i18n.t('settings.general')}</Card.CardTitle>
53
- </Card.CardHeader>
54
- <Card.CardContent class="space-y-4">
55
- <div class="flex items-center gap-4">
56
- <div class="relative">
57
- <div class="flex h-16 w-16 items-center justify-center rounded-full bg-primary/10 text-primary text-xl font-bold">AC</div>
58
- <button class="absolute -bottom-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm">
59
- <Camera class="h-3 w-3" />
60
- </button>
61
- </div>
62
- <div>
63
- <p class="text-sm font-medium text-foreground">{i18n.t('account.photo')}</p>
64
- <p class="text-xs text-muted-foreground">150x150px JPEG, PNG Image</p>
65
- </div>
66
- </div>
67
-
68
- <Separator />
69
-
70
- <div class="grid gap-4 sm:grid-cols-2">
71
- <div class="space-y-2">
72
- <Label for="display-name">{i18n.t('profile.name')}</Label>
73
- <Input id="display-name" bind:value={displayName} />
74
- </div>
75
- <div class="space-y-2">
76
- <Label for="email">Email</Label>
77
- <Input id="email" bind:value={email} type="email" />
78
- </div>
79
- <div class="space-y-2">
80
- <Label for="phone">{i18n.t('account.phone')}</Label>
81
- <Input id="phone" bind:value={phone} />
82
- </div>
83
- <div class="space-y-2">
84
- <Label for="dob">{i18n.t('account.dateOfBirth')}</Label>
85
- <Input id="dob" bind:value={dateOfBirth} type="date" />
86
- </div>
87
- </div>
88
-
89
- <div class="space-y-2">
90
- <Label for="bio">{i18n.t('account.bio')}</Label>
91
- <textarea
92
- id="bio"
93
- bind:value={bio}
94
- rows={3}
95
- class="flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
96
- ></textarea>
97
- </div>
98
-
99
- <Separator />
100
-
101
- <div class="flex items-center justify-between gap-4">
102
- <div class="space-y-1">
103
- <Label for="email-notif">{i18n.t('notifications.emailTitle')}</Label>
104
- <p class="text-xs text-muted-foreground">{i18n.t('notifications.emailDescription')}</p>
105
- </div>
106
- <Switch id="email-notif" bind:checked={emailNotifications} />
107
- </div>
108
-
109
- <div class="flex items-center justify-between gap-4">
110
- <div class="space-y-1">
111
- <Label for="two-factor">{i18n.t('account.twoFactorAuth')}</Label>
112
- <p class="text-xs text-muted-foreground">{i18n.t('security.twoFactorDescription')}</p>
113
- </div>
114
- <Switch id="two-factor" bind:checked={twoFactor} />
115
- </div>
116
- </Card.CardContent>
117
- </Card.Card>
118
-
119
- <!-- Address -->
120
- <Card.Card class="border-border/60">
121
- <Card.CardHeader>
122
- <Card.CardTitle class="text-base">{i18n.t('account.address')}</Card.CardTitle>
123
- </Card.CardHeader>
124
- <Card.CardContent>
125
- <div class="grid gap-4 sm:grid-cols-2">
126
- <div class="space-y-2">
127
- <Label for="country">{i18n.t('account.country')}</Label>
128
- <Input id="country" bind:value={country} />
129
- </div>
130
- <div class="space-y-2">
131
- <Label for="state">{i18n.t('account.state')}</Label>
132
- <Input id="state" bind:value={region} />
133
- </div>
134
- <div class="space-y-2">
135
- <Label for="city">{i18n.t('account.city')}</Label>
136
- <Input id="city" bind:value={city} />
137
- </div>
138
- <div class="space-y-2">
139
- <Label for="postcode">{i18n.t('account.postcode')}</Label>
140
- <Input id="postcode" bind:value={postcode} />
141
- </div>
142
- </div>
143
- </Card.CardContent>
144
- </Card.Card>
145
-
146
- <!-- Password -->
147
- <Card.Card class="border-border/60">
148
- <Card.CardHeader>
149
- <Card.CardTitle class="text-base">{i18n.t('account.password')}</Card.CardTitle>
150
- </Card.CardHeader>
151
- <Card.CardContent>
152
- <div class="grid gap-4 sm:grid-cols-3">
153
- <div class="space-y-2">
154
- <Label for="current-password">{i18n.t('profile.currentPassword')}</Label>
155
- <Input id="current-password" bind:value={currentPassword} type="password" />
156
- </div>
157
- <div class="space-y-2">
158
- <Label for="new-password">{i18n.t('profile.newPassword')}</Label>
159
- <Input id="new-password" bind:value={newPassword} type="password" />
160
- </div>
161
- <div class="space-y-2">
162
- <Label for="confirm-password">{i18n.t('auth.confirmPassword')}</Label>
163
- <Input id="confirm-password" bind:value={confirmPassword} type="password" />
164
- </div>
165
- </div>
166
- </Card.CardContent>
167
- </Card.Card>
168
-
169
- <!-- Delete Account -->
170
- <Card.Card class="border-destructive/30">
171
- <Card.CardHeader>
172
- <Card.CardTitle class="text-base text-destructive">{i18n.t('account.deleteAccount')}</Card.CardTitle>
173
- <Card.CardDescription>{i18n.t('account.deleteAccountDescription')}</Card.CardDescription>
174
- </Card.CardHeader>
175
- <Card.CardContent class="space-y-4">
176
- <div class="flex items-center justify-between gap-4">
177
- <Label for="confirm-delete" class="text-sm font-normal">{i18n.t('account.confirmDeleteAccount')}</Label>
178
- <Switch id="confirm-delete" bind:checked={confirmDelete} />
179
- </div>
180
- <div class="flex justify-end">
181
- <Button variant="destructive" size="sm" disabled={!confirmDelete}>
182
- <Trash2 class="h-4 w-4 mr-1" />{i18n.t('account.deleteAccount')}
183
- </Button>
184
- </div>
185
- </Card.CardContent>
186
- </Card.Card>
187
- </div>
19
+ <ContentPageShell pageId="account-settings-plain" width="narrow">
20
+ <div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between"><ContentPageHeader title={i18n.t('account.settingsPlain')} description={i18n.t('account.settingsPlainDescription')} /><Button size="sm" onclick={save} disabled={saving}>{i18n.t('common.save')}</Button></div>
21
+ <Card.Card><Card.CardContent class="space-y-5 p-5"><div class="space-y-2"><Label for="settings-name">{i18n.t('profile.name')}</Label><Input id="settings-name" bind:value={displayName} /></div><div class="space-y-2"><Label for="settings-email">Email</Label><Input id="settings-email" bind:value={email} type="email" /></div><div class="flex items-center justify-between gap-4 border-t border-border pt-4"><div><p class="text-sm font-medium text-foreground">{i18n.t('settings.notifications')}</p><p class="mt-1 text-sm text-muted-foreground">Receive account and workflow updates.</p></div><Switch bind:checked={notifications} /></div><div class="flex items-center justify-between gap-4 border-t border-border pt-4"><div><p class="text-sm font-medium text-foreground">{i18n.t('account.twoFactorAuth')}</p><p class="mt-1 text-sm text-muted-foreground">Require an additional verification code when signing in.</p></div><Switch bind:checked={twoFactor} /></div></Card.CardContent></Card.Card>
22
+ </ContentPageShell>