@svadmin/ui 0.45.0 → 0.46.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/AuthPageShell.svelte +32 -0
- package/dist/components/AuthPageShell.svelte.d.ts +11 -0
- package/dist/components/ForgotPasswordPage.svelte +4 -68
- package/dist/components/LoginPage.svelte +33 -124
- package/dist/components/PermissionMatrix.svelte +5 -5
- package/dist/components/RegisterPage.svelte +7 -62
- package/dist/components/SvadminLogo.svelte +1 -1
- package/dist/components/UpdatePasswordPage.svelte +3 -55
- package/dist/components/account/CompanyProfilePage.svelte +11 -5
- package/dist/components/account/SettingsEnterprisePage.svelte +25 -4
- package/dist/components/account/SettingsPlainPage.svelte +25 -3
- package/dist/components/account/SettingsSidebarPage.svelte +31 -2
- package/dist/components/content/ApiKeyList.svelte +13 -4
- package/dist/components/content/ApiKeyList.svelte.d.ts +7 -0
- package/dist/components/content/DataState.svelte +25 -9
- package/dist/components/content/DataState.svelte.d.ts +2 -0
- package/dist/components/content/FileList.svelte +12 -4
- package/dist/components/content/FileList.svelte.d.ts +9 -0
- package/dist/components/content/FilterToolbar.svelte +10 -2
- package/dist/components/content/FilterToolbar.svelte.d.ts +1 -0
- package/dist/components/content/MemberList.svelte +13 -5
- package/dist/components/content/MemberList.svelte.d.ts +9 -0
- package/dist/components/content/MetricBlock.svelte +19 -2
- package/dist/components/content/MetricBlock.svelte.d.ts +2 -0
- package/dist/components/content/NetworkTable.svelte +9 -3
- package/dist/components/content/NetworkTable.svelte.d.ts +9 -0
- package/dist/components/content/NetworkUserCard.svelte +47 -7
- package/dist/components/content/NetworkUserCard.svelte.d.ts +13 -0
- package/dist/components/content/SectionHeader.svelte +3 -2
- package/dist/components/content/SectionHeader.svelte.d.ts +1 -0
- package/dist/components/content/SecurityEventTable.svelte +14 -5
- package/dist/components/content/SecurityEventTable.svelte.d.ts +9 -0
- package/dist/components/content/SettingsFieldRow.svelte +28 -0
- package/dist/components/content/SettingsFieldRow.svelte.d.ts +11 -0
- package/dist/components/content/SettingsGroup.svelte +33 -0
- package/dist/components/content/SettingsGroup.svelte.d.ts +12 -0
- package/dist/components/content/WorkspaceLayout.svelte +28 -0
- package/dist/components/content/WorkspaceLayout.svelte.d.ts +11 -0
- package/dist/components/content/index.d.ts +4 -1
- package/dist/components/content/index.js +3 -0
- package/dist/components/network/UserCardsNFTPage.svelte +25 -6
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import SvadminLogo from './SvadminLogo.svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
brand: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
footer?: Snippet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { brand, title, description, children, footer }: Props = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<div class="grid min-h-screen bg-muted/20 lg:grid-cols-[minmax(18rem,0.72fr)_minmax(0,1.28fr)]" data-svadmin-auth-page>
|
|
17
|
+
<aside class="hidden border-r border-border bg-card p-10 lg:flex lg:flex-col lg:justify-between xl:p-12">
|
|
18
|
+
<div class="flex items-center gap-3"><SvadminLogo size={40} /><span class="text-lg font-semibold text-foreground">{brand}</span></div>
|
|
19
|
+
<div class="max-w-sm space-y-3"><h2 class="text-2xl font-semibold leading-tight text-foreground">{title}</h2>{#if description}<p class="text-sm leading-6 text-muted-foreground">{description}</p>{/if}</div>
|
|
20
|
+
<p class="text-xs text-muted-foreground">{brand}</p>
|
|
21
|
+
</aside>
|
|
22
|
+
<main class="flex items-center justify-center p-5 sm:p-8 lg:p-12">
|
|
23
|
+
<div class="w-full max-w-md space-y-5">
|
|
24
|
+
<div class="flex items-center gap-3 lg:hidden"><SvadminLogo size={36} /><span class="text-base font-semibold text-foreground">{brand}</span></div>
|
|
25
|
+
<section class="rounded-lg border border-border bg-card p-5 shadow-sm sm:p-7">
|
|
26
|
+
<header class="mb-6 space-y-2"><h1 class="text-xl font-semibold leading-tight text-foreground">{title}</h1>{#if description}<p class="text-sm leading-6 text-muted-foreground">{description}</p>{/if}</header>
|
|
27
|
+
{@render children()}
|
|
28
|
+
</section>
|
|
29
|
+
{#if footer}<div class="text-center">{@render footer()}</div>{/if}
|
|
30
|
+
</div>
|
|
31
|
+
</main>
|
|
32
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
brand: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
footer?: Snippet;
|
|
8
|
+
}
|
|
9
|
+
declare const AuthPageShell: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type AuthPageShell = ReturnType<typeof AuthPageShell>;
|
|
11
|
+
export default AuthPageShell;
|
|
@@ -6,7 +6,7 @@
|
|
|
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 AuthPageShell from './AuthPageShell.svelte';
|
|
10
10
|
import { User, ArrowLeft, Loader2, AlertCircle } from '@lucide/svelte';
|
|
11
11
|
|
|
12
12
|
const i18n = useTranslation();
|
|
@@ -37,65 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
</script>
|
|
39
39
|
|
|
40
|
-
<
|
|
41
|
-
<!-- Left brand panel -->
|
|
42
|
-
<div class="hidden lg:flex lg:w-[480px] xl:w-[520px] relative overflow-hidden bg-gradient-to-br from-primary/90 via-primary to-primary/80 shrink-0">
|
|
43
|
-
<div class="absolute inset-0">
|
|
44
|
-
<div class="absolute top-[12%] left-[8%] w-28 h-28 rounded-3xl border border-white/10 bg-white/5 rotate-12 backdrop-blur-sm"></div>
|
|
45
|
-
<div class="absolute top-[55%] left-[55%] w-40 h-40 rounded-full border border-white/10 bg-white/5 -rotate-6 backdrop-blur-sm"></div>
|
|
46
|
-
<div class="absolute bottom-[15%] left-[15%] w-24 h-24 rounded-2xl border border-white/10 bg-white/5 -rotate-12 backdrop-blur-sm"></div>
|
|
47
|
-
<div class="absolute top-[30%] right-[10%] w-16 h-16 rounded-xl border border-white/10 bg-white/5 rotate-45 backdrop-blur-sm"></div>
|
|
48
|
-
<div class="absolute inset-0 opacity-[0.03]" style="background-image: radial-gradient(circle, white 1px, transparent 1px); background-size: 24px 24px;"></div>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
<div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
|
|
52
|
-
<div class="flex items-center gap-3">
|
|
53
|
-
<SvadminLogo size={36} onDark />
|
|
54
|
-
<span class="text-lg font-semibold text-white tracking-tight">{title}</span>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<div class="space-y-4">
|
|
58
|
-
<h2 class="text-3xl xl:text-4xl font-bold text-white leading-tight">
|
|
59
|
-
{i18n.t('auth.forgotPassword')}
|
|
60
|
-
</h2>
|
|
61
|
-
<p class="text-base text-white/70 leading-relaxed max-w-[320px]">
|
|
62
|
-
{i18n.t('auth.forgotPasswordDescription')}
|
|
63
|
-
</p>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<div class="flex items-center gap-3">
|
|
67
|
-
<div class="h-1 w-8 rounded-full bg-white/30"></div>
|
|
68
|
-
<div class="h-1 w-4 rounded-full bg-white/15"></div>
|
|
69
|
-
<div class="h-1 w-2 rounded-full bg-white/10"></div>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<!-- Right form panel -->
|
|
75
|
-
<div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-8 lg:p-12">
|
|
76
|
-
<div class="w-full max-w-[400px]">
|
|
77
|
-
<div class="mb-8 flex items-center gap-3 lg:hidden">
|
|
78
|
-
<SvadminLogo size={36} />
|
|
79
|
-
<span class="text-lg font-semibold tracking-tight">{title}</span>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
<div class="space-y-1 mb-8">
|
|
83
|
-
<h1 class="text-2xl font-semibold tracking-tight">
|
|
84
|
-
{#if sent}
|
|
85
|
-
{i18n.t('auth.resetLinkSentTitle')}
|
|
86
|
-
{:else}
|
|
87
|
-
{i18n.t('auth.forgotPassword')}
|
|
88
|
-
{/if}
|
|
89
|
-
</h1>
|
|
90
|
-
<p class="text-sm text-muted-foreground">
|
|
91
|
-
{#if sent}
|
|
92
|
-
{i18n.t('auth.resetLinkSentDescription')}
|
|
93
|
-
{:else}
|
|
94
|
-
{i18n.t('auth.forgotPasswordDescription')}
|
|
95
|
-
{/if}
|
|
96
|
-
</p>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
40
|
+
<AuthPageShell brand={title} title={sent ? i18n.t('auth.resetLinkSentTitle') : i18n.t('auth.forgotPassword')} description={sent ? i18n.t('auth.resetLinkSentDescription') : i18n.t('auth.forgotPasswordDescription')}>
|
|
99
41
|
{#if sent}
|
|
100
42
|
<div class="space-y-4">
|
|
101
43
|
<Button variant="outline" class="w-full" onclick={() => adminContext.navigate('/login')}>
|
|
@@ -135,17 +77,11 @@
|
|
|
135
77
|
</Button>
|
|
136
78
|
</form>
|
|
137
79
|
|
|
138
|
-
<div class="flex items-center justify-center
|
|
80
|
+
<div class="mt-6 flex items-center justify-center border-t pt-5">
|
|
139
81
|
<Button variant="link" class="text-sm h-auto p-0 font-medium inline-flex items-center gap-1" onclick={() => adminContext.navigate('/login')}>
|
|
140
82
|
<ArrowLeft class="h-3 w-3" />
|
|
141
83
|
{i18n.t('auth.backToLogin')}
|
|
142
84
|
</Button>
|
|
143
85
|
</div>
|
|
144
86
|
{/if}
|
|
145
|
-
|
|
146
|
-
<p class="text-xs text-muted-foreground/60 mt-8 text-center">
|
|
147
|
-
Powered by {title}
|
|
148
|
-
</p>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
87
|
+
</AuthPageShell>
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import { Input } from './ui/input/index.js';
|
|
6
6
|
import { Label } from './ui/label/index.js';
|
|
7
7
|
import * as Alert from './ui/alert/index.js';
|
|
8
|
+
import AuthPageShell from './AuthPageShell.svelte';
|
|
8
9
|
import PasswordInput from './PasswordInput.svelte';
|
|
9
|
-
import SvadminLogo from './SvadminLogo.svelte';
|
|
10
10
|
import { Separator } from './ui/separator/index.js';
|
|
11
11
|
import { User, Loader2, AlertCircle } from '@lucide/svelte';
|
|
12
12
|
|
|
@@ -66,113 +66,67 @@
|
|
|
66
66
|
}
|
|
67
67
|
</script>
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<div class="
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<div class="absolute bottom-[14%] left-[13%] h-28 w-28 rounded-2xl border border-white/10 bg-white/5 -rotate-12 backdrop-blur-sm"></div>
|
|
75
|
-
<div class="absolute top-[30%] right-[10%] h-20 w-20 rounded-xl border border-white/10 bg-white/5 rotate-45 backdrop-blur-sm"></div>
|
|
76
|
-
<div class="absolute inset-0 opacity-[0.04]" style="background-image: radial-gradient(circle, white 1px, transparent 1px); background-size: 24px 24px;"></div>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
<div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
|
|
80
|
-
<div class="flex items-center gap-3">
|
|
81
|
-
<SvadminLogo size={44} onDark />
|
|
82
|
-
<span class="text-xl font-semibold text-white tracking-tight">{title}</span>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<div class="max-w-[360px] space-y-4">
|
|
86
|
-
<h2 class="text-4xl font-bold text-white leading-tight">
|
|
87
|
-
{i18n.t('auth.welcomeBack')}
|
|
88
|
-
</h2>
|
|
89
|
-
<p class="text-lg text-white/75 leading-relaxed">
|
|
90
|
-
{i18n.t('auth.welcomeMessage')}
|
|
91
|
-
</p>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
<div class="flex items-center gap-3">
|
|
95
|
-
<div class="h-1.5 w-10 rounded-full bg-white/40"></div>
|
|
96
|
-
<div class="h-1.5 w-5 rounded-full bg-white/20"></div>
|
|
97
|
-
<div class="h-1.5 w-2 rounded-full bg-white/15"></div>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
<div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-10 lg:p-14">
|
|
103
|
-
<div class="w-full max-w-[460px] space-y-8">
|
|
104
|
-
<div class="mb-8 flex items-center gap-3 lg:hidden">
|
|
105
|
-
<SvadminLogo size={44} />
|
|
106
|
-
<span class="text-xl font-semibold tracking-tight text-foreground">{title}</span>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<div class="space-y-2.5">
|
|
110
|
-
<h1 class="text-3xl font-semibold tracking-tight text-foreground">
|
|
111
|
-
{i18n.t('auth.welcomeBack')}
|
|
112
|
-
</h1>
|
|
113
|
-
<p class="text-base text-muted-foreground leading-relaxed">
|
|
114
|
-
{i18n.t('auth.welcomeMessage')}
|
|
115
|
-
</p>
|
|
116
|
-
</div>
|
|
69
|
+
{#snippet footer()}
|
|
70
|
+
{#if authProvider?.register}
|
|
71
|
+
<div class="flex items-center justify-center gap-1.5"><span class="text-sm text-muted-foreground">{i18n.t('auth.noAccount')}</span><Button variant="link" class="h-auto p-0 text-sm font-semibold" onclick={() => adminContext.navigate('/register')}>{i18n.t('auth.register')}</Button></div>
|
|
72
|
+
{/if}
|
|
73
|
+
{/snippet}
|
|
117
74
|
|
|
75
|
+
<AuthPageShell brand={title} title={i18n.t('auth.welcomeBack')} description={i18n.t('auth.welcomeMessage')} {footer}>
|
|
118
76
|
{#if loginHint || defaultIdentifier || defaultPassword}
|
|
119
|
-
<
|
|
77
|
+
<aside class="mb-5 rounded-md border border-border bg-muted/35 p-3">
|
|
120
78
|
{#if loginHint}
|
|
121
|
-
<p class="text-sm font-medium text-
|
|
79
|
+
<p class="text-sm font-medium text-foreground">{loginHint}</p>
|
|
122
80
|
{/if}
|
|
123
|
-
<
|
|
81
|
+
<dl class="mt-3 grid gap-2 sm:grid-cols-2">
|
|
124
82
|
{#if defaultIdentifier}
|
|
125
|
-
<div class="rounded-
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
</p>
|
|
129
|
-
<p class="mt-1 font-mono text-sm font-semibold text-foreground">{defaultIdentifier}</p>
|
|
83
|
+
<div class="rounded-md border border-border bg-background px-3 py-2">
|
|
84
|
+
<dt class="text-xs font-medium text-muted-foreground">{i18n.t('auth.usernameOrEmail')}</dt>
|
|
85
|
+
<dd class="mt-1 truncate font-mono text-sm text-foreground">{defaultIdentifier}</dd>
|
|
130
86
|
</div>
|
|
131
87
|
{/if}
|
|
132
88
|
{#if defaultPassword}
|
|
133
|
-
<div class="rounded-
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
</p>
|
|
137
|
-
<p class="mt-1 font-mono text-sm font-semibold text-foreground">{defaultPassword}</p>
|
|
89
|
+
<div class="rounded-md border border-border bg-background px-3 py-2">
|
|
90
|
+
<dt class="text-xs font-medium text-muted-foreground">{i18n.t('auth.password')}</dt>
|
|
91
|
+
<dd class="mt-1 font-mono text-sm text-foreground">{defaultPassword}</dd>
|
|
138
92
|
</div>
|
|
139
93
|
{/if}
|
|
140
|
-
</
|
|
141
|
-
</
|
|
94
|
+
</dl>
|
|
95
|
+
</aside>
|
|
142
96
|
{/if}
|
|
143
97
|
|
|
144
|
-
<form onsubmit={handleSubmit} class="space-y-
|
|
98
|
+
<form onsubmit={handleSubmit} class="space-y-5">
|
|
145
99
|
{#if error}
|
|
146
|
-
<Alert.Root variant="destructive"
|
|
100
|
+
<Alert.Root variant="destructive">
|
|
147
101
|
<AlertCircle class="h-4 w-4" />
|
|
148
102
|
<Alert.Description class="text-sm font-medium">{error}</Alert.Description>
|
|
149
103
|
</Alert.Root>
|
|
150
104
|
{/if}
|
|
151
105
|
|
|
152
|
-
<div class="space-y-2
|
|
153
|
-
<Label for="login-identifier"
|
|
106
|
+
<div class="space-y-2">
|
|
107
|
+
<Label for="login-identifier">
|
|
154
108
|
{i18n.t('auth.usernameOrEmail')}
|
|
155
109
|
</Label>
|
|
156
110
|
<div class="relative">
|
|
157
|
-
<User class="absolute left-
|
|
111
|
+
<User class="pointer-events-none absolute left-3 top-1/2 z-[1] h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
158
112
|
<Input
|
|
159
113
|
id="login-identifier"
|
|
160
114
|
type="text"
|
|
161
115
|
placeholder={i18n.t('auth.identifierPlaceholder')}
|
|
162
116
|
bind:value={identifier}
|
|
163
|
-
class="pl-
|
|
117
|
+
class="pl-9"
|
|
164
118
|
autocomplete="username"
|
|
165
119
|
/>
|
|
166
120
|
</div>
|
|
167
121
|
</div>
|
|
168
122
|
|
|
169
|
-
<div class="space-y-2
|
|
170
|
-
<div class="flex items-center justify-between
|
|
171
|
-
<Label for="login-password"
|
|
123
|
+
<div class="space-y-2">
|
|
124
|
+
<div class="flex items-center justify-between">
|
|
125
|
+
<Label for="login-password">
|
|
172
126
|
{i18n.t('auth.password')}
|
|
173
127
|
</Label>
|
|
174
128
|
{#if authProvider?.forgotPassword}
|
|
175
|
-
<Button variant="link" class="
|
|
129
|
+
<Button variant="link" class="h-auto p-0 text-xs font-medium" onclick={() => adminContext.navigate('/forgot-password')}>
|
|
176
130
|
{i18n.t('auth.forgotPasswordLink')}
|
|
177
131
|
</Button>
|
|
178
132
|
{/if}
|
|
@@ -185,7 +139,7 @@
|
|
|
185
139
|
/>
|
|
186
140
|
</div>
|
|
187
141
|
|
|
188
|
-
<Button type="submit" class="
|
|
142
|
+
<Button type="submit" class="h-10 w-full" disabled={login.isLoading}>
|
|
189
143
|
{#if login.isLoading}
|
|
190
144
|
<Loader2 class="h-5 w-5 animate-spin mr-2" />
|
|
191
145
|
{/if}
|
|
@@ -194,9 +148,9 @@
|
|
|
194
148
|
</form>
|
|
195
149
|
|
|
196
150
|
{#if socialProviders.length > 0}
|
|
197
|
-
<div class="relative my-
|
|
151
|
+
<div class="relative my-6">
|
|
198
152
|
<Separator />
|
|
199
|
-
<span class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-
|
|
153
|
+
<span class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-card px-4 text-xs font-medium text-muted-foreground">
|
|
200
154
|
{i18n.t('auth.orContinueWith')}
|
|
201
155
|
</span>
|
|
202
156
|
</div>
|
|
@@ -205,7 +159,7 @@
|
|
|
205
159
|
{#each socialProviders as provider (provider.name)}
|
|
206
160
|
<Button
|
|
207
161
|
variant="outline"
|
|
208
|
-
class="
|
|
162
|
+
class="h-10 w-full"
|
|
209
163
|
onclick={provider.onClick}
|
|
210
164
|
>
|
|
211
165
|
{#if provider.icon}
|
|
@@ -216,49 +170,4 @@
|
|
|
216
170
|
{/each}
|
|
217
171
|
</div>
|
|
218
172
|
{/if}
|
|
219
|
-
|
|
220
|
-
{#if authProvider?.register}
|
|
221
|
-
<div class="flex items-center justify-center gap-1.5 mt-8 pt-6 border-t border-border/60">
|
|
222
|
-
<span class="text-sm text-muted-foreground">{i18n.t('auth.noAccount')}</span>
|
|
223
|
-
<Button variant="link" class="text-sm h-auto p-0 font-semibold text-primary hover:underline" onclick={() => adminContext.navigate('/register')}>
|
|
224
|
-
{i18n.t('auth.register')}
|
|
225
|
-
</Button>
|
|
226
|
-
</div>
|
|
227
|
-
{/if}
|
|
228
|
-
|
|
229
|
-
<p class="text-xs text-muted-foreground/50 mt-8 text-center tracking-wide font-medium">
|
|
230
|
-
Powered by {title}
|
|
231
|
-
</p>
|
|
232
|
-
</div>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
|
|
236
|
-
<style>
|
|
237
|
-
.login-form :global(input[data-slot='input']) {
|
|
238
|
-
height: 3.5rem;
|
|
239
|
-
border-radius: 0.75rem;
|
|
240
|
-
border-color: color-mix(in oklch, var(--border) 88%, var(--foreground));
|
|
241
|
-
background-color: var(--background);
|
|
242
|
-
font-size: 0.975rem;
|
|
243
|
-
transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.login-form :global(input[data-slot='input']:focus-visible) {
|
|
247
|
-
border-color: var(--primary);
|
|
248
|
-
box-shadow: 0 0 0 3px color-mix(in oklch, var(--primary) 18%, transparent);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.login-form :global(.relative > svg:first-of-type) {
|
|
252
|
-
left: 1rem;
|
|
253
|
-
height: 1.25rem;
|
|
254
|
-
width: 1.25rem;
|
|
255
|
-
color: color-mix(in oklch, var(--muted-foreground) 82%, var(--foreground));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.login-form :global(.relative > button) {
|
|
259
|
-
right: 0.625rem;
|
|
260
|
-
height: 2.25rem;
|
|
261
|
-
width: 2.25rem;
|
|
262
|
-
border-radius: 0.5rem;
|
|
263
|
-
}
|
|
264
|
-
</style>
|
|
173
|
+
</AuthPageShell>
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
|
|
81
81
|
<div class="flex flex-col md:flex-row gap-6 flex-1 min-h-[500px]">
|
|
82
82
|
<!-- Left: Role Selector Sidebar -->
|
|
83
|
-
<div class="w-full md:w-64 flex-shrink-0 bg-card rounded-
|
|
84
|
-
<h2 class="text-sm font-semibold text-muted-
|
|
83
|
+
<div class="w-full md:w-64 flex-shrink-0 bg-card rounded-lg shadow-sm border border-border p-4 flex flex-col">
|
|
84
|
+
<h2 class="text-sm font-semibold text-muted-foregroundr mb-4 px-2">{i18n.t('permissions.roles') || 'Roles'}</h2>
|
|
85
85
|
<div class="space-y-1 flex-1 overflow-y-auto">
|
|
86
86
|
{#each roles as role, _i (_i)}
|
|
87
87
|
<button
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
class="w-full text-left px-3 py-2.5 rounded-lg text-sm transition-all duration-200 {selectedRole === role.code ? 'bg-primary/10 text-primary font-medium shadow-sm ring-1 ring-primary/20' : 'text-foreground hover:bg-muted'}"
|
|
90
90
|
>
|
|
91
91
|
{role.name}
|
|
92
|
-
<div class="text-[10px] mt-0.5 opacity-60 font-
|
|
92
|
+
<div class="text-[10px] mt-0.5 opacity-60 font-monoer">{role.code}</div>
|
|
93
93
|
</button>
|
|
94
94
|
{/each}
|
|
95
95
|
{#if roles.length === 0}
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
</div>
|
|
107
107
|
|
|
108
108
|
<!-- Right: Matrix Grid -->
|
|
109
|
-
<div class="flex-1 bg-card rounded-
|
|
109
|
+
<div class="flex-1 bg-card rounded-lg shadow-sm border border-border overflow-hidden flex flex-col">
|
|
110
110
|
<!-- Matrix Header -->
|
|
111
111
|
<div class="p-5 border-b border-border bg-muted/30 flex items-center justify-between">
|
|
112
112
|
<div class="flex items-center gap-4">
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
<!-- Section Grouping Header -->
|
|
157
157
|
{#if resource.section && (i === 0 || resource.section !== resources[i-1].section)}
|
|
158
158
|
<tr class="bg-primary/5">
|
|
159
|
-
<td colspan="{actions.length + 1}" class="px-4 py-2 text-xs font-bold text-
|
|
159
|
+
<td colspan="{actions.length + 1}" class="px-4 py-2 text-xs font-bold text-primaryr">{resource.section}</td>
|
|
160
160
|
</tr>
|
|
161
161
|
{/if}
|
|
162
162
|
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import { Input } from './ui/input/index.js';
|
|
6
6
|
import { Label } from './ui/label/index.js';
|
|
7
7
|
import * as Alert from './ui/alert/index.js';
|
|
8
|
+
import AuthPageShell from './AuthPageShell.svelte';
|
|
8
9
|
import PasswordInput from './PasswordInput.svelte';
|
|
9
|
-
import SvadminLogo from './SvadminLogo.svelte';
|
|
10
10
|
import { User, Loader2, AlertCircle } from '@lucide/svelte';
|
|
11
11
|
|
|
12
12
|
const i18n = useTranslation();
|
|
@@ -41,53 +41,11 @@
|
|
|
41
41
|
}
|
|
42
42
|
</script>
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<div class="absolute inset-0">
|
|
48
|
-
<div class="absolute top-[12%] left-[8%] w-28 h-28 rounded-3xl border border-white/10 bg-white/5 rotate-12 backdrop-blur-sm"></div>
|
|
49
|
-
<div class="absolute top-[55%] left-[55%] w-40 h-40 rounded-full border border-white/10 bg-white/5 -rotate-6 backdrop-blur-sm"></div>
|
|
50
|
-
<div class="absolute bottom-[15%] left-[15%] w-24 h-24 rounded-2xl border border-white/10 bg-white/5 -rotate-12 backdrop-blur-sm"></div>
|
|
51
|
-
<div class="absolute top-[30%] right-[10%] w-16 h-16 rounded-xl border border-white/10 bg-white/5 rotate-45 backdrop-blur-sm"></div>
|
|
52
|
-
<div class="absolute inset-0 opacity-[0.03]" style="background-image: radial-gradient(circle, white 1px, transparent 1px); background-size: 24px 24px;"></div>
|
|
53
|
-
</div>
|
|
54
|
-
|
|
55
|
-
<div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
|
|
56
|
-
<div class="flex items-center gap-3">
|
|
57
|
-
<SvadminLogo size={36} onDark />
|
|
58
|
-
<span class="text-lg font-semibold text-white tracking-tight">{title}</span>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
<div class="space-y-4">
|
|
62
|
-
<h2 class="text-3xl xl:text-4xl font-bold text-white leading-tight">
|
|
63
|
-
{i18n.t('auth.createAccount')}
|
|
64
|
-
</h2>
|
|
65
|
-
<p class="text-base text-white/70 leading-relaxed max-w-[320px]">
|
|
66
|
-
{i18n.t('auth.createAccountMessage')}
|
|
67
|
-
</p>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<div class="flex items-center gap-3">
|
|
71
|
-
<div class="h-1 w-8 rounded-full bg-white/30"></div>
|
|
72
|
-
<div class="h-1 w-4 rounded-full bg-white/15"></div>
|
|
73
|
-
<div class="h-1 w-2 rounded-full bg-white/10"></div>
|
|
74
|
-
</div>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
|
|
78
|
-
<!-- Right form panel -->
|
|
79
|
-
<div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-8 lg:p-12">
|
|
80
|
-
<div class="w-full max-w-[400px]">
|
|
81
|
-
<div class="mb-8 flex items-center gap-3 lg:hidden">
|
|
82
|
-
<SvadminLogo size={36} />
|
|
83
|
-
<span class="text-lg font-semibold tracking-tight">{title}</span>
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<div class="space-y-1 mb-8">
|
|
87
|
-
<h1 class="text-2xl font-semibold tracking-tight">{i18n.t('auth.createAccount')}</h1>
|
|
88
|
-
<p class="text-sm text-muted-foreground">{i18n.t('auth.createAccountMessage')}</p>
|
|
89
|
-
</div>
|
|
44
|
+
{#snippet footer()}
|
|
45
|
+
<div class="flex items-center justify-center gap-1"><span class="text-sm text-muted-foreground">{i18n.t('auth.hasAccount')}</span><Button variant="link" class="h-auto p-0 text-sm font-medium" onclick={() => adminContext.navigate('/login')}>{i18n.t('auth.login')}</Button></div>
|
|
46
|
+
{/snippet}
|
|
90
47
|
|
|
48
|
+
<AuthPageShell brand={title} title={i18n.t('auth.createAccount')} description={i18n.t('auth.createAccountMessage')} {footer}>
|
|
91
49
|
<form onsubmit={handleSubmit} class="space-y-5">
|
|
92
50
|
{#if error}
|
|
93
51
|
<Alert.Root variant="destructive">
|
|
@@ -126,24 +84,11 @@
|
|
|
126
84
|
autocomplete="new-password"
|
|
127
85
|
/>
|
|
128
86
|
|
|
129
|
-
<Button type="submit" class="w-full
|
|
87
|
+
<Button type="submit" class="h-10 w-full" disabled={register.isLoading}>
|
|
130
88
|
{#if register.isLoading}
|
|
131
89
|
<Loader2 class="h-4 w-4 animate-spin mr-2" />
|
|
132
90
|
{/if}
|
|
133
91
|
{i18n.t('auth.registerButton')}
|
|
134
92
|
</Button>
|
|
135
93
|
</form>
|
|
136
|
-
|
|
137
|
-
<div class="flex items-center justify-center gap-1 mt-8 pt-6 border-t">
|
|
138
|
-
<span class="text-sm text-muted-foreground">{i18n.t('auth.hasAccount')}</span>
|
|
139
|
-
<Button variant="link" class="text-sm h-auto p-0 font-medium" onclick={() => adminContext.navigate('/login')}>
|
|
140
|
-
{i18n.t('auth.login')}
|
|
141
|
-
</Button>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
<p class="text-xs text-muted-foreground/60 mt-8 text-center">
|
|
145
|
-
Powered by {title}
|
|
146
|
-
</p>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
94
|
+
</AuthPageShell>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<span
|
|
12
12
|
aria-hidden="true"
|
|
13
|
-
class="inline-block shrink-0 rounded-lg bg-
|
|
13
|
+
class="inline-block shrink-0 rounded-lg bg-primary shadow-sm {onDark ? 'ring-1 ring-white/35' : ''}"
|
|
14
14
|
style:width={`${size}px`}
|
|
15
15
|
style:height={`${size}px`}
|
|
16
16
|
></span>
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { Button } from './ui/button/index.js';
|
|
6
6
|
import * as Alert from './ui/alert/index.js';
|
|
7
|
+
import AuthPageShell from './AuthPageShell.svelte';
|
|
7
8
|
import PasswordInput from './PasswordInput.svelte';
|
|
8
|
-
import SvadminLogo from './SvadminLogo.svelte';
|
|
9
9
|
import { Loader2, AlertCircle } from '@lucide/svelte';
|
|
10
10
|
|
|
11
11
|
const i18n = useTranslation();
|
|
@@ -35,53 +35,7 @@
|
|
|
35
35
|
}
|
|
36
36
|
</script>
|
|
37
37
|
|
|
38
|
-
<
|
|
39
|
-
<!-- Left brand panel -->
|
|
40
|
-
<div class="hidden lg:flex lg:w-[480px] xl:w-[520px] relative overflow-hidden bg-gradient-to-br from-primary/90 via-primary to-primary/80 shrink-0">
|
|
41
|
-
<div class="absolute inset-0">
|
|
42
|
-
<div class="absolute top-[12%] left-[8%] w-28 h-28 rounded-3xl border border-white/10 bg-white/5 rotate-12 backdrop-blur-sm"></div>
|
|
43
|
-
<div class="absolute top-[55%] left-[55%] w-40 h-40 rounded-full border border-white/10 bg-white/5 -rotate-6 backdrop-blur-sm"></div>
|
|
44
|
-
<div class="absolute bottom-[15%] left-[15%] w-24 h-24 rounded-2xl border border-white/10 bg-white/5 -rotate-12 backdrop-blur-sm"></div>
|
|
45
|
-
<div class="absolute top-[30%] right-[10%] w-16 h-16 rounded-xl border border-white/10 bg-white/5 rotate-45 backdrop-blur-sm"></div>
|
|
46
|
-
<div class="absolute inset-0 opacity-[0.03]" style="background-image: radial-gradient(circle, white 1px, transparent 1px); background-size: 24px 24px;"></div>
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
<div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
|
|
50
|
-
<div class="flex items-center gap-3">
|
|
51
|
-
<SvadminLogo size={36} onDark />
|
|
52
|
-
<span class="text-lg font-semibold text-white tracking-tight">{title}</span>
|
|
53
|
-
</div>
|
|
54
|
-
|
|
55
|
-
<div class="space-y-4">
|
|
56
|
-
<h2 class="text-3xl xl:text-4xl font-bold text-white leading-tight">
|
|
57
|
-
{i18n.t('auth.resetPassword')}
|
|
58
|
-
</h2>
|
|
59
|
-
<p class="text-base text-white/70 leading-relaxed max-w-[320px]">
|
|
60
|
-
{i18n.t('auth.resetPasswordDescription')}
|
|
61
|
-
</p>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
<div class="flex items-center gap-3">
|
|
65
|
-
<div class="h-1 w-8 rounded-full bg-white/30"></div>
|
|
66
|
-
<div class="h-1 w-4 rounded-full bg-white/15"></div>
|
|
67
|
-
<div class="h-1 w-2 rounded-full bg-white/10"></div>
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
<!-- Right form panel -->
|
|
73
|
-
<div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-8 lg:p-12">
|
|
74
|
-
<div class="w-full max-w-[400px]">
|
|
75
|
-
<div class="mb-8 flex items-center gap-3 lg:hidden">
|
|
76
|
-
<SvadminLogo size={36} />
|
|
77
|
-
<span class="text-lg font-semibold tracking-tight">{title}</span>
|
|
78
|
-
</div>
|
|
79
|
-
|
|
80
|
-
<div class="space-y-1 mb-8">
|
|
81
|
-
<h1 class="text-2xl font-semibold tracking-tight">{i18n.t('auth.resetPassword')}</h1>
|
|
82
|
-
<p class="text-sm text-muted-foreground">{i18n.t('auth.resetPasswordDescription')}</p>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
38
|
+
<AuthPageShell brand={title} title={i18n.t('auth.resetPassword')} description={i18n.t('auth.resetPasswordDescription')}>
|
|
85
39
|
<form onsubmit={handleSubmit} class="space-y-5">
|
|
86
40
|
{#if error}
|
|
87
41
|
<Alert.Root variant="destructive">
|
|
@@ -118,10 +72,4 @@
|
|
|
118
72
|
</Button>
|
|
119
73
|
</div>
|
|
120
74
|
</form>
|
|
121
|
-
|
|
122
|
-
<p class="text-xs text-muted-foreground/60 mt-8 text-center">
|
|
123
|
-
Powered by {title}
|
|
124
|
-
</p>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
75
|
+
</AuthPageShell>
|
|
@@ -4,11 +4,13 @@
|
|
|
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 { Textarea } from '../ui/textarea/index.js';
|
|
8
8
|
import ContentPageShell from '../content/ContentPageShell.svelte';
|
|
9
9
|
import ContentPageHeader from '../content/ContentPageHeader.svelte';
|
|
10
10
|
import DescriptionList from '../content/DescriptionList.svelte';
|
|
11
11
|
import MemberList from '../content/MemberList.svelte';
|
|
12
|
+
import SectionHeader from '../content/SectionHeader.svelte';
|
|
13
|
+
import SettingsGroup from '../content/SettingsGroup.svelte';
|
|
12
14
|
import type { MemberSummary } from '../content/MemberList.svelte';
|
|
13
15
|
const i18n = useTranslation();
|
|
14
16
|
let companyName = $state('Acme Corporation');
|
|
@@ -25,11 +27,15 @@
|
|
|
25
27
|
async function save() { saving = true; await new Promise((resolve) => setTimeout(resolve, 400)); saving = false; }
|
|
26
28
|
</script>
|
|
27
29
|
|
|
30
|
+
{#snippet headerActions()}
|
|
31
|
+
<Button onclick={save} disabled={saving} size="sm">{#if saving}<Loader2 class="size-3.5 animate-spin" />{/if}{i18n.t('common.save')}</Button>
|
|
32
|
+
{/snippet}
|
|
33
|
+
|
|
28
34
|
<ContentPageShell pageId="account-company-profile" width="wide">
|
|
29
|
-
<
|
|
35
|
+
<ContentPageHeader title={i18n.t('account.companyProfile')} description={i18n.t('account.companyProfileDescription')} actions={headerActions} />
|
|
30
36
|
<div class="grid gap-4 lg:grid-cols-[minmax(0,1fr)_20rem]">
|
|
31
|
-
<
|
|
32
|
-
<
|
|
37
|
+
<SettingsGroup title={i18n.t('account.basicSettings')} description={i18n.t('account.companyProfileDescription')} bodyClass="space-y-4"><div class="grid gap-4 sm:grid-cols-2"><div class="space-y-2"><Label for="company-name">{i18n.t('profile.companyName')}</Label><Input id="company-name" bind:value={companyName} /></div><div class="space-y-2"><Label for="company-industry">{i18n.t('profile.industry')}</Label><Input id="company-industry" bind:value={industry} /></div><div class="space-y-2 sm:col-span-2"><Label for="company-website">{i18n.t('profile.website')}</Label><Input id="company-website" bind:value={website} type="url" /></div></div><div class="space-y-2"><Label for="company-description">{i18n.t('profile.companyDescription')}</Label><Textarea id="company-description" bind:value={description} rows={4} /></div></SettingsGroup>
|
|
38
|
+
<SettingsGroup title={i18n.t('profile.companyName')} description={industry} bodyClass="space-y-4"><p class="text-lg font-semibold text-foreground">{companyName}</p><DescriptionList columns={1} items={[{ label: i18n.t('profile.website'), value: website, href: website }, { label: i18n.t('profile.employees'), value: '1,250' }, { label: i18n.t('profile.founded'), value: '2015' }]} /></SettingsGroup>
|
|
33
39
|
</div>
|
|
34
|
-
<section class="space-y-3"><
|
|
40
|
+
<section class="space-y-3"><SectionHeader title={i18n.t('profileSections.members')} /><MemberList {members} emptyTitle={i18n.t('common.noData')} emptyDescription={i18n.t('empty.description')} /></section>
|
|
35
41
|
</ContentPageShell>
|