@svadmin/ui 0.38.2 → 0.38.7
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/app.css +8 -4
- package/dist/app.theme.css +8 -4
- package/dist/components/AICommandBar.svelte +16 -15
- package/dist/components/AboutSettings.svelte +12 -11
- package/dist/components/AdminApp.svelte +144 -70
- package/dist/components/AdminApp.svelte.d.ts +6 -3
- package/dist/components/ApiSettings.svelte +19 -16
- package/dist/components/AppearanceSettings.svelte +18 -16
- package/dist/components/AuditLogDrawer.svelte +6 -3
- package/dist/components/AuditLogViewer.svelte +18 -15
- package/dist/components/Authenticated.svelte +4 -4
- package/dist/components/AutoForm.svelte +19 -16
- package/dist/components/AutoSaveIndicator.svelte +7 -5
- package/dist/components/AutoTable.svelte +185 -159
- package/dist/components/AutoTable.svelte.d.ts +1 -1
- package/dist/components/Breadcrumbs.svelte +17 -13
- package/dist/components/CancelTaskButton.svelte +5 -2
- package/dist/components/CatchAllNavigate.svelte +3 -3
- package/dist/components/ChatDialog.svelte +22 -19
- package/dist/components/ComboboxField.svelte +6 -4
- package/dist/components/CommandPalette.svelte +20 -20
- package/dist/components/ConfigErrorScreen.svelte +12 -10
- package/dist/components/ConfirmDialog.svelte +6 -4
- package/dist/components/CreatePage.svelte +4 -2
- package/dist/components/DevTools.svelte +11 -14
- package/dist/components/DrawerForm.svelte +5 -2
- package/dist/components/EditPage.svelte +8 -5
- package/dist/components/EmptyState.svelte +5 -3
- package/dist/components/ErrorBoundary.svelte +5 -3
- package/dist/components/ErrorComponent.svelte +6 -4
- package/dist/components/ErrorPage.svelte +12 -10
- package/dist/components/FieldRenderer.svelte +17 -15
- package/dist/components/ForgotPasswordPage.svelte +22 -19
- package/dist/components/Header.svelte +7 -4
- package/dist/components/InfiniteList.svelte +6 -3
- package/dist/components/IntegrationsSettings.svelte +9 -6
- package/dist/components/KeyboardShortcuts.svelte +4 -2
- package/dist/components/Layout.svelte +12 -13
- package/dist/components/LazyPage.svelte +47 -0
- package/dist/components/LazyPage.svelte.d.ts +14 -0
- package/dist/components/ListPage.svelte +8 -5
- package/dist/components/LoginPage.svelte +24 -22
- package/dist/components/MarkdownRenderer.svelte +89 -35
- package/dist/components/ModalForm.svelte +4 -2
- package/dist/components/NavigateToResource.svelte +3 -3
- package/dist/components/NotificationsSettings.svelte +26 -24
- package/dist/components/PasswordInput.svelte +4 -2
- package/dist/components/PermissionMatrix.svelte +15 -12
- package/dist/components/ProfilePage.svelte +23 -21
- package/dist/components/RegisterPage.svelte +21 -19
- package/dist/components/ResourceOperationsPage.svelte +3 -2
- package/dist/components/RetryTaskButton.svelte +5 -2
- package/dist/components/RolesSettings.svelte +10 -8
- package/dist/components/SecuritySettings.svelte +26 -23
- package/dist/components/SettingsPage.svelte +14 -11
- package/dist/components/ShowPage.svelte +9 -7
- package/dist/components/Sidebar.svelte +36 -28
- package/dist/components/SidebarItem.svelte +4 -4
- package/dist/components/SidebarItem.svelte.d.ts +1 -1
- package/dist/components/StepsForm.svelte +11 -8
- package/dist/components/TaskDetails.svelte +14 -11
- package/dist/components/TaskList.svelte +14 -11
- package/dist/components/TaskQueueDrawer.svelte +34 -32
- package/dist/components/TaskStatusBadge.svelte +5 -2
- package/dist/components/Toast.svelte +18 -4
- package/dist/components/TooltipButton.svelte +25 -3
- package/dist/components/TwoFactorAuthPage.svelte +26 -24
- package/dist/components/UndoableNotification.svelte +5 -2
- package/dist/components/UpdatePasswordPage.svelte +18 -15
- package/dist/components/VirtualTable.svelte +0 -4
- package/dist/components/account/CompanyProfilePage.svelte +13 -11
- package/dist/components/account/GetStartedPage.svelte +15 -13
- package/dist/components/account/ImportMembersPage.svelte +14 -12
- package/dist/components/account/MembersStarterPage.svelte +10 -8
- package/dist/components/account/SecurityLogPage.svelte +24 -22
- package/dist/components/account/SettingsEnterprisePage.svelte +6 -4
- package/dist/components/account/SettingsPlainPage.svelte +14 -12
- package/dist/components/account/SettingsSidebarPage.svelte +11 -9
- package/dist/components/account/TeamMembersPage.svelte +10 -8
- package/dist/components/buttons/CloneButton.svelte +4 -2
- package/dist/components/buttons/CreateButton.svelte +4 -2
- package/dist/components/buttons/DeleteButton.svelte +6 -4
- package/dist/components/buttons/EditButton.svelte +4 -2
- package/dist/components/buttons/ExportButton.svelte +4 -2
- package/dist/components/buttons/ImportButton.svelte +4 -2
- package/dist/components/buttons/RefreshButton.svelte +4 -2
- package/dist/components/buttons/SaveButton.svelte +4 -2
- package/dist/components/buttons/ShowButton.svelte +4 -2
- package/dist/components/fieldComponentMap.js +0 -3
- package/dist/components/fields/ArrayField.svelte +6 -4
- package/dist/components/fields/JsonField.svelte +5 -3
- package/dist/components/fields/RelationField.svelte +3 -3
- package/dist/components/fields/RichTextField.svelte +4 -2
- package/dist/components/network/TeamCrewTablePage.svelte +18 -16
- package/dist/components/network/UserCardsNFTPage.svelte +11 -9
- package/dist/components/profile/ActivityTimeline.svelte +10 -8
- package/dist/components/profile/ProfileCard.svelte +13 -11
- package/dist/components/profile/ProjectsGrid.svelte +7 -5
- package/dist/components/profile/PublicProfilePage.svelte +18 -16
- package/dist/components/profile/TeamsShowcase.svelte +7 -5
- package/dist/components/toast-host.svelte.d.ts +5 -0
- package/dist/components/toast-host.svelte.js +21 -0
- package/dist/components/ui/command/command-dialog.svelte +29 -13
- package/dist/components/ui/command/command-dialog.svelte.d.ts +4 -3
- package/dist/components/ui/command/command-empty.svelte +18 -0
- package/dist/components/ui/command/command-empty.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-group.svelte +41 -0
- package/dist/components/ui/command/command-group.svelte.d.ts +8 -0
- package/dist/components/ui/command/command-input.svelte +23 -0
- package/dist/components/ui/command/command-input.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-item.svelte +43 -0
- package/dist/components/ui/command/command-item.svelte.d.ts +11 -0
- package/dist/components/ui/command/command-list.svelte +18 -0
- package/dist/components/ui/command/command-list.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-loading.svelte +12 -0
- package/dist/components/ui/command/command-loading.svelte.d.ts +4 -0
- package/dist/components/ui/command/command-separator.svelte +23 -0
- package/dist/components/ui/command/command-separator.svelte.d.ts +7 -0
- package/dist/components/ui/command/command.svelte +26 -0
- package/dist/components/ui/command/command.svelte.d.ts +4 -0
- package/dist/components/ui/command/index.d.ts +55 -2
- package/dist/components/ui/command/index.js +21 -2
- package/dist/components/ui/sheet/sheet-content.svelte +1 -4
- package/dist/components/ui/sheet/sheet-content.svelte.d.ts +1 -3
- package/dist/components/ui/tabs/tabs-content.svelte +6 -2
- package/dist/components/ui/tabs/tabs-context.svelte.d.ts +5 -0
- package/dist/components/ui/tabs/tabs-context.svelte.js +2 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte +49 -4
- package/dist/components/ui/tabs/tabs.svelte +12 -1
- package/dist/components/ui/tooltip/index.d.ts +4 -1
- package/dist/components/ui/tooltip/index.js +3 -1
- package/dist/components/ui/tooltip/tooltip-content.svelte +308 -35
- package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +42 -3
- package/dist/components/ui/tooltip/tooltip-context.svelte.d.ts +37 -0
- package/dist/components/ui/tooltip/tooltip-context.svelte.js +220 -0
- package/dist/components/ui/tooltip/tooltip-layer-registry.svelte.d.ts +11 -0
- package/dist/components/ui/tooltip/tooltip-layer-registry.svelte.js +93 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte +56 -3
- package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +23 -2
- package/dist/components/ui/tooltip/tooltip.svelte +63 -3
- package/dist/components/ui/tooltip/tooltip.svelte.d.ts +23 -2
- package/dist/router-state.svelte.d.ts +13 -0
- package/dist/router-state.svelte.js +72 -37
- package/package.json +14 -11
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import { Shield, AlertCircle, Check } from '@lucide/svelte';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
4
|
import type { RoleInfo, ResourceInfo, ActionInfo } from '../types.js';
|
|
5
|
-
import {
|
|
5
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
6
|
+
|
|
7
|
+
const i18n = useTranslation();
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
let {
|
|
8
11
|
// Basic metadata lists
|
|
@@ -57,9 +60,9 @@
|
|
|
57
60
|
<div>
|
|
58
61
|
<h1 class="text-2xl font-bold text-foreground flex items-center gap-2">
|
|
59
62
|
<Shield class="w-6 h-6 text-primary" />
|
|
60
|
-
{t('permissions.title') || 'Permission Matrix'}
|
|
63
|
+
{i18n.t('permissions.title') || 'Permission Matrix'}
|
|
61
64
|
</h1>
|
|
62
|
-
<p class="text-muted-foreground mt-1 text-sm">{t('permissions.description') || 'Configure authorization rules for system modules and roles.'}</p>
|
|
65
|
+
<p class="text-muted-foreground mt-1 text-sm">{i18n.t('permissions.description') || 'Configure authorization rules for system modules and roles.'}</p>
|
|
63
66
|
</div>
|
|
64
67
|
|
|
65
68
|
<!-- Toast Feedback -->
|
|
@@ -78,7 +81,7 @@
|
|
|
78
81
|
<div class="flex flex-col md:flex-row gap-6 flex-1 min-h-[500px]">
|
|
79
82
|
<!-- Left: Role Selector Sidebar -->
|
|
80
83
|
<div class="w-full md:w-64 flex-shrink-0 bg-card rounded-xl shadow-sm border border-border p-4 flex flex-col">
|
|
81
|
-
<h2 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2">{t('permissions.roles') || 'Roles'}</h2>
|
|
84
|
+
<h2 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2">{i18n.t('permissions.roles') || 'Roles'}</h2>
|
|
82
85
|
<div class="space-y-1 flex-1 overflow-y-auto">
|
|
83
86
|
{#each roles as role, _i (_i)}
|
|
84
87
|
<button
|
|
@@ -90,7 +93,7 @@
|
|
|
90
93
|
</button>
|
|
91
94
|
{/each}
|
|
92
95
|
{#if roles.length === 0}
|
|
93
|
-
<p class="text-center text-muted-foreground text-sm p-4">{t('permissions.noRoles') || 'No roles available'}</p>
|
|
96
|
+
<p class="text-center text-muted-foreground text-sm p-4">{i18n.t('permissions.noRoles') || 'No roles available'}</p>
|
|
94
97
|
{/if}
|
|
95
98
|
</div>
|
|
96
99
|
|
|
@@ -108,12 +111,12 @@
|
|
|
108
111
|
<div class="p-5 border-b border-border bg-muted/30 flex items-center justify-between">
|
|
109
112
|
<div class="flex items-center gap-4">
|
|
110
113
|
<div>
|
|
111
|
-
<h2 class="text-lg font-bold text-foreground">{t('permissions.currentRole') || 'Current role'}:
|
|
112
|
-
<span class="text-primary">{roles.find(r => r.code === selectedRole)?.name || (t('permissions.noneSelected') || 'None selected')}</span>
|
|
114
|
+
<h2 class="text-lg font-bold text-foreground">{i18n.t('permissions.currentRole') || 'Current role'}:
|
|
115
|
+
<span class="text-primary">{roles.find(r => r.code === selectedRole)?.name || (i18n.t('permissions.noneSelected') || 'None selected')}</span>
|
|
113
116
|
</h2>
|
|
114
|
-
<p class="text-xs text-muted-foreground mt-1">{t('permissions.toggleHint') || 'Toggle checkboxes to grant or revoke permissions'}</p>
|
|
117
|
+
<p class="text-xs text-muted-foreground mt-1">{i18n.t('permissions.toggleHint') || 'Toggle checkboxes to grant or revoke permissions'}</p>
|
|
115
118
|
</div>
|
|
116
|
-
|
|
119
|
+
|
|
117
120
|
<!-- Slot for header extra logic -->
|
|
118
121
|
{#if headerExtra}
|
|
119
122
|
{@render headerExtra()}
|
|
@@ -122,7 +125,7 @@
|
|
|
122
125
|
|
|
123
126
|
{#if conflictCount > 0}
|
|
124
127
|
<div class="px-3 py-1.5 bg-destructive/10 text-destructive rounded-lg text-xs font-medium ring-1 ring-destructive/20 flex items-center gap-1.5">
|
|
125
|
-
<AlertCircle class="w-4 h-4" /> {conflictCount} {t('permissions.conflicts') || 'conflicting rules'}
|
|
128
|
+
<AlertCircle class="w-4 h-4" /> {conflictCount} {i18n.t('permissions.conflicts') || 'conflicting rules'}
|
|
126
129
|
</div>
|
|
127
130
|
{/if}
|
|
128
131
|
</div>
|
|
@@ -132,7 +135,7 @@
|
|
|
132
135
|
<table class="min-w-full rounded-lg overflow-hidden shadow-sm ring-1 ring-border/10">
|
|
133
136
|
<thead class="bg-muted/50">
|
|
134
137
|
<tr>
|
|
135
|
-
<th scope="col" class="py-3 pr-3 pl-4 text-left text-sm font-semibold text-foreground w-48">{t('permissions.resources') || 'Resources'}</th>
|
|
138
|
+
<th scope="col" class="py-3 pr-3 pl-4 text-left text-sm font-semibold text-foreground w-48">{i18n.t('permissions.resources') || 'Resources'}</th>
|
|
136
139
|
{#each actions as action, _i (_i)}
|
|
137
140
|
<th scope="col" class="px-2 py-3 text-center text-sm font-semibold text-foreground">
|
|
138
141
|
<div class="flex flex-col items-center">
|
|
@@ -146,7 +149,7 @@
|
|
|
146
149
|
<tbody class="bg-card">
|
|
147
150
|
{#if resources.length === 0}
|
|
148
151
|
<tr>
|
|
149
|
-
<td colspan={actions.length + 1} class="py-10 text-center text-muted-foreground">{t('permissions.noResources') || 'No resources available'}</td>
|
|
152
|
+
<td colspan={actions.length + 1} class="py-10 text-center text-muted-foreground">{i18n.t('permissions.noResources') || 'No resources available'}</td>
|
|
150
153
|
</tr>
|
|
151
154
|
{/if}
|
|
152
155
|
{#each resources as resource, i (i)}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useGetIdentity, useUpdatePassword, getAuthProvider } from '@svadmin/core';
|
|
3
|
-
import {
|
|
3
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
4
4
|
import { Button } from './ui/button/index.js';
|
|
5
5
|
import { Label } from './ui/label/index.js';
|
|
6
6
|
import { Input } from './ui/input/index.js';
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import PasswordInput from './PasswordInput.svelte';
|
|
10
10
|
import { User, Mail, Lock, Loader2, AlertCircle, CheckCircle, Camera } from '@lucide/svelte';
|
|
11
11
|
|
|
12
|
+
const i18n = useTranslation();
|
|
13
|
+
|
|
12
14
|
const identity = useGetIdentity();
|
|
13
15
|
const updatePw = useUpdatePassword();
|
|
14
16
|
|
|
@@ -42,7 +44,7 @@
|
|
|
42
44
|
editingProfile = false;
|
|
43
45
|
identity.refetch();
|
|
44
46
|
} else {
|
|
45
|
-
profileError = result.error?.message ?? t('common.operationFailed');
|
|
47
|
+
profileError = result.error?.message ?? i18n.t('common.operationFailed');
|
|
46
48
|
}
|
|
47
49
|
} catch (e) {
|
|
48
50
|
profileError = (e as Error).message;
|
|
@@ -79,8 +81,8 @@
|
|
|
79
81
|
pwError = '';
|
|
80
82
|
pwSuccess = false;
|
|
81
83
|
|
|
82
|
-
if (!newPassword) { pwError = t('auth.passwordRequired'); return; }
|
|
83
|
-
if (newPassword !== confirmPassword) { pwError = t('auth.passwordMismatch'); return; }
|
|
84
|
+
if (!newPassword) { pwError = i18n.t('auth.passwordRequired'); return; }
|
|
85
|
+
if (newPassword !== confirmPassword) { pwError = i18n.t('auth.passwordMismatch'); return; }
|
|
84
86
|
|
|
85
87
|
const result = await updatePw.mutate({
|
|
86
88
|
password: newPassword,
|
|
@@ -93,7 +95,7 @@
|
|
|
93
95
|
newPassword = '';
|
|
94
96
|
confirmPassword = '';
|
|
95
97
|
} else {
|
|
96
|
-
pwError = result.error?.message ?? t('common.operationFailed');
|
|
98
|
+
pwError = result.error?.message ?? i18n.t('common.operationFailed');
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
|
|
@@ -107,7 +109,7 @@
|
|
|
107
109
|
|
|
108
110
|
<div class="space-y-6">
|
|
109
111
|
<div>
|
|
110
|
-
<h2 class="text-xl font-semibold text-foreground">{t('profile.title')}</h2>
|
|
112
|
+
<h2 class="text-xl font-semibold text-foreground">{i18n.t('profile.title')}</h2>
|
|
111
113
|
</div>
|
|
112
114
|
|
|
113
115
|
<!-- Profile Info -->
|
|
@@ -115,20 +117,20 @@
|
|
|
115
117
|
<Card.CardHeader>
|
|
116
118
|
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
117
119
|
<User class="h-4 w-4 text-muted-foreground" />
|
|
118
|
-
{t('profile.title')}
|
|
120
|
+
{i18n.t('profile.title')}
|
|
119
121
|
</Card.CardTitle>
|
|
120
122
|
</Card.CardHeader>
|
|
121
123
|
<Card.CardContent>
|
|
122
124
|
{#if identity.isLoading}
|
|
123
125
|
<div class="flex items-center gap-2 text-muted-foreground">
|
|
124
126
|
<Loader2 class="h-4 w-4 animate-spin" />
|
|
125
|
-
{t('common.loading')}
|
|
127
|
+
{i18n.t('common.loading')}
|
|
126
128
|
</div>
|
|
127
129
|
{:else if identity.data}
|
|
128
130
|
{#if profileSuccess}
|
|
129
131
|
<Alert.Root class="border-success/30 bg-success/5 text-success mb-4">
|
|
130
132
|
<CheckCircle class="h-4 w-4" />
|
|
131
|
-
<Alert.Description>{t('common.updateSuccess')}</Alert.Description>
|
|
133
|
+
<Alert.Description>{i18n.t('common.updateSuccess')}</Alert.Description>
|
|
132
134
|
</Alert.Root>
|
|
133
135
|
{/if}
|
|
134
136
|
|
|
@@ -173,7 +175,7 @@
|
|
|
173
175
|
{#if editingProfile}
|
|
174
176
|
<div class="space-y-3 max-w-sm">
|
|
175
177
|
<div>
|
|
176
|
-
<Label for="edit-name" class="text-xs text-muted-foreground">{t('profile.name')}</Label>
|
|
178
|
+
<Label for="edit-name" class="text-xs text-muted-foreground">{i18n.t('profile.name')}</Label>
|
|
177
179
|
<Input id="edit-name" bind:value={editName} class="mt-1" />
|
|
178
180
|
</div>
|
|
179
181
|
{#if profileError}
|
|
@@ -182,16 +184,16 @@
|
|
|
182
184
|
<div class="flex gap-2">
|
|
183
185
|
<Button size="sm" onclick={saveProfile} disabled={profileSaving}>
|
|
184
186
|
{#if profileSaving}<Loader2 class="h-3 w-3 animate-spin mr-1" />{/if}
|
|
185
|
-
{t('common.save')}
|
|
187
|
+
{i18n.t('common.save')}
|
|
186
188
|
</Button>
|
|
187
189
|
<Button size="sm" variant="outline" onclick={() => { editingProfile = false; }}>
|
|
188
|
-
{t('common.cancel')}
|
|
190
|
+
{i18n.t('common.cancel')}
|
|
189
191
|
</Button>
|
|
190
192
|
</div>
|
|
191
193
|
</div>
|
|
192
194
|
{:else}
|
|
193
195
|
<div>
|
|
194
|
-
<Label class="text-xs text-muted-foreground">{t('profile.name')}</Label>
|
|
196
|
+
<Label class="text-xs text-muted-foreground">{i18n.t('profile.name')}</Label>
|
|
195
197
|
<p class="text-lg font-semibold truncate">{identity.data.name ?? '—'}</p>
|
|
196
198
|
</div>
|
|
197
199
|
|
|
@@ -214,14 +216,14 @@
|
|
|
214
216
|
|
|
215
217
|
{#if canUpdateProfile}
|
|
216
218
|
<Button size="sm" variant="outline" onclick={startEditProfile}>
|
|
217
|
-
{t('common.edit')}
|
|
219
|
+
{i18n.t('common.edit')}
|
|
218
220
|
</Button>
|
|
219
221
|
{/if}
|
|
220
222
|
{/if}
|
|
221
223
|
</div>
|
|
222
224
|
</div>
|
|
223
225
|
{:else}
|
|
224
|
-
<p class="text-sm text-muted-foreground">{t('profile.notAvailable')}</p>
|
|
226
|
+
<p class="text-sm text-muted-foreground">{i18n.t('profile.notAvailable')}</p>
|
|
225
227
|
{/if}
|
|
226
228
|
</Card.CardContent>
|
|
227
229
|
</Card.Card>
|
|
@@ -231,7 +233,7 @@
|
|
|
231
233
|
<Card.CardHeader>
|
|
232
234
|
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
233
235
|
<Lock class="h-4 w-4 text-muted-foreground" />
|
|
234
|
-
{t('profile.changePassword')}
|
|
236
|
+
{i18n.t('profile.changePassword')}
|
|
235
237
|
</Card.CardTitle>
|
|
236
238
|
</Card.CardHeader>
|
|
237
239
|
<Card.CardContent>
|
|
@@ -246,20 +248,20 @@
|
|
|
246
248
|
{#if pwSuccess}
|
|
247
249
|
<Alert.Root class="border-success/30 bg-success/5 text-success">
|
|
248
250
|
<CheckCircle class="h-4 w-4" />
|
|
249
|
-
<Alert.Description>{t('profile.passwordChanged')}</Alert.Description>
|
|
251
|
+
<Alert.Description>{i18n.t('profile.passwordChanged')}</Alert.Description>
|
|
250
252
|
</Alert.Root>
|
|
251
253
|
{/if}
|
|
252
254
|
|
|
253
255
|
<PasswordInput
|
|
254
256
|
id="current-password"
|
|
255
|
-
label={t('profile.currentPassword')}
|
|
257
|
+
label={i18n.t('profile.currentPassword')}
|
|
256
258
|
bind:value={currentPassword}
|
|
257
259
|
autocomplete="current-password"
|
|
258
260
|
/>
|
|
259
261
|
|
|
260
262
|
<PasswordInput
|
|
261
263
|
id="new-password"
|
|
262
|
-
label={t('auth.password')}
|
|
264
|
+
label={i18n.t('auth.password')}
|
|
263
265
|
bind:value={newPassword}
|
|
264
266
|
autocomplete="new-password"
|
|
265
267
|
showStrength
|
|
@@ -267,7 +269,7 @@
|
|
|
267
269
|
|
|
268
270
|
<PasswordInput
|
|
269
271
|
id="confirm-new-password"
|
|
270
|
-
label={t('auth.confirmPassword')}
|
|
272
|
+
label={i18n.t('auth.confirmPassword')}
|
|
271
273
|
bind:value={confirmPassword}
|
|
272
274
|
autocomplete="new-password"
|
|
273
275
|
/>
|
|
@@ -276,7 +278,7 @@
|
|
|
276
278
|
{#if updatePw.isLoading}
|
|
277
279
|
<Loader2 class="h-4 w-4 animate-spin mr-2" />
|
|
278
280
|
{/if}
|
|
279
|
-
{t('profile.updatePassword')}
|
|
281
|
+
{i18n.t('profile.updatePassword')}
|
|
280
282
|
</Button>
|
|
281
283
|
</form>
|
|
282
284
|
</Card.CardContent>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { useRegister } from '@svadmin/core';
|
|
3
|
-
import {
|
|
4
|
-
import { navigate } from '@svadmin/core/router';
|
|
2
|
+
import { captureAdminContext, useRegister } from '@svadmin/core';
|
|
3
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
5
4
|
import { Button } from './ui/button/index.js';
|
|
6
5
|
import { Input } from './ui/input/index.js';
|
|
7
6
|
import { Label } from './ui/label/index.js';
|
|
@@ -9,11 +8,14 @@
|
|
|
9
8
|
import PasswordInput from './PasswordInput.svelte';
|
|
10
9
|
import { User, Loader2, AlertCircle, Shield } from '@lucide/svelte';
|
|
11
10
|
|
|
11
|
+
const i18n = useTranslation();
|
|
12
|
+
|
|
12
13
|
let { title = 'Admin', onSuccess } = $props<{
|
|
13
14
|
title?: string;
|
|
14
15
|
onSuccess?: () => void;
|
|
15
16
|
}>();
|
|
16
17
|
|
|
18
|
+
const adminContext = captureAdminContext();
|
|
17
19
|
const register = useRegister();
|
|
18
20
|
|
|
19
21
|
let identifier = $state('');
|
|
@@ -25,15 +27,15 @@
|
|
|
25
27
|
e.preventDefault();
|
|
26
28
|
error = '';
|
|
27
29
|
|
|
28
|
-
if (!identifier) { error = t('auth.usernameOrEmailRequired'); return; }
|
|
29
|
-
if (!password) { error = t('auth.passwordRequired'); return; }
|
|
30
|
-
if (password !== confirmPassword) { error = t('auth.passwordMismatch'); return; }
|
|
30
|
+
if (!identifier) { error = i18n.t('auth.usernameOrEmailRequired'); return; }
|
|
31
|
+
if (!password) { error = i18n.t('auth.passwordRequired'); return; }
|
|
32
|
+
if (password !== confirmPassword) { error = i18n.t('auth.passwordMismatch'); return; }
|
|
31
33
|
|
|
32
34
|
const result = await register.mutate({ email: identifier, username: identifier, password });
|
|
33
35
|
if (result.success) {
|
|
34
36
|
onSuccess?.();
|
|
35
37
|
} else {
|
|
36
|
-
error = result.error?.message ?? t('common.operationFailed');
|
|
38
|
+
error = result.error?.message ?? i18n.t('common.operationFailed');
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
</script>
|
|
@@ -59,10 +61,10 @@
|
|
|
59
61
|
|
|
60
62
|
<div class="space-y-4">
|
|
61
63
|
<h2 class="text-3xl xl:text-4xl font-bold text-white leading-tight">
|
|
62
|
-
{t('auth.createAccount')}
|
|
64
|
+
{i18n.t('auth.createAccount')}
|
|
63
65
|
</h2>
|
|
64
66
|
<p class="text-base text-white/70 leading-relaxed max-w-[320px]">
|
|
65
|
-
{t('auth.createAccountMessage')}
|
|
67
|
+
{i18n.t('auth.createAccountMessage')}
|
|
66
68
|
</p>
|
|
67
69
|
</div>
|
|
68
70
|
|
|
@@ -85,8 +87,8 @@
|
|
|
85
87
|
</div>
|
|
86
88
|
|
|
87
89
|
<div class="space-y-1 mb-8">
|
|
88
|
-
<h1 class="text-2xl font-semibold tracking-tight">{t('auth.createAccount')}</h1>
|
|
89
|
-
<p class="text-sm text-muted-foreground">{t('auth.createAccountMessage')}</p>
|
|
90
|
+
<h1 class="text-2xl font-semibold tracking-tight">{i18n.t('auth.createAccount')}</h1>
|
|
91
|
+
<p class="text-sm text-muted-foreground">{i18n.t('auth.createAccountMessage')}</p>
|
|
90
92
|
</div>
|
|
91
93
|
|
|
92
94
|
<form onsubmit={handleSubmit} class="space-y-5">
|
|
@@ -98,13 +100,13 @@
|
|
|
98
100
|
{/if}
|
|
99
101
|
|
|
100
102
|
<div class="space-y-2">
|
|
101
|
-
<Label for="register-identifier">{t('auth.usernameOrEmail')}</Label>
|
|
103
|
+
<Label for="register-identifier">{i18n.t('auth.usernameOrEmail')}</Label>
|
|
102
104
|
<div class="relative">
|
|
103
105
|
<User class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none z-[1]" />
|
|
104
106
|
<Input
|
|
105
107
|
id="register-identifier"
|
|
106
108
|
type="text"
|
|
107
|
-
placeholder={t('auth.identifierPlaceholder')}
|
|
109
|
+
placeholder={i18n.t('auth.identifierPlaceholder')}
|
|
108
110
|
bind:value={identifier}
|
|
109
111
|
class="pl-9"
|
|
110
112
|
autocomplete="username"
|
|
@@ -114,7 +116,7 @@
|
|
|
114
116
|
|
|
115
117
|
<PasswordInput
|
|
116
118
|
id="register-password"
|
|
117
|
-
label={t('auth.password')}
|
|
119
|
+
label={i18n.t('auth.password')}
|
|
118
120
|
bind:value={password}
|
|
119
121
|
autocomplete="new-password"
|
|
120
122
|
showStrength
|
|
@@ -122,7 +124,7 @@
|
|
|
122
124
|
|
|
123
125
|
<PasswordInput
|
|
124
126
|
id="register-confirm"
|
|
125
|
-
label={t('auth.confirmPassword')}
|
|
127
|
+
label={i18n.t('auth.confirmPassword')}
|
|
126
128
|
bind:value={confirmPassword}
|
|
127
129
|
autocomplete="new-password"
|
|
128
130
|
/>
|
|
@@ -131,14 +133,14 @@
|
|
|
131
133
|
{#if register.isLoading}
|
|
132
134
|
<Loader2 class="h-4 w-4 animate-spin mr-2" />
|
|
133
135
|
{/if}
|
|
134
|
-
{t('auth.registerButton')}
|
|
136
|
+
{i18n.t('auth.registerButton')}
|
|
135
137
|
</Button>
|
|
136
138
|
</form>
|
|
137
139
|
|
|
138
140
|
<div class="flex items-center justify-center gap-1 mt-8 pt-6 border-t">
|
|
139
|
-
<span class="text-sm text-muted-foreground">{t('auth.hasAccount')}</span>
|
|
140
|
-
<Button variant="link" class="text-sm h-auto p-0 font-medium" onclick={() => navigate('/login')}>
|
|
141
|
-
{t('auth.login')}
|
|
141
|
+
<span class="text-sm text-muted-foreground">{i18n.t('auth.hasAccount')}</span>
|
|
142
|
+
<Button variant="link" class="text-sm h-auto p-0 font-medium" onclick={() => adminContext.navigate('/login')}>
|
|
143
|
+
{i18n.t('auth.login')}
|
|
142
144
|
</Button>
|
|
143
145
|
</div>
|
|
144
146
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { captureAdminContext } from '@svadmin/core';
|
|
3
3
|
import type { Component } from 'svelte';
|
|
4
4
|
import AutoTable from './AutoTable.svelte';
|
|
5
5
|
import { Badge } from './ui/badge/index.js';
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
highlightsLabel = 'Focus queue',
|
|
70
70
|
workspaceStyle = 'operations',
|
|
71
71
|
}: Props = $props();
|
|
72
|
+
const adminContext = captureAdminContext();
|
|
72
73
|
</script>
|
|
73
74
|
|
|
74
75
|
<div class="space-y-6" data-svadmin-resource-operations={resourceName} data-svadmin-workspace-style={workspaceStyle}>
|
|
@@ -86,7 +87,7 @@
|
|
|
86
87
|
<p class="mt-2 max-w-2xl text-sm text-muted-foreground">{description}</p>
|
|
87
88
|
</div>
|
|
88
89
|
</div>
|
|
89
|
-
<Button class="mt-6" onclick={() => navigate(`/${resourceName}/create`)}>
|
|
90
|
+
<Button class="mt-6" onclick={() => adminContext.navigate(`/${resourceName}/create`)}>
|
|
90
91
|
{actionLabel}
|
|
91
92
|
</Button>
|
|
92
93
|
</div>
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getTaskProvider } from '@svadmin/core';
|
|
3
3
|
import { useQueryClient } from '@tanstack/svelte-query';
|
|
4
|
-
import {
|
|
4
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
5
|
+
|
|
5
6
|
import { Button } from './ui/button/index.js';
|
|
6
7
|
import { Loader2, RotateCcw } from '@lucide/svelte';
|
|
7
8
|
|
|
9
|
+
const i18n = useTranslation();
|
|
10
|
+
|
|
8
11
|
let {
|
|
9
12
|
taskId,
|
|
10
13
|
taskProvider = getTaskProvider({ optional: true }),
|
|
@@ -55,6 +58,6 @@
|
|
|
55
58
|
{#if children}
|
|
56
59
|
{@render children()}
|
|
57
60
|
{:else}
|
|
58
|
-
{t('common.retry')}
|
|
61
|
+
{i18n.t('common.retry')}
|
|
59
62
|
{/if}
|
|
60
63
|
</Button>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
3
3
|
import { getAuthProvider, getResources } from '@svadmin/core';
|
|
4
4
|
import PermissionMatrix from './PermissionMatrix.svelte';
|
|
5
5
|
import type { RoleInfo, ResourceInfo, ActionInfo } from '../types.js';
|
|
6
6
|
import { toast } from '@svadmin/core/toast';
|
|
7
7
|
import { AlertCircle } from '@lucide/svelte';
|
|
8
8
|
|
|
9
|
+
const i18n = useTranslation();
|
|
10
|
+
|
|
9
11
|
const authProvider = getAuthProvider({ optional: true });
|
|
10
12
|
const rawResources = getResources();
|
|
11
13
|
|
|
@@ -29,15 +31,15 @@
|
|
|
29
31
|
);
|
|
30
32
|
|
|
31
33
|
let matrixActions = $state<ActionInfo[]>([
|
|
32
|
-
{ code: 'create', name: t('common.create') ?? 'Create' },
|
|
33
|
-
{ code: 'read', name: t('common.detail') ?? 'Read' },
|
|
34
|
-
{ code: 'update', name: t('common.edit') ?? 'Update' },
|
|
35
|
-
{ code: 'delete', name: t('common.delete') ?? 'Delete' },
|
|
34
|
+
{ code: 'create', name: i18n.t('common.create') ?? 'Create' },
|
|
35
|
+
{ code: 'read', name: i18n.t('common.detail') ?? 'Read' },
|
|
36
|
+
{ code: 'update', name: i18n.t('common.edit') ?? 'Update' },
|
|
37
|
+
{ code: 'delete', name: i18n.t('common.delete') ?? 'Delete' },
|
|
36
38
|
]);
|
|
37
39
|
|
|
38
40
|
async function loadRoles() {
|
|
39
41
|
if (!authProvider?.getRoles) {
|
|
40
|
-
error = t('settings.rbacNotSupported') ?? 'RBAC not supported by AuthProvider';
|
|
42
|
+
error = i18n.t('settings.rbacNotSupported') ?? 'RBAC not supported by AuthProvider';
|
|
41
43
|
loadingRoles = false;
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
@@ -111,7 +113,7 @@
|
|
|
111
113
|
if (result.success) {
|
|
112
114
|
// Success silently
|
|
113
115
|
} else {
|
|
114
|
-
throw new Error(result.error?.message ?? t('common.operationFailed'));
|
|
116
|
+
throw new Error(result.error?.message ?? i18n.t('common.operationFailed'));
|
|
115
117
|
}
|
|
116
118
|
} catch (e) {
|
|
117
119
|
toast.error((e as Error).message);
|
|
@@ -128,7 +130,7 @@
|
|
|
128
130
|
<div class="h-full flex items-center justify-center">
|
|
129
131
|
<div class="max-w-md p-6 border border-destructive/20 bg-destructive/10 text-destructive rounded-lg flex flex-col items-center gap-3 text-center">
|
|
130
132
|
<AlertCircle class="h-8 w-8" />
|
|
131
|
-
<h3 class="font-bold text-lg">{t('common.error') ?? 'Error'}</h3>
|
|
133
|
+
<h3 class="font-bold text-lg">{i18n.t('common.error') ?? 'Error'}</h3>
|
|
132
134
|
<p>{error}</p>
|
|
133
135
|
</div>
|
|
134
136
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { useTranslation } from '@svadmin/core/i18n';
|
|
3
|
+
|
|
3
4
|
import { AlertTriangle, CheckCircle2, KeyRound, Monitor, Shield, ShieldAlert, ShieldCheck, Smartphone, Trash2 } from '@lucide/svelte';
|
|
4
5
|
import type { Component } from 'svelte';
|
|
5
6
|
import * as Alert from './ui/alert/index.js';
|
|
@@ -9,6 +10,8 @@
|
|
|
9
10
|
import { Switch } from './ui/switch/index.js';
|
|
10
11
|
import PasswordInput from './PasswordInput.svelte';
|
|
11
12
|
|
|
13
|
+
const i18n = useTranslation();
|
|
14
|
+
|
|
12
15
|
interface SessionInfo {
|
|
13
16
|
id: string;
|
|
14
17
|
os: string;
|
|
@@ -39,19 +42,19 @@
|
|
|
39
42
|
passwordErrorMessage = '';
|
|
40
43
|
|
|
41
44
|
if (!currentPassword) {
|
|
42
|
-
passwordErrorMessage = t('profile.currentPassword');
|
|
45
|
+
passwordErrorMessage = i18n.t('profile.currentPassword');
|
|
43
46
|
return;
|
|
44
47
|
}
|
|
45
48
|
if (newPassword.length < 8) {
|
|
46
|
-
passwordErrorMessage = t('validation.minLength', { min: 8 });
|
|
49
|
+
passwordErrorMessage = i18n.t('validation.minLength', { min: 8 });
|
|
47
50
|
return;
|
|
48
51
|
}
|
|
49
52
|
if (newPassword !== confirmPassword) {
|
|
50
|
-
passwordErrorMessage = t('auth.passwordMismatch');
|
|
53
|
+
passwordErrorMessage = i18n.t('auth.passwordMismatch');
|
|
51
54
|
return;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
passwordSuccessMessage = t('profile.passwordChanged');
|
|
57
|
+
passwordSuccessMessage = i18n.t('profile.passwordChanged');
|
|
55
58
|
lastChanged = 'Just now';
|
|
56
59
|
currentPassword = '';
|
|
57
60
|
newPassword = '';
|
|
@@ -69,8 +72,8 @@
|
|
|
69
72
|
|
|
70
73
|
<div class="space-y-6">
|
|
71
74
|
<div>
|
|
72
|
-
<h2 class="text-xl font-semibold text-foreground">{t('settings.security')}</h2>
|
|
73
|
-
<p class="mt-1 text-sm text-muted-foreground">{t('settings.securityDescription')}</p>
|
|
75
|
+
<h2 class="text-xl font-semibold text-foreground">{i18n.t('settings.security')}</h2>
|
|
76
|
+
<p class="mt-1 text-sm text-muted-foreground">{i18n.t('settings.securityDescription')}</p>
|
|
74
77
|
</div>
|
|
75
78
|
|
|
76
79
|
<Card.Card>
|
|
@@ -81,16 +84,16 @@
|
|
|
81
84
|
{:else}
|
|
82
85
|
<ShieldAlert class="h-5 w-5 text-amber-500" />
|
|
83
86
|
{/if}
|
|
84
|
-
{t('security.twoFactorAuth')}
|
|
87
|
+
{i18n.t('security.twoFactorAuth')}
|
|
85
88
|
</Card.CardTitle>
|
|
86
|
-
<Card.CardDescription>{t('security.twoFactorDescription')}</Card.CardDescription>
|
|
89
|
+
<Card.CardDescription>{i18n.t('security.twoFactorDescription')}</Card.CardDescription>
|
|
87
90
|
</Card.CardHeader>
|
|
88
91
|
<Card.CardContent>
|
|
89
92
|
<div class="flex items-center justify-between gap-4 border-t pt-4">
|
|
90
93
|
<div class="space-y-1">
|
|
91
|
-
<Label for="two-factor" class="text-sm font-medium">{t('security.enable2fa')}</Label>
|
|
94
|
+
<Label for="two-factor" class="text-sm font-medium">{i18n.t('security.enable2fa')}</Label>
|
|
92
95
|
<p class="text-xs text-muted-foreground">
|
|
93
|
-
{is2faEnabled ? t('security.twoFactorActive') : t('security.twoFactorInactive')}
|
|
96
|
+
{is2faEnabled ? i18n.t('security.twoFactorActive') : i18n.t('security.twoFactorInactive')}
|
|
94
97
|
</p>
|
|
95
98
|
</div>
|
|
96
99
|
<Switch id="two-factor" bind:checked={is2faEnabled} />
|
|
@@ -102,9 +105,9 @@
|
|
|
102
105
|
<Card.CardHeader>
|
|
103
106
|
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
104
107
|
<KeyRound class="h-4 w-4 text-muted-foreground" />
|
|
105
|
-
{t('profile.changePassword')}
|
|
108
|
+
{i18n.t('profile.changePassword')}
|
|
106
109
|
</Card.CardTitle>
|
|
107
|
-
<Card.CardDescription>{t('security.passwordLastChanged', { time: lastChanged })}</Card.CardDescription>
|
|
110
|
+
<Card.CardDescription>{i18n.t('security.passwordLastChanged', { time: lastChanged })}</Card.CardDescription>
|
|
108
111
|
</Card.CardHeader>
|
|
109
112
|
<Card.CardContent>
|
|
110
113
|
<form onsubmit={handlePasswordChange} class="max-w-sm space-y-4">
|
|
@@ -121,11 +124,11 @@
|
|
|
121
124
|
</Alert.Root>
|
|
122
125
|
{/if}
|
|
123
126
|
|
|
124
|
-
<PasswordInput id="security-current-password" label={t('profile.currentPassword')} bind:value={currentPassword} autocomplete="current-password" />
|
|
125
|
-
<PasswordInput id="security-new-password" label={t('profile.newPassword')} bind:value={newPassword} autocomplete="new-password" showStrength />
|
|
126
|
-
<PasswordInput id="security-confirm-password" label={t('auth.confirmPassword')} bind:value={confirmPassword} autocomplete="new-password" />
|
|
127
|
+
<PasswordInput id="security-current-password" label={i18n.t('profile.currentPassword')} bind:value={currentPassword} autocomplete="current-password" />
|
|
128
|
+
<PasswordInput id="security-new-password" label={i18n.t('profile.newPassword')} bind:value={newPassword} autocomplete="new-password" showStrength />
|
|
129
|
+
<PasswordInput id="security-confirm-password" label={i18n.t('auth.confirmPassword')} bind:value={confirmPassword} autocomplete="new-password" />
|
|
127
130
|
|
|
128
|
-
<Button type="submit">{t('profile.updatePassword')}</Button>
|
|
131
|
+
<Button type="submit">{i18n.t('profile.updatePassword')}</Button>
|
|
129
132
|
</form>
|
|
130
133
|
</Card.CardContent>
|
|
131
134
|
</Card.Card>
|
|
@@ -135,12 +138,12 @@
|
|
|
135
138
|
<div class="space-y-1">
|
|
136
139
|
<Card.CardTitle class="flex items-center gap-2 text-base">
|
|
137
140
|
<Shield class="h-4 w-4 text-muted-foreground" />
|
|
138
|
-
{t('security.activeSessions')}
|
|
141
|
+
{i18n.t('security.activeSessions')}
|
|
139
142
|
</Card.CardTitle>
|
|
140
|
-
<Card.CardDescription>{t('security.sessionsDescription')}</Card.CardDescription>
|
|
143
|
+
<Card.CardDescription>{i18n.t('security.sessionsDescription')}</Card.CardDescription>
|
|
141
144
|
</div>
|
|
142
145
|
{#if sessions.length > 1}
|
|
143
|
-
<Button variant="outline" size="sm" onclick={revokeAllOthers}>{t('security.revokeOthers')}</Button>
|
|
146
|
+
<Button variant="outline" size="sm" onclick={revokeAllOthers}>{i18n.t('security.revokeOthers')}</Button>
|
|
144
147
|
{/if}
|
|
145
148
|
</Card.CardHeader>
|
|
146
149
|
<Card.CardContent>
|
|
@@ -155,7 +158,7 @@
|
|
|
155
158
|
<div class="flex flex-wrap items-center gap-2">
|
|
156
159
|
<span class="text-sm font-medium">{session.os} - {session.browser}</span>
|
|
157
160
|
{#if session.isCurrent}
|
|
158
|
-
<span class="rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold text-primary">{t('security.currentSession')}</span>
|
|
161
|
+
<span class="rounded bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold text-primary">{i18n.t('security.currentSession')}</span>
|
|
159
162
|
{/if}
|
|
160
163
|
</div>
|
|
161
164
|
<div class="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
|
@@ -165,13 +168,13 @@
|
|
|
165
168
|
</div>
|
|
166
169
|
</div>
|
|
167
170
|
{#if !session.isCurrent}
|
|
168
|
-
<Button variant="ghost" size="icon-sm" onclick={() => revokeSession(session.id)} title={t('security.revokeSession')}>
|
|
171
|
+
<Button variant="ghost" size="icon-sm" onclick={() => revokeSession(session.id)} title={i18n.t('security.revokeSession')}>
|
|
169
172
|
<Trash2 class="h-4 w-4" />
|
|
170
173
|
</Button>
|
|
171
174
|
{/if}
|
|
172
175
|
</div>
|
|
173
176
|
{:else}
|
|
174
|
-
<div class="py-6 text-center text-sm text-muted-foreground">{t('security.noSessions')}</div>
|
|
177
|
+
<div class="py-6 text-center text-sm text-muted-foreground">{i18n.t('security.noSessions')}</div>
|
|
175
178
|
{/each}
|
|
176
179
|
</div>
|
|
177
180
|
</Card.CardContent>
|