@svadmin/ui 0.34.10 → 0.35.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.
Files changed (70) hide show
  1. package/dist/app.css +372 -0
  2. package/dist/component-registry.svelte.d.ts +1 -0
  3. package/dist/components/AdminApp.svelte +94 -15
  4. package/dist/components/AdminApp.svelte.d.ts +32 -2
  5. package/dist/components/ApiSettings.svelte +191 -0
  6. package/dist/components/ApiSettings.svelte.d.ts +3 -0
  7. package/dist/components/AutoForm.svelte +48 -43
  8. package/dist/components/AutoTable.svelte +27 -22
  9. package/dist/components/ErrorPage.svelte +58 -0
  10. package/dist/components/ErrorPage.svelte.d.ts +8 -0
  11. package/dist/components/FieldRenderer.svelte +2 -2
  12. package/dist/components/ForgotPasswordPage.svelte +111 -68
  13. package/dist/components/Header.svelte +6 -7
  14. package/dist/components/IntegrationsSettings.svelte +70 -0
  15. package/dist/components/IntegrationsSettings.svelte.d.ts +4 -0
  16. package/dist/components/Layout.svelte +8 -8
  17. package/dist/components/LoginPage.svelte +207 -105
  18. package/dist/components/LoginPage.svelte.d.ts +3 -15
  19. package/dist/components/NotificationsSettings.svelte +115 -0
  20. package/dist/components/NotificationsSettings.svelte.d.ts +3 -0
  21. package/dist/components/PageHeader.svelte +9 -7
  22. package/dist/components/RegisterPage.svelte +103 -65
  23. package/dist/components/ResourceOperationsPage.svelte +153 -0
  24. package/dist/components/ResourceOperationsPage.svelte.d.ts +37 -0
  25. package/dist/components/SecuritySettings.svelte +179 -0
  26. package/dist/components/SecuritySettings.svelte.d.ts +4 -0
  27. package/dist/components/SettingsPage.svelte +121 -54
  28. package/dist/components/SettingsPage.svelte.d.ts +25 -1
  29. package/dist/components/ShowPage.svelte +9 -9
  30. package/dist/components/Sidebar.svelte +96 -48
  31. package/dist/components/SidebarItem.svelte +37 -12
  32. package/dist/components/TwoFactorAuthPage.svelte +247 -0
  33. package/dist/components/TwoFactorAuthPage.svelte.d.ts +3 -0
  34. package/dist/components/UpdatePasswordPage.svelte +86 -48
  35. package/dist/components/account/CompanyProfilePage.svelte +99 -0
  36. package/dist/components/account/CompanyProfilePage.svelte.d.ts +3 -0
  37. package/dist/components/account/GetStartedPage.svelte +123 -0
  38. package/dist/components/account/GetStartedPage.svelte.d.ts +4 -0
  39. package/dist/components/account/ImportMembersPage.svelte +143 -0
  40. package/dist/components/account/ImportMembersPage.svelte.d.ts +3 -0
  41. package/dist/components/account/MembersStarterPage.svelte +104 -0
  42. package/dist/components/account/MembersStarterPage.svelte.d.ts +3 -0
  43. package/dist/components/account/SecurityLogPage.svelte +108 -0
  44. package/dist/components/account/SecurityLogPage.svelte.d.ts +3 -0
  45. package/dist/components/account/SettingsEnterprisePage.svelte +131 -0
  46. package/dist/components/account/SettingsEnterprisePage.svelte.d.ts +3 -0
  47. package/dist/components/account/SettingsPlainPage.svelte +81 -0
  48. package/dist/components/account/SettingsPlainPage.svelte.d.ts +3 -0
  49. package/dist/components/account/SettingsSidebarPage.svelte +98 -0
  50. package/dist/components/account/SettingsSidebarPage.svelte.d.ts +3 -0
  51. package/dist/components/account/TeamMembersPage.svelte +98 -0
  52. package/dist/components/account/TeamMembersPage.svelte.d.ts +3 -0
  53. package/dist/components/network/TeamCrewTablePage.svelte +122 -0
  54. package/dist/components/network/TeamCrewTablePage.svelte.d.ts +3 -0
  55. package/dist/components/network/UserCardsNFTPage.svelte +134 -0
  56. package/dist/components/network/UserCardsNFTPage.svelte.d.ts +3 -0
  57. package/dist/components/profile/ActivityTimeline.svelte +77 -0
  58. package/dist/components/profile/ActivityTimeline.svelte.d.ts +16 -0
  59. package/dist/components/profile/ProfileCard.svelte +216 -0
  60. package/dist/components/profile/ProfileCard.svelte.d.ts +27 -0
  61. package/dist/components/profile/ProjectsGrid.svelte +91 -0
  62. package/dist/components/profile/ProjectsGrid.svelte.d.ts +17 -0
  63. package/dist/components/profile/PublicProfilePage.svelte +179 -0
  64. package/dist/components/profile/PublicProfilePage.svelte.d.ts +10 -0
  65. package/dist/components/profile/TeamsShowcase.svelte +98 -0
  66. package/dist/components/profile/TeamsShowcase.svelte.d.ts +19 -0
  67. package/dist/index.d.ts +23 -0
  68. package/dist/index.js +27 -0
  69. package/dist/router-state.svelte.js +17 -0
  70. package/package.json +2 -2
