@svadmin/ui 0.45.0 → 0.47.1
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/AboutSettings.svelte +21 -96
- package/dist/components/ApiSettings.svelte +54 -234
- package/dist/components/AppearanceSettings.svelte +36 -165
- package/dist/components/AuthPageShell.svelte +32 -0
- package/dist/components/AuthPageShell.svelte.d.ts +11 -0
- package/dist/components/AutoForm.svelte +21 -3
- package/dist/components/DetailDrawer.svelte +74 -0
- package/dist/components/DetailDrawer.svelte.d.ts +16 -0
- package/dist/components/FieldRenderer.svelte +70 -2
- package/dist/components/FieldRenderer.svelte.d.ts +2 -0
- package/dist/components/ForgotPasswordPage.svelte +4 -68
- package/dist/components/IntegrationsSettings.svelte +22 -61
- package/dist/components/LoginPage.svelte +33 -124
- package/dist/components/NotificationsSettings.svelte +22 -103
- package/dist/components/PermissionMatrix.svelte +18 -9
- package/dist/components/RegisterPage.svelte +7 -62
- package/dist/components/RowActions.svelte +154 -0
- package/dist/components/RowActions.svelte.d.ts +26 -0
- package/dist/components/SecuritySettings.svelte +18 -58
- package/dist/components/SettingsPage.svelte +12 -2
- package/dist/components/SvadminLogo.svelte +1 -1
- package/dist/components/UpdatePasswordPage.svelte +3 -55
- package/dist/components/account/CompanyProfilePage.svelte +19 -7
- 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/account/UserProfilePage.svelte +5 -4
- package/dist/components/content/ApiKeyList.svelte +40 -6
- package/dist/components/content/ApiKeyList.svelte.d.ts +10 -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 +31 -0
- package/dist/components/content/WorkspaceLayout.svelte.d.ts +12 -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 +9 -1
- package/dist/index.js +6 -0
- package/package.json +4 -1
|
@@ -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>
|
|
@@ -1,117 +1,36 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { useNotification } from '@svadmin/core';
|
|
2
3
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
3
|
-
import { Bell, Mail, Smartphone } from '@lucide/svelte';
|
|
4
|
-
import * as Card from './ui/card/index.js';
|
|
5
4
|
import { Button } from './ui/button/index.js';
|
|
6
|
-
import { Label } from './ui/label/index.js';
|
|
7
5
|
import { Switch } from './ui/switch/index.js';
|
|
6
|
+
import SettingsGroup from './content/SettingsGroup.svelte';
|
|
7
|
+
import SettingsFieldRow from './content/SettingsFieldRow.svelte';
|
|
8
8
|
|
|
9
9
|
const i18n = useTranslation();
|
|
10
|
-
|
|
10
|
+
const notification = useNotification();
|
|
11
11
|
let emailAlerts = $state({ security: true, activity: false, reports: true });
|
|
12
12
|
let pushAlerts = $state({ security: true, activity: true, reports: false });
|
|
13
13
|
let smsAlerts = $state({ security: true });
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function saveSettings() {
|
|
17
|
-
isSaved = true;
|
|
18
|
-
setTimeout(() => {
|
|
19
|
-
isSaved = false;
|
|
20
|
-
}, 3000);
|
|
21
|
-
}
|
|
14
|
+
function saveSettings() { notification.success(i18n.t('common.autoSaved'), 3000); }
|
|
22
15
|
</script>
|
|
23
16
|
|
|
24
17
|
<div class="space-y-6">
|
|
25
|
-
<div class="flex flex-col justify-between gap-3 sm:flex-row sm:items-start">
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<
|
|
18
|
+
<div class="flex flex-col justify-between gap-3 sm:flex-row sm:items-start"><div><h2 class="text-xl font-semibold text-foreground">{i18n.t('settings.notifications')}</h2><p class="mt-1 text-sm text-muted-foreground">{i18n.t('settings.notificationsDescription')}</p></div><Button onclick={saveSettings}>{i18n.t('common.save')}</Button></div>
|
|
19
|
+
<SettingsGroup title={i18n.t('notifications.emailTitle')} description={i18n.t('notifications.emailDescription')}>
|
|
20
|
+
<div class="divide-y divide-border">
|
|
21
|
+
<SettingsFieldRow label={i18n.t('notifications.securityAlerts')} description={i18n.t('notifications.securityAlertsDesc')}>{#snippet control()}<Switch id="email-security" bind:checked={emailAlerts.security} />{/snippet}</SettingsFieldRow>
|
|
22
|
+
<SettingsFieldRow label={i18n.t('notifications.activityLogs')} description={i18n.t('notifications.activityLogsDesc')}>{#snippet control()}<Switch id="email-activity" bind:checked={emailAlerts.activity} />{/snippet}</SettingsFieldRow>
|
|
23
|
+
<SettingsFieldRow label={i18n.t('notifications.systemAlerts')} description={i18n.t('notifications.systemAlertsDesc')}>{#snippet control()}<Switch id="email-reports" bind:checked={emailAlerts.reports} />{/snippet}</SettingsFieldRow>
|
|
29
24
|
</div>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<Card.CardContent class="space-y-4">
|
|
42
|
-
<div class="flex items-center justify-between gap-4 border-b py-2">
|
|
43
|
-
<div class="space-y-1">
|
|
44
|
-
<Label for="email-security">{i18n.t('notifications.securityAlerts')}</Label>
|
|
45
|
-
<p class="text-xs text-muted-foreground">{i18n.t('notifications.securityAlertsDesc')}</p>
|
|
46
|
-
</div>
|
|
47
|
-
<Switch id="email-security" bind:checked={emailAlerts.security} />
|
|
48
|
-
</div>
|
|
49
|
-
<div class="flex items-center justify-between gap-4 border-b py-2">
|
|
50
|
-
<div class="space-y-1">
|
|
51
|
-
<Label for="email-activity">{i18n.t('notifications.activityLogs')}</Label>
|
|
52
|
-
<p class="text-xs text-muted-foreground">{i18n.t('notifications.activityLogsDesc')}</p>
|
|
53
|
-
</div>
|
|
54
|
-
<Switch id="email-activity" bind:checked={emailAlerts.activity} />
|
|
55
|
-
</div>
|
|
56
|
-
<div class="flex items-center justify-between gap-4 py-2">
|
|
57
|
-
<div class="space-y-1">
|
|
58
|
-
<Label for="email-reports">{i18n.t('notifications.systemAlerts')}</Label>
|
|
59
|
-
<p class="text-xs text-muted-foreground">{i18n.t('notifications.systemAlertsDesc')}</p>
|
|
60
|
-
</div>
|
|
61
|
-
<Switch id="email-reports" bind:checked={emailAlerts.reports} />
|
|
62
|
-
</div>
|
|
63
|
-
</Card.CardContent>
|
|
64
|
-
</Card.Card>
|
|
65
|
-
|
|
66
|
-
<Card.Card>
|
|
67
|
-
<Card.CardHeader>
|
|
68
|
-
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
69
|
-
<Bell class="h-4 w-4 text-muted-foreground" />
|
|
70
|
-
{i18n.t('notifications.pushTitle')}
|
|
71
|
-
</Card.CardTitle>
|
|
72
|
-
<Card.CardDescription>{i18n.t('notifications.pushDescription')}</Card.CardDescription>
|
|
73
|
-
</Card.CardHeader>
|
|
74
|
-
<Card.CardContent class="space-y-4">
|
|
75
|
-
<div class="flex items-center justify-between gap-4 border-b py-2">
|
|
76
|
-
<div class="space-y-1">
|
|
77
|
-
<Label for="push-security">{i18n.t('notifications.securityAlerts')}</Label>
|
|
78
|
-
<p class="text-xs text-muted-foreground">{i18n.t('notifications.securityPushDesc')}</p>
|
|
79
|
-
</div>
|
|
80
|
-
<Switch id="push-security" bind:checked={pushAlerts.security} />
|
|
81
|
-
</div>
|
|
82
|
-
<div class="flex items-center justify-between gap-4 border-b py-2">
|
|
83
|
-
<div class="space-y-1">
|
|
84
|
-
<Label for="push-activity">{i18n.t('notifications.activityLogs')}</Label>
|
|
85
|
-
<p class="text-xs text-muted-foreground">{i18n.t('notifications.activityPushDesc')}</p>
|
|
86
|
-
</div>
|
|
87
|
-
<Switch id="push-activity" bind:checked={pushAlerts.activity} />
|
|
88
|
-
</div>
|
|
89
|
-
<div class="flex items-center justify-between gap-4 py-2">
|
|
90
|
-
<div class="space-y-1">
|
|
91
|
-
<Label for="push-reports">{i18n.t('notifications.systemAlerts')}</Label>
|
|
92
|
-
<p class="text-xs text-muted-foreground">{i18n.t('notifications.systemPushDesc')}</p>
|
|
93
|
-
</div>
|
|
94
|
-
<Switch id="push-reports" bind:checked={pushAlerts.reports} />
|
|
95
|
-
</div>
|
|
96
|
-
</Card.CardContent>
|
|
97
|
-
</Card.Card>
|
|
98
|
-
|
|
99
|
-
<Card.Card>
|
|
100
|
-
<Card.CardHeader>
|
|
101
|
-
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
102
|
-
<Smartphone class="h-4 w-4 text-muted-foreground" />
|
|
103
|
-
{i18n.t('notifications.smsTitle')}
|
|
104
|
-
</Card.CardTitle>
|
|
105
|
-
<Card.CardDescription>{i18n.t('notifications.smsDescription')}</Card.CardDescription>
|
|
106
|
-
</Card.CardHeader>
|
|
107
|
-
<Card.CardContent>
|
|
108
|
-
<div class="flex items-center justify-between gap-4 py-2">
|
|
109
|
-
<div class="space-y-1">
|
|
110
|
-
<Label for="sms-security">{i18n.t('notifications.criticalSecurity')}</Label>
|
|
111
|
-
<p class="text-xs text-muted-foreground">{i18n.t('notifications.criticalSecurityDesc')}</p>
|
|
112
|
-
</div>
|
|
113
|
-
<Switch id="sms-security" bind:checked={smsAlerts.security} />
|
|
114
|
-
</div>
|
|
115
|
-
</Card.CardContent>
|
|
116
|
-
</Card.Card>
|
|
25
|
+
</SettingsGroup>
|
|
26
|
+
<SettingsGroup title={i18n.t('notifications.pushTitle')} description={i18n.t('notifications.pushDescription')}>
|
|
27
|
+
<div class="divide-y divide-border">
|
|
28
|
+
<SettingsFieldRow label={i18n.t('notifications.securityAlerts')} description={i18n.t('notifications.securityPushDesc')}>{#snippet control()}<Switch id="push-security" bind:checked={pushAlerts.security} />{/snippet}</SettingsFieldRow>
|
|
29
|
+
<SettingsFieldRow label={i18n.t('notifications.activityLogs')} description={i18n.t('notifications.activityPushDesc')}>{#snippet control()}<Switch id="push-activity" bind:checked={pushAlerts.activity} />{/snippet}</SettingsFieldRow>
|
|
30
|
+
<SettingsFieldRow label={i18n.t('notifications.systemAlerts')} description={i18n.t('notifications.systemPushDesc')}>{#snippet control()}<Switch id="push-reports" bind:checked={pushAlerts.reports} />{/snippet}</SettingsFieldRow>
|
|
31
|
+
</div>
|
|
32
|
+
</SettingsGroup>
|
|
33
|
+
<SettingsGroup title={i18n.t('notifications.smsTitle')} description={i18n.t('notifications.smsDescription')}>
|
|
34
|
+
<SettingsFieldRow label={i18n.t('notifications.criticalSecurity')} description={i18n.t('notifications.criticalSecurityDesc')}>{#snippet control()}<Switch id="sms-security" bind:checked={smsAlerts.security} />{/snippet}</SettingsFieldRow>
|
|
35
|
+
</SettingsGroup>
|
|
117
36
|
</div>
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
|
|
57
57
|
<div class="h-full flex flex-col pt-2">
|
|
58
58
|
<!-- Header Title -->
|
|
59
|
-
<div class="mb-6 flex items-
|
|
59
|
+
<div class="mb-6 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
60
60
|
<div>
|
|
61
61
|
<h1 class="text-2xl font-bold text-foreground flex items-center gap-2">
|
|
62
62
|
<Shield class="w-6 h-6 text-primary" />
|
|
@@ -78,10 +78,19 @@
|
|
|
78
78
|
{/if}
|
|
79
79
|
</div>
|
|
80
80
|
|
|
81
|
-
<div class="flex flex-col
|
|
81
|
+
<div class="flex flex-col lg:flex-row gap-6 flex-1 min-h-[500px]">
|
|
82
|
+
<div class="w-full rounded-lg border border-border bg-card p-4 shadow-sm lg:hidden">
|
|
83
|
+
<label for="permission-role-select" class="mb-2 block text-sm font-medium text-foreground">{i18n.t('permissions.roles') || 'Roles'}</label>
|
|
84
|
+
<select id="permission-role-select" bind:value={selectedRole} class="h-9 w-full rounded-md border border-input bg-background px-3 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2" disabled={roles.length === 0}>
|
|
85
|
+
{#each roles as role (role.code)}<option value={role.code}>{role.name}</option>{/each}
|
|
86
|
+
</select>
|
|
87
|
+
{#if roles.length === 0}<p class="mt-2 text-sm text-muted-foreground">{i18n.t('permissions.noRoles') || 'No roles available'}</p>{/if}
|
|
88
|
+
{#if sidebarExtra}<div class="mt-4 border-t border-border pt-4">{@render sidebarExtra()}</div>{/if}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
82
91
|
<!-- Left: Role Selector Sidebar -->
|
|
83
|
-
<div class="w-full
|
|
84
|
-
<h2 class="text-sm font-semibold text-muted-foreground
|
|
92
|
+
<div class="hidden w-full flex-shrink-0 flex-col rounded-lg border border-border bg-card p-4 shadow-sm lg:flex lg:w-64">
|
|
93
|
+
<h2 class="mb-4 px-2 text-sm font-semibold text-muted-foreground">{i18n.t('permissions.roles') || 'Roles'}</h2>
|
|
85
94
|
<div class="space-y-1 flex-1 overflow-y-auto">
|
|
86
95
|
{#each roles as role, _i (_i)}
|
|
87
96
|
<button
|
|
@@ -89,7 +98,7 @@
|
|
|
89
98
|
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
99
|
>
|
|
91
100
|
{role.name}
|
|
92
|
-
<div class="
|
|
101
|
+
<div class="mt-0.5 font-mono text-[10px] opacity-60">{role.code}</div>
|
|
93
102
|
</button>
|
|
94
103
|
{/each}
|
|
95
104
|
{#if roles.length === 0}
|
|
@@ -106,10 +115,10 @@
|
|
|
106
115
|
</div>
|
|
107
116
|
|
|
108
117
|
<!-- Right: Matrix Grid -->
|
|
109
|
-
<div class="flex-1 bg-card rounded-
|
|
118
|
+
<div class="flex-1 bg-card rounded-lg shadow-sm border border-border overflow-hidden flex flex-col">
|
|
110
119
|
<!-- Matrix Header -->
|
|
111
|
-
<div class="p-5 border-b border-border bg-muted/30 flex items-
|
|
112
|
-
<div class="flex items-center gap-4">
|
|
120
|
+
<div class="p-5 border-b border-border bg-muted/30 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
121
|
+
<div class="flex min-w-0 flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
|
|
113
122
|
<div>
|
|
114
123
|
<h2 class="text-lg font-bold text-foreground">{i18n.t('permissions.currentRole') || 'Current role'}:
|
|
115
124
|
<span class="text-primary">{roles.find(r => r.code === selectedRole)?.name || (i18n.t('permissions.noneSelected') || 'None selected')}</span>
|
|
@@ -156,7 +165,7 @@
|
|
|
156
165
|
<!-- Section Grouping Header -->
|
|
157
166
|
{#if resource.section && (i === 0 || resource.section !== resources[i-1].section)}
|
|
158
167
|
<tr class="bg-primary/5">
|
|
159
|
-
<td colspan="{actions.length + 1}" class="px-4 py-2 text-xs font-bold text-primary
|
|
168
|
+
<td colspan="{actions.length + 1}" class="px-4 py-2 text-xs font-bold text-primary">{resource.section}</td>
|
|
160
169
|
</tr>
|
|
161
170
|
{/if}
|
|
162
171
|
|
|
@@ -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>
|