@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.
- package/dist/components/AdminApp.svelte +3 -2
- package/dist/components/ApiSettings.svelte +86 -1
- package/dist/components/AutoTable.svelte +108 -96
- package/dist/components/DevTools.svelte +0 -5
- package/dist/components/ForgotPasswordPage.svelte +5 -8
- package/dist/components/LoginPage.svelte +5 -8
- package/dist/components/RegisterPage.svelte +5 -8
- package/dist/components/Sidebar.svelte +10 -4
- package/dist/components/SvadminLogo.svelte +16 -0
- package/dist/components/SvadminLogo.svelte.d.ts +7 -0
- package/dist/components/UpdatePasswordPage.svelte +5 -8
- package/dist/components/account/CompanyProfilePage.svelte +56 -1
- package/dist/components/account/ImportMembersPage.svelte +31 -0
- package/dist/components/account/SettingsEnterprisePage.svelte +172 -19
- package/dist/components/account/SettingsPlainPage.svelte +121 -17
- package/dist/components/account/SettingsSidebarPage.svelte +282 -70
- package/dist/components/account/TeamMembersPage.svelte +68 -2
- package/dist/components/account/UserProfilePage.svelte +206 -0
- package/dist/components/account/UserProfilePage.svelte.d.ts +4 -0
- package/dist/components/network/TeamCrewTablePage.svelte +17 -1
- package/dist/components/network/UserCardsNFTPage.svelte +5 -0
- package/dist/components/profile/ActivityTimeline.svelte +24 -2
- package/dist/components/profile/ActivityTimeline.svelte.d.ts +1 -0
- package/dist/components/profile/ProfileVariantSections.svelte +452 -0
- package/dist/components/profile/ProfileVariantSections.svelte.d.ts +8 -0
- package/dist/components/profile/ProjectsGrid.svelte +20 -6
- package/dist/components/profile/PublicProfilePage.svelte +42 -21
- package/dist/components/profile/PublicProfilePage.svelte.d.ts +1 -0
- package/dist/components/profile/TeamsShowcase.svelte +31 -12
- package/dist/components/profile/TeamsShowcase.svelte.d.ts +1 -0
- package/dist/components/ui/alert/alert.svelte.d.ts +14 -14
- package/dist/components/ui/avatar/avatar.svelte.d.ts +14 -14
- package/dist/components/ui/badge/badge.svelte.d.ts +20 -20
- package/dist/components/ui/button/button.svelte.d.ts +44 -44
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- 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
|
|
222
|
-
<
|
|
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
|
|
227
|
-
|
|
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>
|
|
@@ -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
|
|
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
|
-
<
|
|
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
|
|
77
|
-
<
|
|
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 {
|
|
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
|
-
|
|
67
|
+
{i18n.t('account.singleSignOn')}
|
|
45
68
|
</Card.CardTitle>
|
|
46
|
-
<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"
|
|
52
|
-
<p class="text-xs text-muted-foreground"
|
|
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">
|
|
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"
|
|
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"
|
|
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"
|
|
85
|
-
<option value="strong"
|
|
86
|
-
<option value="very-strong"
|
|
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"
|
|
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">
|
|
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"
|
|
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"
|
|
115
|
-
<p class="text-xs text-muted-foreground"
|
|
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"
|
|
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>
|