@@ -5,44 +5,46 @@
5
5
  import { Button } from './ui/button/index.js';
6
6
  import { Input } from './ui/input/index.js';
7
7
  import { Label } from './ui/label/index.js';
8
- import * as Card from './ui/card/index.js';
9
8
  import * as Alert from './ui/alert/index.js';
10
9
  import PasswordInput from './PasswordInput.svelte';
11
10
  import { Separator } from './ui/separator/index.js';
12
- import { LogIn, User, Loader2, AlertCircle } from '@lucide/svelte';
13
-
14
- /**
15
- * Social provider config for the login page.
16
- * Each entry renders a button that calls `onClick()` when clicked.
17
- *
18
- * @example
19
- * ```ts
20
- * const socialProviders = [
21
- * { name: 'Google', icon: '🔵', onClick: () => googleAuth.login({}) },
22
- * { name: 'GitHub', icon: '🐙', onClick: () => githubAuth.login({}) },
23
- * ];
24
- * ```
25
- */
11
+ import { User, Loader2, AlertCircle, Shield } from '@lucide/svelte';
12
+
26
13
  interface SocialProvider {
27
- /** Display name (e.g., 'Google') */
28
14
  name: string;
29
- /** Icon character, emoji, or SVG string */
30
15
  icon?: string;
31
- /** Handler to initiate social login */
32
16
  onClick: () => void | Promise<void>;
33
17
  }
34
18
 
