@svadmin/ui 0.38.5 → 0.39.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 (37) hide show
  1. package/dist/components/AdminApp.svelte +3 -2
  2. package/dist/components/ApiSettings.svelte +86 -1
  3. package/dist/components/AutoTable.svelte +108 -96
  4. package/dist/components/DevTools.svelte +0 -5
  5. package/dist/components/ForgotPasswordPage.svelte +5 -8
  6. package/dist/components/LoginPage.svelte +5 -8
  7. package/dist/components/RegisterPage.svelte +5 -8
  8. package/dist/components/Sidebar.svelte +10 -4
  9. package/dist/components/SvadminLogo.svelte +16 -0
  10. package/dist/components/SvadminLogo.svelte.d.ts +7 -0
  11. package/dist/components/UpdatePasswordPage.svelte +5 -8
  12. package/dist/components/account/CompanyProfilePage.svelte +56 -1
  13. package/dist/components/account/ImportMembersPage.svelte +31 -0
  14. package/dist/components/account/SettingsEnterprisePage.svelte +172 -19
  15. package/dist/components/account/SettingsPlainPage.svelte +121 -17
  16. package/dist/components/account/SettingsSidebarPage.svelte +282 -70
  17. package/dist/components/account/TeamMembersPage.svelte +68 -2
  18. package/dist/components/account/UserProfilePage.svelte +206 -0
  19. package/dist/components/account/UserProfilePage.svelte.d.ts +4 -0
  20. package/dist/components/network/TeamCrewTablePage.svelte +17 -1
  21. package/dist/components/network/UserCardsNFTPage.svelte +5 -0
  22. package/dist/components/profile/ActivityTimeline.svelte +24 -2
  23. package/dist/components/profile/ActivityTimeline.svelte.d.ts +1 -0
  24. package/dist/components/profile/ProfileVariantSections.svelte +452 -0
  25. package/dist/components/profile/ProfileVariantSections.svelte.d.ts +8 -0
  26. package/dist/components/profile/ProjectsGrid.svelte +20 -6
  27. package/dist/components/profile/PublicProfilePage.svelte +42 -21
  28. package/dist/components/profile/PublicProfilePage.svelte.d.ts +1 -0
  29. package/dist/components/profile/TeamsShowcase.svelte +31 -12
  30. package/dist/components/profile/TeamsShowcase.svelte.d.ts +1 -0
  31. package/dist/components/ui/alert/alert.svelte.d.ts +14 -14
  32. package/dist/components/ui/avatar/avatar.svelte.d.ts +14 -14
  33. package/dist/components/ui/badge/badge.svelte.d.ts +20 -20
  34. package/dist/components/ui/button/button.svelte.d.ts +44 -44
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.js +3 -0
  37. package/package.json +14 -13
@@ -9,6 +9,7 @@
9
9
  import TooltipButton from './TooltipButton.svelte';
10
10
  import * as Tooltip from './ui/tooltip/index.js';
11
11
  import SidebarItem from './SidebarItem.svelte';
12
+ import SvadminLogo from './SvadminLogo.svelte';
12
13
 
13
14
  import { ScrollArea } from './ui/scroll-area/index.js';
14
15
  import * as Collapsible from './ui/collapsible/index.js';
@@ -218,13 +219,18 @@
218
219
  >
219
220
  <div class="flex h-[70px] items-center shrink-0" class:px-5={!collapsed} class:justify-center={collapsed}>
