@svadmin/ui 0.38.7 → 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/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 +13 -13
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
2
|
const loadPublicProfilePage = () => import('./profile/PublicProfilePage.svelte');
|
|
3
3
|
const loadGetStartedPage = () => import('./account/GetStartedPage.svelte');
|
|
4
|
+
const loadUserProfilePage = () => import('./account/UserProfilePage.svelte');
|
|
4
5
|
const loadCompanyProfilePage = () => import('./account/CompanyProfilePage.svelte');
|
|
5
6
|
const loadSettingsPlainPage = () => import('./account/SettingsPlainPage.svelte');
|
|
6
7
|
const loadSettingsSidebarPage = () => import('./account/SettingsSidebarPage.svelte');
|
|
@@ -319,11 +320,11 @@
|
|
|
319
320
|
{:else if route === '/public-profile/teams'}
|
|
320
321
|
<LazyPage loader={loadPublicProfilePage} props={{ variant: 'default', initialTab: 'teams' }} />
|
|
321
322
|
{:else if route === '/public-profile/profiles/:variant'}
|
|
322
|
-
<LazyPage loader={loadPublicProfilePage} props={{ variant: (params.variant === 'company' || params.variant === 'gamer' || params.variant === 'default' ? params.variant : 'default') as 'company' | 'gamer' | 'default' }} />
|
|
323
|
+
<LazyPage loader={loadPublicProfilePage} props={{ variant: (params.variant === 'company' || params.variant === 'gamer' || params.variant === 'default' ? params.variant : 'default') as 'company' | 'gamer' | 'default', showSections: true }} />
|
|
323
324
|
{:else if route === '/account/get-started' || route === '/account/home/get-started'}
|
|
324
325
|
<LazyPage loader={loadGetStartedPage} props={{}} />
|
|
325
326
|
{:else if route === '/account/home/user-profile'}
|
|
326
|
-
<
|
|
327
|
+
<LazyPage loader={loadUserProfilePage} props={{}} />
|
|
327
328
|
{:else if route === '/account/company-profile' || route === '/account/home/company-profile'}
|
|
328
329
|
<LazyPage loader={loadCompanyProfilePage} props={{}} />
|
|
329
330
|
{:else if route === '/account/settings-plain' || route === '/account/home/settings-plain'}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
3
3
|
|
|
4
|
-
import { AlertCircle, Calendar, Check, Copy, Key, Plus, ShieldCheck, Trash2 } from '@lucide/svelte';
|
|
4
|
+
import { AlertCircle, Calendar, Check, Copy, Key, Plus, ShieldCheck, Trash2, Webhook } from '@lucide/svelte';
|
|
5
5
|
import * as Card from './ui/card/index.js';
|
|
6
6
|
import { Badge } from './ui/badge/index.js';
|
|
7
7
|
import { Button } from './ui/button/index.js';
|
|
@@ -30,6 +30,35 @@
|
|
|
30
30
|
let showCreatedToken = $state(false);
|
|
31
31
|
let copiedKeyId = $state<string | null>(null);
|
|
32
32
|
|
|
33
|
+
interface WebhookEntry {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
url: string;
|
|
37
|
+
eventType: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let webhooks = $state<WebhookEntry[]>([
|
|
41
|
+
{ id: 'wh-1', name: 'Order Events', url: 'https://api.acme.com/hooks/orders', eventType: 'resource.updated' },
|
|
42
|
+
]);
|
|
43
|
+
let newWebhookName = $state('');
|
|
44
|
+
let newWebhookUrl = $state('');
|
|
45
|
+
let newWebhookEvent = $state('resource.created');
|
|
46
|
+
|
|
47
|
+
function handleAddWebhook(event: SubmitEvent) {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
if (!newWebhookName.trim() || !newWebhookUrl.trim()) return;
|
|
50
|
+
webhooks = [
|
|
51
|
+
...webhooks,
|
|
52
|
+
{ id: Date.now().toString(), name: newWebhookName.trim(), url: newWebhookUrl.trim(), eventType: newWebhookEvent },
|
|
53
|
+
];
|
|
54
|
+
newWebhookName = '';
|
|
55
|
+
newWebhookUrl = '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function deleteWebhook(id: string) {
|
|
59
|
+
webhooks = webhooks.filter(w => w.id !== id);
|
|
60
|
+
}
|
|
61
|
+
|
|
33
62
|
function generateDemoToken() {
|
|
34
63
|
const randomHex = Array.from({ length: 24 }, () => Math.floor(Math.random() * 16).toString(16)).join('');
|
|
35
64
|
return `sv_demo_${randomHex}`;
|
|
@@ -190,5 +219,61 @@
|
|
|
190
219
|
</div>
|
|
191
220
|
</Card.CardContent>
|
|
192
221
|
</Card.Card>
|
|
222
|
+
|
|
223
|
+
<!-- Webhooks -->
|
|
224
|
+
<Card.Card>
|
|
225
|
+
<Card.CardHeader>
|
|
226
|
+
<Card.CardTitle class="flex items-center gap-2">
|
|
227
|
+
<Webhook class="h-4 w-4 text-muted-foreground" />
|
|
228
|
+
{i18n.t('api.webhooks')}
|
|
229
|
+
</Card.CardTitle>
|
|
230
|
+
<Card.CardDescription>{i18n.t('api.webhooksDesc')}</Card.CardDescription>
|
|
231
|
+
</Card.CardHeader>
|
|
232
|
+
<Card.CardContent class="space-y-4">
|
|
233
|
+
<form class="grid gap-3 sm:grid-cols-[1fr_1fr_180px_auto]" onsubmit={handleAddWebhook}>
|
|
234
|
+
<div class="space-y-1.5">
|
|
235
|
+
<Label for="webhook-name">{i18n.t('api.webhookName')}</Label>
|
|
236
|
+
<Input id="webhook-name" bind:value={newWebhookName} placeholder="Order Events" />
|
|
237
|
+
</div>
|
|
238
|
+
<div class="space-y-1.5">
|
|
239
|
+
<Label for="webhook-url">{i18n.t('api.webhookUrl')}</Label>
|
|
240
|
+
<Input id="webhook-url" type="url" bind:value={newWebhookUrl} placeholder="https://api.example.com/hooks" />
|
|
241
|
+
</div>
|
|
242
|
+
<div class="space-y-1.5">
|
|
243
|
+
<Label for="webhook-event">{i18n.t('api.eventType')}</Label>
|
|
244
|
+
<select id="webhook-event" bind:value={newWebhookEvent} 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">
|
|
245
|
+
<option value="resource.created">resource.created</option>
|
|
246
|
+
<option value="resource.updated">resource.updated</option>
|
|
247
|
+
<option value="resource.deleted">resource.deleted</option>
|
|
248
|
+
<option value="auth.login">auth.login</option>
|
|
249
|
+
</select>
|
|
250
|
+
</div>
|
|
251
|
+
<div class="flex items-end">
|
|
252
|
+
<Button type="submit" size="sm" disabled={!newWebhookName.trim() || !newWebhookUrl.trim()}>
|
|
253
|
+
<Plus class="h-3.5 w-3.5 mr-1" />{i18n.t('api.addWebhook')}
|
|
254
|
+
</Button>
|
|
255
|
+
</div>
|
|
256
|
+
</form>
|
|
257
|
+
|
|
258
|
+
<div class="divide-y border-t">
|
|
259
|
+
{#each webhooks as hook (hook.id)}
|
|
260
|
+
<div class="flex items-center justify-between gap-4 py-3">
|
|
261
|
+
<div class="min-w-0">
|
|
262
|
+
<p class="truncate text-sm font-medium text-foreground">{hook.name}</p>
|
|
263
|
+
<p class="truncate font-mono text-xs text-muted-foreground">{hook.url}</p>
|
|
264
|
+
</div>
|
|
265
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
266
|
+
<Badge variant="secondary" class="px-2 py-0 text-[10px]">{hook.eventType}</Badge>
|
|
267
|
+
<Button variant="ghost" size="icon-sm" onclick={() => deleteWebhook(hook.id)} title={i18n.t('common.delete')}>
|
|
268
|
+
<Trash2 class="h-4 w-4" />
|
|
269
|
+
</Button>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
{:else}
|
|
273
|
+
<p class="py-4 text-center text-sm text-muted-foreground">{i18n.t('api.noWebhooks')}</p>
|
|
274
|
+
{/each}
|
|
275
|
+
</div>
|
|
276
|
+
</Card.CardContent>
|
|
277
|
+
</Card.Card>
|
|
193
278
|
</div>
|
|
194
279
|
</div>
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { Input } from './ui/input/index.js';
|
|
7
7
|
import { Label } from './ui/label/index.js';
|
|
8
8
|
import * as Alert from './ui/alert/index.js';
|
|
9
|
-
import
|
|
9
|
+
import SvadminLogo from './SvadminLogo.svelte';
|
|
10
|
+
import { User, ArrowLeft, CheckCircle, Loader2, AlertCircle } from '@lucide/svelte';
|
|
10
11
|
|
|
11
12
|
const i18n = useTranslation();
|
|
12
13
|
|
|
@@ -49,9 +50,7 @@
|
|
|
49
50
|
|
|
50
51
|
<div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
|
|
51
52
|
<div class="flex items-center gap-3">
|
|
52
|
-
<
|
|
53
|
-
<Shield class="h-5 w-5 text-white" />
|
|
54
|
-
</div>
|
|
53
|
+
<SvadminLogo size={36} onDark />
|
|
55
54
|
<span class="text-lg font-semibold text-white tracking-tight">{title}</span>
|
|
56
55
|
</div>
|
|
57
56
|
|
|
@@ -75,10 +74,8 @@
|
|
|
75
74
|
<!-- Right form panel -->
|
|
76
75
|
<div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-8 lg:p-12">
|
|
77
76
|
<div class="w-full max-w-[400px]">
|
|
78
|
-
<div class="flex items-center gap-3
|
|
79
|
-
<
|
|
80
|
-
<Shield class="h-5 w-5" />
|
|
81
|
-
</div>
|
|
77
|
+
<div class="mb-8 flex items-center gap-3 lg:hidden">
|
|
78
|
+
<SvadminLogo size={36} />
|
|
82
79
|
<span class="text-lg font-semibold tracking-tight">{title}</span>
|
|
83
80
|
</div>
|
|
84
81
|
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
import { Label } from './ui/label/index.js';
|
|
7
7
|
import * as Alert from './ui/alert/index.js';
|
|
8
8
|
import PasswordInput from './PasswordInput.svelte';
|
|
9
|
+
import SvadminLogo from './SvadminLogo.svelte';
|
|
9
10
|
import { Separator } from './ui/separator/index.js';
|
|
10
|
-
import { User, Loader2, AlertCircle
|
|
11
|
+
import { User, Loader2, AlertCircle } from '@lucide/svelte';
|
|
11
12
|
|
|
12
13
|
const i18n = useTranslation();
|
|
13
14
|
|
|
@@ -77,9 +78,7 @@
|
|
|
77
78
|
|
|
78
79
|
<div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
|
|
79
80
|
<div class="flex items-center gap-3">
|
|
80
|
-
<
|
|
81
|
-
<Shield class="h-5 w-5 text-white" />
|
|
82
|
-
</div>
|
|
81
|
+
<SvadminLogo size={44} onDark />
|
|
83
82
|
<span class="text-xl font-semibold text-white tracking-tight">{title}</span>
|
|
84
83
|
</div>
|
|
85
84
|
|
|
@@ -102,10 +101,8 @@
|
|
|
102
101
|
|
|
103
102
|
<div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-10 lg:p-14">
|
|
104
103
|
<div class="w-full max-w-[460px] space-y-8">
|
|
105
|
-
<div class="flex items-center gap-3
|
|
106
|
-
<
|
|
107
|
-
<Shield class="h-5 w-5" />
|
|
108
|
-
</div>
|
|
104
|
+
<div class="mb-8 flex items-center gap-3 lg:hidden">
|
|
105
|
+
<SvadminLogo size={44} />
|
|
109
106
|
<span class="text-xl font-semibold tracking-tight text-foreground">{title}</span>
|
|
110
107
|
</div>
|
|
111
108
|
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { Label } from './ui/label/index.js';
|
|
7
7
|
import * as Alert from './ui/alert/index.js';
|
|
8
8
|
import PasswordInput from './PasswordInput.svelte';
|
|
9
|
-
import
|
|
9
|
+
import SvadminLogo from './SvadminLogo.svelte';
|
|
10
|
+
import { User, Loader2, AlertCircle } from '@lucide/svelte';
|
|
10
11
|
|
|
11
12
|
const i18n = useTranslation();
|
|
12
13
|
|
|
@@ -53,9 +54,7 @@
|
|
|
53
54
|
|
|
54
55
|
<div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
|
|
55
56
|
<div class="flex items-center gap-3">
|
|
56
|
-
<
|
|
57
|
-
<Shield class="h-5 w-5 text-white" />
|
|
58
|
-
</div>
|
|
57
|
+
<SvadminLogo size={36} onDark />
|
|
59
58
|
<span class="text-lg font-semibold text-white tracking-tight">{title}</span>
|
|
60
59
|
</div>
|
|
61
60
|
|
|
@@ -79,10 +78,8 @@
|
|
|
79
78
|
<!-- Right form panel -->
|
|
80
79
|
<div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-8 lg:p-12">
|
|
81
80
|
<div class="w-full max-w-[400px]">
|
|
82
|
-
<div class="flex items-center gap-3
|
|
83
|
-
<
|
|
84
|
-
<Shield class="h-5 w-5" />
|
|
85
|
-
</div>
|
|
81
|
+
<div class="mb-8 flex items-center gap-3 lg:hidden">
|
|
82
|
+
<SvadminLogo size={36} />
|
|
86
83
|
<span class="text-lg font-semibold tracking-tight">{title}</span>
|
|
87
84
|
</div>
|
|
88
85
|
|
|
@@ -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>
|