35
- let { title = 'Admin', onSuccess, socialProviders = [] } = $props<{
19
+ let {
20
+ title = 'Admin',
21
+ onSuccess,
22
+ socialProviders = [],
23
+ defaultIdentifier = '',
24
+ defaultPassword = '',
25
+ loginHint,
26
+ } = $props<{
36
27
  title?: string;
37
28
  onSuccess?: () => void;
38
29
  socialProviders?: SocialProvider[];
30
+ defaultIdentifier?: string;
31
+ defaultPassword?: string;
32
+ loginHint?: string;
39
33
  }>();
40
34
 
41
35
  const login = useLogin({ errorMessage: false });
42
36
  const authProvider = getAuthProvider();
43
37
 
44
- let identifier = $state('');
45
- let password = $state('');
38
+ function getInitialIdentifier() {
39
+ return defaultIdentifier;
40
+ }
41
+
42
+ function getInitialPassword() {
43
+ return defaultPassword;
44
+ }
45
+
46
+ let identifier = $state(getInitialIdentifier());
47
+ let password = $state(getInitialPassword());
46
48
  let error = $state('');
47
49
 
48
50
  async function handleSubmit(e: Event) {
@@ -61,103 +63,203 @@
61
63
  }
62
64
  </script>
63
65
 
64
- <div class="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-100 via-blue-50/30 to-indigo-50/40 dark:from-slate-950 dark:via-slate-900 dark:to-indigo-950/20 p-4">
65
- <div class="w-full max-w-[380px]">
66
- <Card.Card>
67
- <Card.CardHeader class="text-center pb-2">
68
- <div class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 text-primary mx-auto mb-3">
69
- <LogIn class="h-5 w-5" />
66
+ <div class="min-h-screen flex flex-col lg:flex-row">
67
+ <div class="hidden lg:flex lg:w-[500px] xl:w-[560px] relative overflow-hidden bg-gradient-to-br from-primary/95 via-primary to-primary/85 shrink-0">
68
+ <div class="absolute inset-0">
69
+ <div class="absolute top-[12%] left-[8%] h-32 w-32 rounded-3xl border border-white/10 bg-white/5 rotate-12 backdrop-blur-sm"></div>
70
+ <div class="absolute top-[54%] left-[54%] h-44 w-44 rounded-full border border-white/10 bg-white/5 -rotate-6 backdrop-blur-sm"></div>
71
+ <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>
72
+ <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>
73
+ <div class="absolute inset-0 opacity-[0.04]" style="background-image: radial-gradient(circle, white 1px, transparent 1px); background-size: 24px 24px;"></div>
74
+ </div>
75
+
76
+ <div class="relative z-10 flex flex-col justify-between p-10 xl:p-12 w-full">
77
+ <div class="flex items-center gap-3">
78
+ <div class="flex h-11 w-11 items-center justify-center rounded-xl bg-white/15 backdrop-blur-sm">
79
+ <Shield class="h-5 w-5 text-white" />
70
80
  </div>
71
- <Card.CardTitle class="text-xl">{t('auth.welcomeBack')}</Card.CardTitle>
72
- <p class="text-sm text-muted-foreground">{t('auth.welcomeMessage')}</p>
73
- </Card.CardHeader>
74
- <Card.CardContent>
75
- <form onsubmit={handleSubmit} class="space-y-4">
76
- {#if error}
77
- <Alert.Root variant="destructive">
78
- <AlertCircle class="h-4 w-4" />
79
- <Alert.Description>{error}</Alert.Description>
80
- </Alert.Root>
81
- {/if}
81
+ <span class="text-xl font-semibold text-white tracking-tight">{title}</span>
82
+ </div>
83
+
84
+ <div class="max-w-[360px] space-y-4">
85
+ <h2 class="text-4xl font-bold text-white leading-tight">
86
+ {t('auth.welcomeBack')}
87
+ </h2>
88
+ <p class="text-lg text-white/75 leading-relaxed">
89
+ {t('auth.welcomeMessage')}
90
+ </p>
91
+ </div>
92
+
93
+ <div class="flex items-center gap-3">
94
+ <div class="h-1.5 w-10 rounded-full bg-white/40"></div>
95
+ <div class="h-1.5 w-5 rounded-full bg-white/20"></div>
96
+ <div class="h-1.5 w-2 rounded-full bg-white/15"></div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <div class="flex-1 flex items-center justify-center bg-background p-6 sm:p-10 lg:p-14">
102
+ <div class="w-full max-w-[460px] space-y-8">
103
+ <div class="flex items-center gap-3 mb-8 lg:hidden">
104
+ <div class="flex h-11 w-11 items-center justify-center rounded-xl bg-primary/10 text-primary">
105
+ <Shield class="h-5 w-5" />
106
+ </div>
107
+ <span class="text-xl font-semibold tracking-tight text-foreground">{title}</span>
108
+ </div>
109
+
110
+ <div class="space-y-2.5">
111
+ <h1 class="text-3xl font-semibold tracking-tight text-foreground">
112
+ {t('auth.welcomeBack')}
113
+ </h1>
114
+ <p class="text-base text-muted-foreground leading-relaxed">
115
+ {t('auth.welcomeMessage')}
116
+ </p>
117
+ </div>
82
118
 
83
- <div class="space-y-2">
84
- <Label for="login-identifier">{t('auth.usernameOrEmail')}</Label>
85
- <div class="relative">
86
- <User class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none z-[1]" />
87
- <Input
88
- id="login-identifier"
89
- type="text"
90
- placeholder={t('auth.identifierPlaceholder')}
91
- bind:value={identifier}
92
- class="pl-9"
93
- autocomplete="username"
94
- />
95
- </div>
119
+ {#if loginHint || defaultIdentifier || defaultPassword}
120
+ <div class="rounded-2xl border border-primary/15 bg-primary/[0.06] p-4 shadow-sm">
121
+ {#if loginHint}
122
+ <p class="text-sm font-medium text-primary">{loginHint}</p>
123
+ {/if}
124
+ <div class="mt-3 grid gap-2 sm:grid-cols-2">
125
+ {#if defaultIdentifier}
126
+ <div class="rounded-xl border border-border/70 bg-background/80 px-3 py-2">
127
+ <p class="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
128
+ {t('auth.usernameOrEmail')}
129
+ </p>
130
+ <p class="mt-1 font-mono text-sm font-semibold text-foreground">{defaultIdentifier}</p>
131
+ </div>
132
+ {/if}
133
+ {#if defaultPassword}
134
+ <div class="rounded-xl border border-border/70 bg-background/80 px-3 py-2">
135
+ <p class="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
136
+ {t('auth.password')}
137
+ </p>
138
+ <p class="mt-1 font-mono text-sm font-semibold text-foreground">{defaultPassword}</p>
139
+ </div>
140
+ {/if}
96
141
  </div>
142
+ </div>
143
+ {/if}
97
144
 
98
- <div>
99
- <div class="flex items-center justify-between mb-2">
100
- <Label for="login-password">{t('auth.password')}</Label>
101
- {#if authProvider.forgotPassword}
102
- <Button variant="link" class="text-xs h-auto p-0" onclick={() => navigate('/forgot-password')}>
103
- {t('auth.forgotPasswordLink')}
104
- </Button>
105
- {/if}
106
- </div>
107
- <PasswordInput
108
- id="login-password"
109
- label=""
110
- bind:value={password}
111
- autocomplete="current-password"
145
+ <form onsubmit={handleSubmit} class="space-y-6 login-form">
146
+ {#if error}
147
+ <Alert.Root variant="destructive" class="rounded-xl border border-destructive/20 bg-destructive/5">
148
+ <AlertCircle class="h-4 w-4" />
149
+ <Alert.Description class="text-sm font-medium">{error}</Alert.Description>
150
+ </Alert.Root>
151
+ {/if}
152
+
153
+ <div class="space-y-2.5">
154
+ <Label for="login-identifier" class="text-sm font-semibold text-foreground/80 pl-0.5">
155
+ {t('auth.usernameOrEmail')}
156
+ </Label>
157
+ <div class="relative">
158
+ <User class="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground pointer-events-none z-[1]" />
159
+ <Input
160
+ id="login-identifier"
161
+ type="text"
162
+ placeholder={t('auth.identifierPlaceholder')}
163
+ bind:value={identifier}
164
+ class="pl-12"
165
+ autocomplete="username"
112
166
  />
113
167
  </div>
168
+ </div>
114
169
 
115
- <Button type="submit" class="w-full" disabled={login.isLoading}>
116
- {#if login.isLoading}
117
- <Loader2 class="h-4 w-4 animate-spin mr-2" />
170
+ <div class="space-y-2.5">
171
+ <div class="flex items-center justify-between pl-0.5">
172
+ <Label for="login-password" class="text-sm font-semibold text-foreground/80">
173
+ {t('auth.password')}
174
+ </Label>
175
+ {#if authProvider.forgotPassword}
176
+ <Button variant="link" class="text-xs h-auto p-0 font-medium hover:text-primary transition-colors" onclick={() => navigate('/forgot-password')}>
177
+ {t('auth.forgotPasswordLink')}
178
+ </Button>
118
179
  {/if}
119
- {t('auth.loginButton')}
120
- </Button>
121
- </form>
122
-
123
- {#if socialProviders.length > 0}
124
- <div class="relative my-5">
125
- <Separator />
126
- <span class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-card px-3 text-xs text-muted-foreground">
127
- {t('auth.orContinueWith')}
128
- </span>
129
180
  </div>
181
+ <PasswordInput
182
+ id="login-password"
183
+ label=""
184
+ bind:value={password}
185
+ autocomplete="current-password"
186
+ />
187
+ </div>
130
188
 
131
- <div class="grid gap-2" class:grid-cols-2={socialProviders.length >= 2}>
132
- {#each socialProviders as provider (provider.name)}
133
- <Button
134
- variant="outline"
135
- class="w-full"
136
- onclick={provider.onClick}
137
- >
138
- {#if provider.icon}
139
- <span class="mr-2">{provider.icon}</span>
140
- {/if}
141
- {provider.name}
142
- </Button>
143
- {/each}
144
- </div>
145
- {/if}
189
+ <Button type="submit" class="w-full h-14 rounded-xl text-base font-semibold shadow-md shadow-primary/20 hover:shadow-lg hover:shadow-primary/25 hover:-translate-y-[1px] active:translate-y-0 transition-all duration-200 mt-2" disabled={login.isLoading}>
190
+ {#if login.isLoading}
191
+ <Loader2 class="h-5 w-5 animate-spin mr-2" />
192
+ {/if}
193
+ {t('auth.loginButton')}
194
+ </Button>
195
+ </form>
146
196
 
147
- {#if authProvider.register}
148
- <div class="flex items-center justify-center gap-1 mt-5 pt-5 border-t">
149
- <span class="text-sm text-muted-foreground">{t('auth.noAccount')}</span>
150
- <Button variant="link" class="text-sm h-auto p-0 font-medium" onclick={() => navigate('/register')}>
151
- {t('auth.register')}
197
+ {#if socialProviders.length > 0}
198
+ <div class="relative my-8">
199
+ <Separator />
200
+ <span class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-4 text-xs font-semibold text-muted-foreground tracking-wider uppercase">
201
+ {t('auth.orContinueWith')}
202
+ </span>
203
+ </div>
204
+
205
+ <div class="grid gap-3" class:grid-cols-2={socialProviders.length >= 2}>
206
+ {#each socialProviders as provider (provider.name)}
207
+ <Button
208
+ variant="outline"
209
+ class="w-full h-12 rounded-xl font-semibold border-input/60 hover:bg-muted/50 hover:text-accent-foreground transition-all duration-200"
210
+ onclick={provider.onClick}
211
+ >
212
+ {#if provider.icon}
213
+ <span class="mr-2 text-lg">{provider.icon}</span>
214
+ {/if}
215
+ {provider.name}
152
216
  </Button>
153
- </div>
154
- {/if}
155
- </Card.CardContent>
156
- </Card.Card>
217
+ {/each}
218
+ </div>
219
+ {/if}
220
+
221
+ {#if authProvider.register}
222
+ <div class="flex items-center justify-center gap-1.5 mt-8 pt-6 border-t border-border/60">
223
+ <span class="text-sm text-muted-foreground">{t('auth.noAccount')}</span>
224
+ <Button variant="link" class="text-sm h-auto p-0 font-semibold text-primary hover:underline" onclick={() => navigate('/register')}>
225
+ {t('auth.register')}
226
+ </Button>
227
+ </div>
228
+ {/if}
157
229
 
158
- <p class="text-xs text-muted-foreground mt-4 text-center">
159
- Powered by {title}
160
- </p>
230
+ <p class="text-xs text-muted-foreground/50 mt-8 text-center tracking-wide font-medium">
231
+ Powered by {title}
232
+ </p>
233
+ </div>
161
234
  </div>
162
235
  </div>
163
236
 
237
+ <style>
238
+ .login-form :global(input[data-slot='input']) {
239
+ height: 3.5rem;
240
+ border-radius: 0.75rem;
241
+ border-color: color-mix(in oklch, var(--border) 88%, var(--foreground));
242
+ background-color: var(--background);
243
+ font-size: 0.975rem;
244
+ transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
245
+ }
246
+
247
+ .login-form :global(input[data-slot='input']:focus-visible) {
248
+ border-color: var(--primary);
249
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--primary) 18%, transparent);
250
+ }
251
+
252
+ .login-form :global(.relative > svg:first-of-type) {
253
+ left: 1rem;
254
+ height: 1.25rem;
255
+ width: 1.25rem;
256
+ color: color-mix(in oklch, var(--muted-foreground) 82%, var(--foreground));
257
+ }
258
+
259
+ .login-form :global(.relative > button) {
260
+ right: 0.625rem;
261
+ height: 2.25rem;
262
+ width: 2.25rem;
263
+ border-radius: 0.5rem;
264
+ }
265
+ </style>
@@ -1,27 +1,15 @@
1
- /**
2
- * Social provider config for the login page.
3
- * Each entry renders a button that calls `onClick()` when clicked.
4
- *
5
- * @example
6
- * ```ts
7
- * const socialProviders = [
8
- * { name: 'Google', icon: '🔵', onClick: () => googleAuth.login({}) },
9
- * { name: 'GitHub', icon: '🐙', onClick: () => githubAuth.login({}) },
10
- * ];
11
- * ```
12
- */
13
1
  interface SocialProvider {
14
- /** Display name (e.g., 'Google') */
15
2
  name: string;
16
- /** Icon character, emoji, or SVG string */
17
3
  icon?: string;
18
- /** Handler to initiate social login */
19
4
  onClick: () => void | Promise<void>;
20
5
  }
21
6
  type $$ComponentProps = {
22
7
  title?: string;
23
8
  onSuccess?: () => void;
24
9
  socialProviders?: SocialProvider[];
10
+ defaultIdentifier?: string;
11
+ defaultPassword?: string;
12
+ loginHint?: string;
25
13
  };
26
14
  declare const LoginPage: import("svelte").Component<$$ComponentProps, {}, "">;
27
15
  type LoginPage = ReturnType<typeof LoginPage>;
@@ -0,0 +1,115 @@
1
+ <script lang="ts">
2
+ import { t } from '@svadmin/core/i18n';
3
+ import { Bell, Mail, Smartphone } from '@lucide/svelte';
4
+ import * as Card from './ui/card/index.js';
5
+ import { Button } from './ui/button/index.js';
6
+ import { Label } from './ui/label/index.js';
7
+ import { Switch } from './ui/switch/index.js';
8
+
9
+ let emailAlerts = $state({ security: true, activity: false, reports: true });
10
+ let pushAlerts = $state({ security: true, activity: true, reports: false });
11
+ let smsAlerts = $state({ security: true });
12
+ let isSaved = $state(false);
13
+
14
+ function saveSettings() {
15
+ isSaved = true;
16
+ setTimeout(() => {
17
+ isSaved = false;
18
+ }, 3000);
19
+ }
20
+ </script>
21
+
22
+ <div class="space-y-6">
23
+ <div class="flex flex-col justify-between gap-3 sm:flex-row sm:items-start">
24
+ <div>
25
+ <h2 class="text-xl font-semibold text-foreground">{t('settings.notifications')}</h2>
26
+ <p class="mt-1 text-sm text-muted-foreground">{t('settings.notificationsDescription')}</p>
27
+ </div>
28
+ <Button onclick={saveSettings}>{isSaved ? t('common.autoSaved') : t('common.save')}</Button>
29
+ </div>
30
+
31
+ <Card.Card>
32
+ <Card.CardHeader>
33
+ <Card.CardTitle class="flex items-center gap-2 text-base">
34
+ <Mail class="h-4 w-4 text-muted-foreground" />
35
+ {t('notifications.emailTitle')}
36
+ </Card.CardTitle>
37
+ <Card.CardDescription>{t('notifications.emailDescription')}</Card.CardDescription>
38
+ </Card.CardHeader>
39
+ <Card.CardContent class="space-y-4">
40
+ <div class="flex items-center justify-between gap-4 border-b py-2">
41
+ <div class="space-y-1">
42
+ <Label for="email-security">{t('notifications.securityAlerts')}</Label>
43
+ <p class="text-xs text-muted-foreground">{t('notifications.securityAlertsDesc')}</p>
44
+ </div>
45
+ <Switch id="email-security" bind:checked={emailAlerts.security} />
46
+ </div>
47
+ <div class="flex items-center justify-between gap-4 border-b py-2">
48
+ <div class="space-y-1">
49
+ <Label for="email-activity">{t('notifications.activityLogs')}</Label>
50
+ <p class="text-xs text-muted-foreground">{t('notifications.activityLogsDesc')}</p>
51
+ </div>
52
+ <Switch id="email-activity" bind:checked={emailAlerts.activity} />
53
+ </div>
54
+ <div class="flex items-center justify-between gap-4 py-2">
55
+ <div class="space-y-1">
56
+ <Label for="email-reports">{t('notifications.systemAlerts')}</Label>
57
+ <p class="text-xs text-muted-foreground">{t('notifications.systemAlertsDesc')}</p>
58
+ </div>
59
+ <Switch id="email-reports" bind:checked={emailAlerts.reports} />
60
+ </div>
61
+ </Card.CardContent>
62
+ </Card.Card>
63
+
64
+ <Card.Card>
65
+ <Card.CardHeader>
66
+ <Card.CardTitle class="flex items-center gap-2 text-base">
67
+ <Bell class="h-4 w-4 text-muted-foreground" />
68
+ {t('notifications.pushTitle')}
69
+ </Card.CardTitle>
70
+ <Card.CardDescription>{t('notifications.pushDescription')}</Card.CardDescription>
71
+ </Card.CardHeader>
72
+ <Card.CardContent class="space-y-4">
73
+ <div class="flex items-center justify-between gap-4 border-b py-2">
74
+ <div class="space-y-1">
75
+ <Label for="push-security">{t('notifications.securityAlerts')}</Label>
76
+ <p class="text-xs text-muted-foreground">{t('notifications.securityPushDesc')}</p>
77
+ </div>
78
+ <Switch id="push-security" bind:checked={pushAlerts.security} />
79
+ </div>
80
+ <div class="flex items-center justify-between gap-4 border-b py-2">
81
+ <div class="space-y-1">
82
+ <Label for="push-activity">{t('notifications.activityLogs')}</Label>
83
+ <p class="text-xs text-muted-foreground">{t('notifications.activityPushDesc')}</p>
84
+ </div>
85
+ <Switch id="push-activity" bind:checked={pushAlerts.activity} />
86
+ </div>
87
+ <div class="flex items-center justify-between gap-4 py-2">
88
+ <div class="space-y-1">
89
+ <Label for="push-reports">{t('notifications.systemAlerts')}</Label>
90
+ <p class="text-xs text-muted-foreground">{t('notifications.systemPushDesc')}</p>
91
+ </div>
92
+ <Switch id="push-reports" bind:checked={pushAlerts.reports} />
93
+ </div>
94
+ </Card.CardContent>
95
+ </Card.Card>
96
+
97
+ <Card.Card>
98
+ <Card.CardHeader>
99
+ <Card.CardTitle class="flex items-center gap-2 text-base">
100
+ <Smartphone class="h-4 w-4 text-muted-foreground" />
101
+ {t('notifications.smsTitle')}
102
+ </Card.CardTitle>
103
+ <Card.CardDescription>{t('notifications.smsDescription')}</Card.CardDescription>
104
+ </Card.CardHeader>
105
+ <Card.CardContent>
106
+ <div class="flex items-center justify-between gap-4 py-2">
107
+ <div class="space-y-1">
108
+ <Label for="sms-security">{t('notifications.criticalSecurity')}</Label>
109
+ <p class="text-xs text-muted-foreground">{t('notifications.criticalSecurityDesc')}</p>
110
+ </div>
111
+ <Switch id="sms-security" bind:checked={smsAlerts.security} />
112
+ </div>
113
+ </Card.CardContent>
114
+ </Card.Card>
115
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const NotificationsSettings: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type NotificationsSettings = ReturnType<typeof NotificationsSettings>;
3
+ export default NotificationsSettings;
@@ -19,19 +19,21 @@
19
19
  }: Props = $props();
20
20
  </script>
21
21
 
22
- <div class="space-y-4 {className}">
22
+ <div class="rounded-3xl border border-border/70 bg-card/90 p-5 shadow-sm shadow-slate-900/[0.03] backdrop-blur {className}">
23
23
  {#if showBreadcrumbs}
24
- <Breadcrumbs />
24
+ <div class="mb-4">
25
+ <Breadcrumbs />
26
+ </div>
25
27
  {/if}
26
- <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
27
- <div>
28
- <h1 class="text-xl font-semibold tracking-tight text-foreground">{title}</h1>
28
+ <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
29
+ <div class="min-w-0">
30
+ <h1 class="truncate text-2xl font-semibold tracking-tight text-foreground">{title}</h1>
29
31
  {#if description}
30
- <p class="text-sm text-muted-foreground">{description}</p>
32
+ <p class="mt-1 max-w-3xl text-sm text-muted-foreground">{description}</p>
31
33
  {/if}
32
34
  </div>
33
35
  {#if actions}
34
- <div class="flex items-center gap-2">
36
+ <div class="flex flex-wrap items-center gap-2">
35
37
  {@render actions()}
36
38
  </div>
37
39
  {/if}