220
221
  {#if !collapsed}
221
- <a href={formatSidebarLink('/')} class="flex items-center gap-2.5 group" onclick={(e) => { e.preventDefault(); adminContext.navigate('/'); }}>
222
- <div class="w-[34px] h-[34px] rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0 transition-transform group-hover:scale-105" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
222
+ <a href={formatSidebarLink('/')} class="group flex items-center gap-2.5" onclick={(e) => { e.preventDefault(); adminContext.navigate('/'); }}>
223
+ <SvadminLogo />
223
224
  <span class="font-semibold text-[15px] tracking-[-0.01em] text-sidebar-foreground">{title}</span>
224
225
  </a>
225
226
  {:else}
226
- <a href={formatSidebarLink('/')} class="group" aria-label={title} onclick={(e) => { e.preventDefault(); adminContext.navigate('/'); }}>
227
- <div class="w-[34px] h-[34px] rounded-lg bg-gradient-to-tr from-primary to-purple-500 shrink-0 transition-transform group-hover:scale-105" style="box-shadow: 0 4px 12px oklch(0.488 0.243 264.376 / 20%);"></div>
227
+ <a
228
+ href={formatSidebarLink('/')}
229
+ class="group"
230
+ aria-label={title}
231
+ onclick={(e) => { e.preventDefault(); adminContext.navigate('/'); }}
232
+ >
233
+ <SvadminLogo />
228
234
  </a>
229
235
  {/if}
230
236
  </div>
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ let {
3
+ size = 34,
4
+ onDark = false,
5
+ }: {
6
+ size?: number;
7
+ onDark?: boolean;
8
+ } = $props();
9
+ </script>
10
+
11
+ <span
12
+ aria-hidden="true"
13
+ class="inline-block shrink-0 rounded-lg bg-gradient-to-tr from-primary to-purple-500 shadow-sm {onDark ? 'ring-1 ring-white/35' : ''}"
14
+ style:width={`${size}px`}
15
+ style:height={`${size}px`}
16
+ ></span>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ size?: number;
3
+ onDark?: boolean;
4
+ };
5
+ declare const SvadminLogo: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type SvadminLogo = ReturnType<typeof SvadminLogo>;
7
+ export default SvadminLogo;
@@ -5,7 +5,8 @@
5
5
  import { Button } from './ui/button/index.js';
6
6
  import * as Alert from './ui/alert/index.js';
7
7
  import PasswordInput from './PasswordInput.svelte';
8
- import { ShieldCheck, Loader2, AlertCircle, Shield } from '@lucide/svelte';
8
+ import SvadminLogo from './SvadminLogo.svelte';
9
+ import { Loader2, AlertCircle } from '@lucide/svelte';
9
10
 
10
11
  const i18n = useTranslation();
11
12
 
@@ -47,9 +48,7 @@
47
48
 
48
49
  <div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
49
50
  <div class="flex items-center gap-3">
50
- <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-white/15 backdrop-blur-sm">
51
- <Shield class="h-5 w-5 text-white" />
52
- </div>
51
+ <SvadminLogo size={36} onDark />
53
52
  <span class="text-lg font-semibold text-white tracking-tight">{title}</span>
54
53
  </div>
55
54
 
@@ -73,10 +72,8 @@
73
72
  <!-- Right form panel -->
74
73
  <div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-8 lg:p-12">
75
74
  <div class="w-full max-w-[400px]">
76
- <div class="flex items-center gap-3 mb-8 lg:hidden">
77
- <div class="flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10 text-primary">
78
- <ShieldCheck class="h-5 w-5" />
79
- </div>
75
+ <div class="mb-8 flex items-center gap-3 lg:hidden">
76
+ <SvadminLogo size={36} />
80
77
  <span class="text-lg font-semibold tracking-tight">{title}</span>
81
78
  </div>
82
79
 
@@ -4,7 +4,9 @@
4
4
  import { Button } from '../ui/button/index.js';
5
5
  import { Input } from '../ui/input/index.js';
6
6
  import { Label } from '../ui/label/index.js';
7
- import { Upload, Loader2 } from '@lucide/svelte';
7
+ import { Badge } from '../ui/badge/index.js';
8
+ import * as Table from '../ui/table/index.js';
9
+ import { Upload, Loader2, ShieldCheck, ShieldOff } from '@lucide/svelte';
8
10
 
9
11
  const i18n = useTranslation();
10
12
 
@@ -16,6 +18,14 @@
16
18
  let description = $state('Building the future of enterprise software with cutting-edge AI and cloud solutions.');
