@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
|
@@ -6,35 +6,67 @@
|
|
|
6
6
|
import { Label } from '../ui/label/index.js';
|
|
7
7
|
import { Switch } from '../ui/switch/index.js';
|
|
8
8
|
import { Separator } from '../ui/separator/index.js';
|
|
9
|
-
import { Loader2 } from '@lucide/svelte';
|
|
9
|
+
import { Loader2, Camera, Trash2 } from '@lucide/svelte';
|
|
10
10
|
|
|
11
11
|
const i18n = useTranslation();
|
|
12
12
|
|
|
13
13
|
let displayName = $state('Alex Chen');
|
|
14
14
|
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('');
|
|
15
25
|
let emailNotifications = $state(true);
|
|
16
|
-
let pushNotifications = $state(true);
|
|
17
26
|
let twoFactor = $state(false);
|
|
27
|
+
let confirmDelete = $state(false);
|
|
18
28
|
let saving = $state(false);
|
|
19
29
|
|
|
20
30
|
async function handleSave() {
|
|
21
31
|
saving = true;
|
|
22
|
-
await new Promise(r => setTimeout(r, 800));
|
|
32
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
23
33
|
saving = false;
|
|
24
34
|
}
|
|
25
35
|
</script>
|
|
26
36
|
|
|
27
37
|
<div class="space-y-6" data-svadmin-content-page="account">
|
|
28
|
-
<div>
|
|
29
|
-
<
|
|
30
|
-
|
|
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>
|
|
31
47
|
</div>
|
|
32
48
|
|
|
49
|
+
<!-- General Settings -->
|
|
33
50
|
<Card.Card class="border-border/60">
|
|
34
51
|
<Card.CardHeader>
|
|
35
52
|
<Card.CardTitle class="text-base">{i18n.t('settings.general')}</Card.CardTitle>
|
|
36
53
|
</Card.CardHeader>
|
|
37
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
|
+
|
|
38
70
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
39
71
|
<div class="space-y-2">
|
|
40
72
|
<Label for="display-name">{i18n.t('profile.name')}</Label>
|
|
@@ -44,6 +76,24 @@
|
|
|
44
76
|
<Label for="email">Email</Label>
|
|
45
77
|
<Input id="email" bind:value={email} type="email" />
|
|
46
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>
|
|
47
97
|
</div>
|
|
48
98
|
|
|
49
99
|
<Separator />
|
|
@@ -58,24 +108,78 @@
|
|
|
58
108
|
|
|
59
109
|
<div class="flex items-center justify-between gap-4">
|
|
60
110
|
<div class="space-y-1">
|
|
61
|
-
<Label for="
|
|
62
|
-
<p class="text-xs text-muted-foreground">{i18n.t('
|
|
111
|
+
<Label for="two-factor">{i18n.t('account.twoFactorAuth')}</Label>
|
|
112
|
+
<p class="text-xs text-muted-foreground">{i18n.t('security.twoFactorDescription')}</p>
|
|
63
113
|
</div>
|
|
64
|
-
<Switch id="
|
|
114
|
+
<Switch id="two-factor" bind:checked={twoFactor} />
|
|
65
115
|
</div>
|
|
116
|
+
</Card.CardContent>
|
|
117
|
+
</Card.Card>
|
|
66
118
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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} />
|
|
71
141
|
</div>
|
|
72
|
-
<Switch id="two-factor" bind:checked={twoFactor} />
|
|
73
142
|
</div>
|
|
143
|
+
</Card.CardContent>
|
|
144
|
+
</Card.Card>
|
|
74
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>
|
|
75
180
|
<div class="flex justify-end">
|
|
76
|
-
<Button
|
|
77
|
-
|
|
78
|
-
{i18n.t('common.save')}
|
|
181
|
+
<Button variant="destructive" size="sm" disabled={!confirmDelete}>
|
|
182
|
+
<Trash2 class="h-4 w-4 mr-1" />{i18n.t('account.deleteAccount')}
|
|
79
183
|
</Button>
|
|
80
184
|
</div>
|
|
81
185
|
</Card.CardContent>
|
|
@@ -2,99 +2,311 @@
|
|
|
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 { Badge } from '../ui/badge/index.js';
|
|
6
|
+
import { Input } from '../ui/input/index.js';
|
|
5
7
|
import { Label } from '../ui/label/index.js';
|
|
6
8
|
import { Switch } from '../ui/switch/index.js';
|
|
7
9
|
import { Separator } from '../ui/separator/index.js';
|
|
8
|
-
import { Loader2,
|
|
10
|
+
import { Loader2, Camera, Key, Copy, Check } from '@lucide/svelte';
|
|
9
11
|
|
|
10
12
|
const i18n = useTranslation();
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const navItems = [
|
|
15
|
+
{ id: 'basic', label: 'account.basicSettings' },
|
|
16
|
+
{ id: 'signin', label: 'account.socialSignIn' },
|
|
17
|
+
{ id: 'password', label: 'account.password' },
|
|
18
|
+
{ id: 'sso', label: 'account.singleSignOn' },
|
|
19
|
+
{ id: 'preferences', label: 'account.preferences' },
|
|
20
|
+
{ id: 'address', label: 'account.address' },
|
|
21
|
+
{ id: 'api', label: 'account.manageApi' },
|
|
22
|
+
{ id: 'delete', label: 'account.deleteAccount' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
let activeSection = $state('basic');
|
|
26
|
+
|
|
27
|
+
function scrollTo(id: string) {
|
|
28
|
+
activeSection = id;
|
|
29
|
+
if (typeof document !== 'undefined') {
|
|
30
|
+
document.getElementById(`settings-section-${id}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let displayName = $state('Alex Chen');
|
|
35
|
+
let company = $state('Nebula Labs');
|
|
36
|
+
let phone = $state('+86 138 **** 9527');
|
|
37
|
+
let email = $state('alex@example.com');
|
|
38
|
+
let availableToHire = $state(true);
|
|
39
|
+
let profileVisible = $state(true);
|
|
40
|
+
let socialGithub = $state(true);
|
|
41
|
+
let socialGoogle = $state(false);
|
|
42
|
+
let twoFactor = $state(true);
|
|
43
|
+
let language = $state('en');
|
|
44
|
+
let timezone = $state('Asia/Shanghai');
|
|
45
|
+
let currency = $state('CNY');
|
|
46
|
+
let apiKey = $state('sv_demo_pub_9f3e••••41ab');
|
|
47
|
+
let copied = $state(false);
|
|
17
48
|
let saving = $state(false);
|
|
18
49
|
|
|
19
50
|
async function handleSave() {
|
|
20
51
|
saving = true;
|
|
21
|
-
await new Promise(r => setTimeout(r, 800));
|
|
52
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
22
53
|
saving = false;
|
|
23
54
|
}
|
|
55
|
+
|
|
56
|
+
async function copyApiKey() {
|
|
57
|
+
try {
|
|
58
|
+
await navigator.clipboard.writeText(apiKey);
|
|
59
|
+
copied = true;
|
|
60
|
+
setTimeout(() => (copied = false), 2000);
|
|
61
|
+
} catch {
|
|
62
|
+
copied = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
24
65
|
</script>
|
|
25
66
|
|
|
26
67
|
<div class="space-y-6" data-svadmin-content-page="account">
|
|
27
|
-
<div>
|
|
28
|
-
<
|
|
29
|
-
|
|
68
|
+
<div class="flex items-start justify-between gap-4">
|
|
69
|
+
<div>
|
|
70
|
+
<h2 class="text-xl font-semibold text-foreground">{i18n.t('account.settingsSidebar')}</h2>
|
|
71
|
+
<p class="mt-1 text-sm text-muted-foreground">{i18n.t('account.settingsSidebarDescription')}</p>
|
|
72
|
+
</div>
|
|
73
|
+
<Button onclick={handleSave} disabled={saving} size="sm">
|
|
74
|
+
{#if saving}<Loader2 class="h-4 w-4 animate-spin mr-2" />{/if}
|
|
75
|
+
{i18n.t('common.save')}
|
|
76
|
+
</Button>
|
|
30
77
|
</div>
|
|
31
78
|
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<Switch id="default-collapsed" bind:checked={defaultCollapsed} />
|
|
79
|
+
<div class="flex flex-col gap-6 lg:flex-row">
|
|
80
|
+
<!-- Anchor nav -->
|
|
81
|
+
<nav class="w-full shrink-0 lg:w-52">
|
|
82
|
+
<div class="flex gap-1 overflow-x-auto lg:sticky lg:top-6 lg:flex-col">
|
|
83
|
+
{#each navItems as item (item.id)}
|
|
84
|
+
<button
|
|
85
|
+
class="whitespace-nowrap rounded-lg px-3 py-2 text-left text-sm transition-colors
|
|
86
|
+
{activeSection === item.id ? 'bg-primary/10 font-medium text-primary' : 'text-muted-foreground hover:bg-accent hover:text-foreground'}"
|
|
87
|
+
onclick={() => scrollTo(item.id)}
|
|
88
|
+
>
|
|
89
|
+
{i18n.t(item.label)}
|
|
90
|
+
</button>
|
|
91
|
+
{/each}
|
|
46
92
|
</div>
|
|
93
|
+
</nav>
|
|
47
94
|
|
|
48
|
-
|
|
95
|
+
<div class="min-w-0 flex-1 space-y-6">
|
|
96
|
+
<!-- Basic Settings -->
|
|
97
|
+
<Card.Card class="border-border/60 scroll-mt-6" id="settings-section-basic">
|
|
98
|
+
<Card.CardHeader>
|
|
99
|
+
<Card.CardTitle class="text-base">{i18n.t('account.basicSettings')}</Card.CardTitle>
|
|
100
|
+
</Card.CardHeader>
|
|
101
|
+
<Card.CardContent class="space-y-4">
|
|
102
|
+
<div class="flex items-center gap-4">
|
|
103
|
+
<div class="relative">
|
|
104
|
+
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-primary/10 text-primary text-xl font-bold">AC</div>
|
|
105
|
+
<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">
|
|
106
|
+
<Camera class="h-3 w-3" />
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
<div>
|
|
110
|
+
<p class="text-sm font-medium text-foreground">{i18n.t('account.photo')}</p>
|
|
111
|
+
<p class="text-xs text-muted-foreground">150x150px JPEG, PNG Image</p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
<Separator />
|
|
115
|
+
<div class="grid gap-4 sm:grid-cols-2">
|
|
116
|
+
<div class="space-y-2">
|
|
117
|
+
<Label for="sb-name">{i18n.t('profile.name')}</Label>
|
|
118
|
+
<Input id="sb-name" bind:value={displayName} />
|
|
119
|
+
</div>
|
|
120
|
+
<div class="space-y-2">
|
|
121
|
+
<Label for="sb-company">{i18n.t('profile.company')}</Label>
|
|
122
|
+
<Input id="sb-company" bind:value={company} />
|
|
123
|
+
</div>
|
|
124
|
+
<div class="space-y-2">
|
|
125
|
+
<Label for="sb-phone">{i18n.t('account.phone')}</Label>
|
|
126
|
+
<Input id="sb-phone" bind:value={phone} />
|
|
127
|
+
</div>
|
|
128
|
+
<div class="space-y-2">
|
|
129
|
+
<Label for="sb-email">Email</Label>
|
|
130
|
+
<Input id="sb-email" bind:value={email} type="email" />
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<Separator />
|
|
134
|
+
<div class="flex items-center justify-between gap-4">
|
|
135
|
+
<div class="space-y-1">
|
|
136
|
+
<Label for="sb-visibility">{i18n.t('account.visibility')}</Label>
|
|
137
|
+
<p class="text-xs text-muted-foreground">{i18n.t('account.publicProfileCard')}</p>
|
|
138
|
+
</div>
|
|
139
|
+
<Switch id="sb-visibility" bind:checked={profileVisible} />
|
|
140
|
+
</div>
|
|
141
|
+
<div class="flex items-center justify-between gap-4">
|
|
142
|
+
<div class="space-y-1">
|
|
143
|
+
<Label for="sb-availability">{i18n.t('account.availability')}</Label>
|
|
144
|
+
<p class="text-xs text-muted-foreground">{i18n.t('account.availableToHire')}</p>
|
|
145
|
+
</div>
|
|
146
|
+
<Switch id="sb-availability" bind:checked={availableToHire} />
|
|
147
|
+
</div>
|
|
148
|
+
</Card.CardContent>
|
|
149
|
+
</Card.Card>
|
|
49
150
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
</
|
|
55
|
-
<
|
|
56
|
-
|
|
151
|
+
<!-- Social Sign in + 2FA -->
|
|
152
|
+
<Card.Card class="border-border/60 scroll-mt-6" id="settings-section-signin">
|
|
153
|
+
<Card.CardHeader>
|
|
154
|
+
<Card.CardTitle class="text-base">{i18n.t('account.socialSignIn')}</Card.CardTitle>
|
|
155
|
+
</Card.CardHeader>
|
|
156
|
+
<Card.CardContent class="space-y-4">
|
|
157
|
+
<div class="flex items-center justify-between gap-4">
|
|
158
|
+
<div class="space-y-1">
|
|
159
|
+
<Label for="sb-github">Source Control</Label>
|
|
160
|
+
<p class="text-xs text-muted-foreground">alexchen</p>
|
|
161
|
+
</div>
|
|
162
|
+
<Switch id="sb-github" bind:checked={socialGithub} />
|
|
163
|
+
</div>
|
|
164
|
+
<div class="flex items-center justify-between gap-4">
|
|
165
|
+
<div class="space-y-1">
|
|
166
|
+
<Label for="sb-google">Identity Cloud</Label>
|
|
167
|
+
<p class="text-xs text-muted-foreground">alex@example.com</p>
|
|
168
|
+
</div>
|
|
169
|
+
<Switch id="sb-google" bind:checked={socialGoogle} />
|
|
170
|
+
</div>
|
|
171
|
+
<Separator />
|
|
172
|
+
<div class="flex items-center justify-between gap-4">
|
|
173
|
+
<div class="space-y-1">
|
|
174
|
+
<Label for="sb-2fa">{i18n.t('account.twoFactorAuth')}</Label>
|
|
175
|
+
<p class="text-xs text-muted-foreground">{i18n.t('security.twoFactorDescription')}</p>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="flex items-center gap-2">
|
|
178
|
+
<Badge variant="secondary" class="text-[10px]">{twoFactor ? i18n.t('security.twoFactorActive') : i18n.t('security.twoFactorInactive')}</Badge>
|
|
179
|
+
<Switch id="sb-2fa" bind:checked={twoFactor} />
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</Card.CardContent>
|
|
183
|
+
</Card.Card>
|
|
57
184
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
</
|
|
63
|
-
<
|
|
64
|
-
|
|
185
|
+
<!-- Password -->
|
|
186
|
+
<Card.Card class="border-border/60 scroll-mt-6" id="settings-section-password">
|
|
187
|
+
<Card.CardHeader>
|
|
188
|
+
<Card.CardTitle class="text-base">{i18n.t('account.password')}</Card.CardTitle>
|
|
189
|
+
</Card.CardHeader>
|
|
190
|
+
<Card.CardContent>
|
|
191
|
+
<div class="grid gap-4 sm:grid-cols-3">
|
|
192
|
+
<div class="space-y-2">
|
|
193
|
+
<Label for="sb-current-pw">{i18n.t('profile.currentPassword')}</Label>
|
|
194
|
+
<Input id="sb-current-pw" type="password" />
|
|
195
|
+
</div>
|
|
196
|
+
<div class="space-y-2">
|
|
197
|
+
<Label for="sb-new-pw">{i18n.t('profile.newPassword')}</Label>
|
|
198
|
+
<Input id="sb-new-pw" type="password" />
|
|
199
|
+
</div>
|
|
200
|
+
<div class="space-y-2">
|
|
201
|
+
<Label for="sb-confirm-pw">{i18n.t('auth.confirmPassword')}</Label>
|
|
202
|
+
<Input id="sb-confirm-pw" type="password" />
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</Card.CardContent>
|
|
206
|
+
</Card.Card>
|
|
65
207
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<
|
|
70
|
-
</
|
|
71
|
-
<
|
|
72
|
-
|
|
208
|
+
<!-- SSO -->
|
|
209
|
+
<Card.Card class="border-border/60 scroll-mt-6" id="settings-section-sso">
|
|
210
|
+
<Card.CardHeader>
|
|
211
|
+
<Card.CardTitle class="text-base">{i18n.t('account.singleSignOn')}</Card.CardTitle>
|
|
212
|
+
</Card.CardHeader>
|
|
213
|
+
<Card.CardContent>
|
|
214
|
+
<div class="grid gap-4 sm:grid-cols-2">
|
|
215
|
+
<div class="space-y-2">
|
|
216
|
+
<Label for="sb-client-id">Client ID</Label>
|
|
217
|
+
<Input id="sb-client-id" placeholder="xxxxxxxx-xxxx-xxxx" />
|
|
218
|
+
</div>
|
|
219
|
+
<div class="space-y-2">
|
|
220
|
+
<Label for="sb-client-secret">Client Secret</Label>
|
|
221
|
+
<Input id="sb-client-secret" type="password" placeholder="••••••••" />
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</Card.CardContent>
|
|
225
|
+
</Card.Card>
|
|
73
226
|
|
|
74
|
-
|
|
227
|
+
<!-- Preferences -->
|
|
228
|
+
<Card.Card class="border-border/60 scroll-mt-6" id="settings-section-preferences">
|
|
229
|
+
<Card.CardHeader>
|
|
230
|
+
<Card.CardTitle class="text-base">{i18n.t('account.preferences')}</Card.CardTitle>
|
|
231
|
+
</Card.CardHeader>
|
|
232
|
+
<Card.CardContent>
|
|
233
|
+
<div class="grid gap-4 sm:grid-cols-3">
|
|
234
|
+
<div class="space-y-2">
|
|
235
|
+
<Label for="sb-language">{i18n.t('account.language')}</Label>
|
|
236
|
+
<select id="sb-language" bind:value={language} class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring">
|
|
237
|
+
<option value="en">English</option>
|
|
238
|
+
<option value="zh-CN">中文(简体)</option>
|
|
239
|
+
</select>
|
|
240
|
+
</div>
|
|
241
|
+
<div class="space-y-2">
|
|
242
|
+
<Label for="sb-timezone">{i18n.t('account.timezone')}</Label>
|
|
243
|
+
<select id="sb-timezone" bind:value={timezone} class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring">
|
|
244
|
+
<option value="Asia/Shanghai">Asia/Shanghai (GMT+8)</option>
|
|
245
|
+
<option value="America/Los_Angeles">America/Los_Angeles (GMT-7)</option>
|
|
246
|
+
<option value="Europe/Berlin">Europe/Berlin (GMT+2)</option>
|
|
247
|
+
</select>
|
|
248
|
+
</div>
|
|
249
|
+
<div class="space-y-2">
|
|
250
|
+
<Label for="sb-currency">{i18n.t('account.currency')}</Label>
|
|
251
|
+
<select id="sb-currency" bind:value={currency} class="flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring">
|
|
252
|
+
<option value="CNY">CNY (¥)</option>
|
|
253
|
+
<option value="USD">USD ($)</option>
|
|
254
|
+
<option value="EUR">EUR (€)</option>
|
|
255
|
+
</select>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</Card.CardContent>
|
|
259
|
+
</Card.Card>
|
|
75
260
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
261
|
+
<!-- Address -->
|
|
262
|
+
<Card.Card class="border-border/60 scroll-mt-6" id="settings-section-address">
|
|
263
|
+
<Card.CardHeader>
|
|
264
|
+
<Card.CardTitle class="text-base">{i18n.t('account.address')}</Card.CardTitle>
|
|
265
|
+
</Card.CardHeader>
|
|
266
|
+
<Card.CardContent>
|
|
267
|
+
<div class="grid gap-4 sm:grid-cols-2">
|
|
268
|
+
<div class="space-y-2">
|
|
269
|
+
<Label for="sb-country">{i18n.t('account.country')}</Label>
|
|
270
|
+
<Input id="sb-country" value="China" />
|
|
271
|
+
</div>
|
|
272
|
+
<div class="space-y-2">
|
|
273
|
+
<Label for="sb-city">{i18n.t('account.city')}</Label>
|
|
274
|
+
<Input id="sb-city" value="Shanghai" />
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
</Card.CardContent>
|
|
278
|
+
</Card.Card>
|
|
91
279
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
280
|
+
<!-- Manage API -->
|
|
281
|
+
<Card.Card class="border-border/60 scroll-mt-6" id="settings-section-api">
|
|
282
|
+
<Card.CardHeader>
|
|
283
|
+
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
284
|
+
<Key class="h-4 w-4 text-muted-foreground" />{i18n.t('account.manageApi')}
|
|
285
|
+
</Card.CardTitle>
|
|
286
|
+
</Card.CardHeader>
|
|
287
|
+
<Card.CardContent>
|
|
288
|
+
<div class="flex items-center gap-2">
|
|
289
|
+
<Input readonly value={apiKey} class="h-9 font-mono text-xs" />
|
|
290
|
+
<Button size="sm" variant="outline" onclick={copyApiKey}>
|
|
291
|
+
{#if copied}<Check class="h-4 w-4 text-green-500" />{:else}<Copy class="h-4 w-4" />{/if}
|
|
292
|
+
</Button>
|
|
293
|
+
<Button size="sm" variant="ghost">{i18n.t('api.regenerate')}</Button>
|
|
294
|
+
</div>
|
|
295
|
+
</Card.CardContent>
|
|
296
|
+
</Card.Card>
|
|
297
|
+
|
|
298
|
+
<!-- Delete Account -->
|
|
299
|
+
<Card.Card class="border-destructive/30 scroll-mt-6" id="settings-section-delete">
|
|
300
|
+
<Card.CardHeader>
|
|
301
|
+
<Card.CardTitle class="text-base text-destructive">{i18n.t('account.deleteAccount')}</Card.CardTitle>
|
|
302
|
+
<Card.CardDescription>{i18n.t('account.deleteAccountDescription')}</Card.CardDescription>
|
|
303
|
+
</Card.CardHeader>
|
|
304
|
+
<Card.CardContent>
|
|
305
|
+
<div class="flex justify-end">
|
|
306
|
+
<Button variant="destructive" size="sm">{i18n.t('account.deleteAccount')}</Button>
|
|
307
|
+
</div>
|
|
308
|
+
</Card.CardContent>
|
|
309
|
+
</Card.Card>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
100
312
|
</div>
|