17
19
  let saving = $state(false);
18
20
 
21
+ const members = [
22
+ { name: 'Alex Chen', email: 'alex@acme.com', role: 'Admin', twoFactor: true, joined: '2022-03-15' },
23
+ { name: 'Sarah Kim', email: 'sarah@acme.com', role: 'Editor', twoFactor: true, joined: '2022-06-22' },
24
+ { name: 'Mike Johnson', email: 'mike@acme.com', role: 'Viewer', twoFactor: false, joined: '2023-01-10' },
25
+ { name: 'Emma Davis', email: 'emma@acme.com', role: 'Admin', twoFactor: true, joined: '2022-02-14' },
26
+ { name: 'Lisa Wang', email: 'lisa@acme.com', role: 'Editor', twoFactor: false, joined: '2023-04-05' },
27
+ ];
28
+
19
29
  async function handleSave() {
20
30
  saving = true;
21
31
  await new Promise(r => setTimeout(r, 800));
@@ -98,4 +108,49 @@
98
108
  </div>
99
109
  </Card.CardContent>
100
110
  </Card.Card>
111
+
112
+ <!-- Members -->
113
+ <Card.Card class="border-border/60">
114
+ <Card.CardHeader>
115
+ <Card.CardTitle class="text-base">{i18n.t('profileSections.members')}</Card.CardTitle>
116
+ </Card.CardHeader>
117
+ <Card.CardContent class="p-0">
118
+ <Table.Root>
119
+ <Table.Header>
120
+ <Table.Row>
121
+ <Table.Head>{i18n.t('profile.name')}</Table.Head>
122
+ <Table.Head>{i18n.t('account.role')}</Table.Head>
123
+ <Table.Head>2FA</Table.Head>
124
+ <Table.Head class="text-right">{i18n.t('account.joined')}</Table.Head>
125
+ </Table.Row>
126
+ </Table.Header>
127
+ <Table.Body>
128
+ {#each members as member (member.email)}
129
+ <Table.Row>
130
+ <Table.Cell>
131
+ <div class="flex items-center gap-3">
132
+ <div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary text-xs font-semibold">
133
+ {member.name.split(' ').map(n => n[0]).join('').slice(0, 2)}
134
+ </div>
135
+ <div class="min-w-0">
136
+ <p class="truncate text-sm font-medium text-foreground">{member.name}</p>
137
+ <p class="truncate text-xs text-muted-foreground">{member.email}</p>
138
+ </div>
139
+ </div>
140
+ </Table.Cell>
141
+ <Table.Cell><Badge variant="secondary" class="text-[10px]">{member.role}</Badge></Table.Cell>
142
+ <Table.Cell>
143
+ {#if member.twoFactor}
144
+ <ShieldCheck class="h-4 w-4 text-green-500" />
145
+ {:else}
146
+ <ShieldOff class="h-4 w-4 text-muted-foreground/50" />
147
+ {/if}
148
+ </Table.Cell>
149
+ <Table.Cell class="text-right text-muted-foreground">{member.joined}</Table.Cell>
150
+ </Table.Row>
151
+ {/each}
152
+ </Table.Body>
153
+ </Table.Root>
154
+ </Card.CardContent>
155
+ </Card.Card>
101
156
  </div>
@@ -2,6 +2,8 @@
2
2
  import { useTranslation } from '@svadmin/core/i18n';
3
3
  import * as Card from '../ui/card/index.js';
4
4
  import { Button } from '../ui/button/index.js';
5
+ import { Switch } from '../ui/switch/index.js';
6
+ import { Label } from '../ui/label/index.js';
5
7
  import { Upload, Download, FileText, CheckCircle2, Loader2 } from '@lucide/svelte';
6
8
 
7
9
  const i18n = useTranslation();
@@ -12,6 +14,15 @@
12
14
  let progress = $state(0);
13
15
  let importCount = $state(0);
14
16
 
17
+ const importOptions = [
18
+ { key: 'account.importCreateUsers', checked: true },
19
+ { key: 'account.importUpdateUsers', checked: false },
20
+ { key: 'account.importNotifyPassword', checked: false },
21
+ { key: 'account.importExternalIds', checked: false },
22
+ { key: 'account.importWelcomeEmail', checked: true },
23
+ ];
24
+ let optionStates = $state<boolean[]>(importOptions.map(o => o.checked));
25
+
15
26
  function handleDragOver(e: DragEvent) {
16
27
  e.preventDefault();
17
28
  isDragging = true;
@@ -141,5 +152,25 @@
141
152
  </Button>
142
153
  </Card.CardContent>
143
154
  </Card.Card>
155
+
156
+ <!-- Import options -->
157
+ <Card.Card class="border-border/60">
158
+ <Card.CardHeader>
159
+ <Card.CardTitle class="text-base">{i18n.t('account.importOptions')}</Card.CardTitle>
160
+ </Card.CardHeader>
161
+ <Card.CardContent class="space-y-4">
162
+ {#each importOptions as option, i (option.key)}
163
+ <div class="flex items-center justify-between gap-4">
164
+ <Label for="import-option-{i}" class="text-sm font-normal text-foreground">{i18n.t(option.key)}</Label>
165
+ <Switch id="import-option-{i}" bind:checked={optionStates[i]} />
166
+ </div>
167
+ {/each}
168
+ <div class="flex justify-end border-t pt-4">
169
+ <Button onclick={() => { const input = document.getElementById('file-input'); input?.click(); }}>
170
+ {i18n.t('account.startImport')}
171
+ </Button>
172
+ </div>
173
+ </Card.CardContent>
174
+ </Card.Card>
144
175
  {/if}
145
176
  </div>
@@ -6,10 +6,33 @@
6
6
  import { Label } from '../ui/label/index.js';
7
7
  import { Switch } from '../ui/switch/index.js';
8
8
  import { Badge } from '../ui/badge/index.js';
9
- import { Loader2, Shield, Building2, Lock, Globe } from '@lucide/svelte';
9
+ import { Loader2, Shield, Building2, Lock, Globe, Calendar, Link2, Database } from '@lucide/svelte';
10
10
 
11
11
  const i18n = useTranslation();
12
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
+
13
36
  let ssoEnabled = $state(false);
14
37
  let ssoProvider = $state('');
15
38
  let domainRestriction = $state('');
@@ -41,26 +64,26 @@
41
64
  <Card.CardHeader>
42
65
  <Card.CardTitle class="flex items-center gap-2 text-base">
43
66
  <Shield class="h-4 w-4 text-muted-foreground" />
44
- 单点登录 (SSO)
67
+ {i18n.t('account.singleSignOn')}
45
68
  </Card.CardTitle>
46
- <Card.CardDescription>配置企业身份提供商以实现统一登录体验。</Card.CardDescription>
69
+ <Card.CardDescription>{i18n.t('account.ssoDescription')}</Card.CardDescription>
47
70
  </Card.CardHeader>
48
71
  <Card.CardContent class="space-y-4">
49
72
  <div class="flex items-center justify-between gap-4">
50
73
  <div class="space-y-1">
51
- <Label for="sso-enabled">启用 SSO</Label>
52
- <p class="text-xs text-muted-foreground">允许员工通过企业 IdP 登录</p>
74
+ <Label for="sso-enabled">{i18n.t('account.enableSso')}</Label>
75
+ <p class="text-xs text-muted-foreground">{i18n.t('account.enableSsoDesc')}</p>
53
76
  </div>
54
77
  <Switch id="sso-enabled" bind:checked={ssoEnabled} />
55
78
  </div>
56
79
  {#if ssoEnabled}
57
80
  <div class="grid gap-4 sm:grid-cols-2">
58
81
  <div class="space-y-2">
59
- <Label for="sso-provider">SSO 提供商</Label>
82
+ <Label for="sso-provider">{i18n.t('account.ssoProvider')}</Label>
60
83
  <Input id="sso-provider" bind:value={ssoProvider} placeholder="Okta, Azure AD, etc." />
61
84
  </div>
62
85
  <div class="space-y-2">
63
- <Label for="domain-restriction">域名限制</Label>
86
+ <Label for="domain-restriction">{i18n.t('account.domainRestriction')}</Label>
64
87
  <Input id="domain-restriction" bind:value={domainRestriction} placeholder="@company.com" />
65
88
  </div>
66
89
  </div>
@@ -73,29 +96,29 @@
73
96
  <Card.CardHeader>
74
97
  <Card.CardTitle class="flex items-center gap-2 text-base">
75
98
  <Lock class="h-4 w-4 text-muted-foreground" />
76
- 安全策略
99
+ {i18n.t('account.securityPolicy')}
77
100
  </Card.CardTitle>
78
101
  </Card.CardHeader>
79
102
  <Card.CardContent class="space-y-4">
80
103
  <div class="grid gap-4 sm:grid-cols-2">
81
104
  <div class="space-y-2">
82
- <Label for="password-policy">密码策略</Label>
105
+ <Label for="password-policy">{i18n.t('account.passwordPolicy')}</Label>
83
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">
84
- <option value="basic">基本</option>
85
- <option value="strong">强密码</option>
86
- <option value="very-strong">非常强</option>
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>
87
110
  </select>
88
111
  </div>
89
112
  <div class="space-y-2">
90
- <Label for="session-timeout">会话超时(分钟)</Label>
113
+ <Label for="session-timeout">{i18n.t('account.sessionTimeout')}</Label>
91
114
  <Input id="session-timeout" bind:value={sessionTimeout} type="number" />
92
115
  </div>
93
116
  </div>
94
117
 
95
118
  <div class="space-y-2">
96
- <Label for="ip-whitelist">IP 白名单</Label>
119
+ <Label for="ip-whitelist">{i18n.t('account.ipWhitelist')}</Label>
97
120
  <Input id="ip-whitelist" bind:value={ipWhitelist} placeholder="10.0.0.0/8, 192.168.1.0/24" />
98
- <p class="text-xs text-muted-foreground">留空表示不限制 IP</p>
121
+ <p class="text-xs text-muted-foreground">{i18n.t('account.ipWhitelistHint')}</p>
99
122
  </div>
100
123
  </Card.CardContent>
101
124
  </Card.Card>
@@ -105,20 +128,20 @@
105
128
  <Card.CardHeader>
106
129
  <Card.CardTitle class="flex items-center gap-2 text-base">
107
130
  <Globe class="h-4 w-4 text-muted-foreground" />
108
- 数据与合规
131
+ {i18n.t('account.dataCompliance')}
109
132
  </Card.CardTitle>
110
133
  </Card.CardHeader>
111
134
  <Card.CardContent class="space-y-4">
112
135
  <div class="flex items-center justify-between gap-4">
113
136
  <div class="space-y-1">
114
- <Label for="audit-logging">审计日志</Label>
115
- <p class="text-xs text-muted-foreground">记录所有管理操作以备合规审查</p>
137
+ <Label for="audit-logging">{i18n.t('account.auditLogging')}</Label>
138
+ <p class="text-xs text-muted-foreground">{i18n.t('account.auditLoggingDesc')}</p>
116
139
  </div>
117
140
  <Switch id="audit-logging" bind:checked={auditLogging} />
118
141
  </div>
119
142
 
120
143
  <div class="space-y-2">
121
- <Label for="data-retention">数据保留天数</Label>
144
+ <Label for="data-retention">{i18n.t('account.dataRetention')}</Label>
122
145
  <Input id="data-retention" bind:value={dataRetention} type="number" />
123
146
  </div>
124
147
 
@@ -130,4 +153,134 @@
130
153
  </div>
131
154
  </Card.CardContent>
132
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>
133
286
  </div>