@svadmin/ui 0.38.2 → 0.38.5

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 (144) hide show
  1. package/dist/app.css +8 -4
  2. package/dist/app.theme.css +8 -4
  3. package/dist/components/AICommandBar.svelte +16 -15
  4. package/dist/components/AboutSettings.svelte +12 -11
  5. package/dist/components/AdminApp.svelte +144 -70
  6. package/dist/components/AdminApp.svelte.d.ts +6 -3
  7. package/dist/components/ApiSettings.svelte +19 -16
  8. package/dist/components/AppearanceSettings.svelte +18 -16
  9. package/dist/components/AuditLogDrawer.svelte +6 -3
  10. package/dist/components/AuditLogViewer.svelte +18 -15
  11. package/dist/components/Authenticated.svelte +4 -4
  12. package/dist/components/AutoForm.svelte +19 -16
  13. package/dist/components/AutoSaveIndicator.svelte +7 -5
  14. package/dist/components/AutoTable.svelte +85 -71
  15. package/dist/components/AutoTable.svelte.d.ts +1 -1
  16. package/dist/components/Breadcrumbs.svelte +17 -13
  17. package/dist/components/CancelTaskButton.svelte +5 -2
  18. package/dist/components/CatchAllNavigate.svelte +3 -3
  19. package/dist/components/ChatDialog.svelte +22 -19
  20. package/dist/components/ComboboxField.svelte +6 -4
  21. package/dist/components/CommandPalette.svelte +20 -20
  22. package/dist/components/ConfigErrorScreen.svelte +12 -10
  23. package/dist/components/ConfirmDialog.svelte +6 -4
  24. package/dist/components/CreatePage.svelte +4 -2
  25. package/dist/components/DevTools.svelte +11 -9
  26. package/dist/components/DrawerForm.svelte +5 -2
  27. package/dist/components/EditPage.svelte +8 -5
  28. package/dist/components/EmptyState.svelte +5 -3
  29. package/dist/components/ErrorBoundary.svelte +5 -3
  30. package/dist/components/ErrorComponent.svelte +6 -4
  31. package/dist/components/ErrorPage.svelte +12 -10
  32. package/dist/components/FieldRenderer.svelte +17 -15
  33. package/dist/components/ForgotPasswordPage.svelte +22 -19
  34. package/dist/components/Header.svelte +7 -4
  35. package/dist/components/InfiniteList.svelte +6 -3
  36. package/dist/components/IntegrationsSettings.svelte +9 -6
  37. package/dist/components/KeyboardShortcuts.svelte +4 -2
  38. package/dist/components/Layout.svelte +12 -13
  39. package/dist/components/LazyPage.svelte +47 -0
  40. package/dist/components/LazyPage.svelte.d.ts +14 -0
  41. package/dist/components/ListPage.svelte +8 -5
  42. package/dist/components/LoginPage.svelte +24 -22
  43. package/dist/components/MarkdownRenderer.svelte +89 -35
  44. package/dist/components/ModalForm.svelte +4 -2
  45. package/dist/components/NavigateToResource.svelte +3 -3
  46. package/dist/components/NotificationsSettings.svelte +26 -24
  47. package/dist/components/PasswordInput.svelte +4 -2
  48. package/dist/components/PermissionMatrix.svelte +15 -12
  49. package/dist/components/ProfilePage.svelte +23 -21
  50. package/dist/components/RegisterPage.svelte +21 -19
  51. package/dist/components/ResourceOperationsPage.svelte +3 -2
  52. package/dist/components/RetryTaskButton.svelte +5 -2
  53. package/dist/components/RolesSettings.svelte +10 -8
  54. package/dist/components/SecuritySettings.svelte +26 -23
  55. package/dist/components/SettingsPage.svelte +14 -11
  56. package/dist/components/ShowPage.svelte +9 -7
  57. package/dist/components/Sidebar.svelte +36 -28
  58. package/dist/components/SidebarItem.svelte +4 -4
  59. package/dist/components/SidebarItem.svelte.d.ts +1 -1
  60. package/dist/components/StepsForm.svelte +11 -8
  61. package/dist/components/TaskDetails.svelte +14 -11
  62. package/dist/components/TaskList.svelte +14 -11
  63. package/dist/components/TaskQueueDrawer.svelte +34 -32
  64. package/dist/components/TaskStatusBadge.svelte +5 -2
  65. package/dist/components/Toast.svelte +18 -4
  66. package/dist/components/TooltipButton.svelte +25 -3
  67. package/dist/components/TwoFactorAuthPage.svelte +26 -24
  68. package/dist/components/UndoableNotification.svelte +5 -2
  69. package/dist/components/UpdatePasswordPage.svelte +18 -15
  70. package/dist/components/VirtualTable.svelte +0 -4
  71. package/dist/components/account/CompanyProfilePage.svelte +13 -11
  72. package/dist/components/account/GetStartedPage.svelte +15 -13
  73. package/dist/components/account/ImportMembersPage.svelte +14 -12
  74. package/dist/components/account/MembersStarterPage.svelte +10 -8
  75. package/dist/components/account/SecurityLogPage.svelte +24 -22
  76. package/dist/components/account/SettingsEnterprisePage.svelte +6 -4
  77. package/dist/components/account/SettingsPlainPage.svelte +14 -12
  78. package/dist/components/account/SettingsSidebarPage.svelte +11 -9
  79. package/dist/components/account/TeamMembersPage.svelte +10 -8
  80. package/dist/components/buttons/CloneButton.svelte +4 -2
  81. package/dist/components/buttons/CreateButton.svelte +4 -2
  82. package/dist/components/buttons/DeleteButton.svelte +6 -4
  83. package/dist/components/buttons/EditButton.svelte +4 -2
  84. package/dist/components/buttons/ExportButton.svelte +4 -2
  85. package/dist/components/buttons/ImportButton.svelte +4 -2
  86. package/dist/components/buttons/RefreshButton.svelte +4 -2
  87. package/dist/components/buttons/SaveButton.svelte +4 -2
  88. package/dist/components/buttons/ShowButton.svelte +4 -2
  89. package/dist/components/fieldComponentMap.js +0 -3
  90. package/dist/components/fields/ArrayField.svelte +6 -4
  91. package/dist/components/fields/JsonField.svelte +5 -3
  92. package/dist/components/fields/RelationField.svelte +3 -3
  93. package/dist/components/fields/RichTextField.svelte +4 -2
  94. package/dist/components/network/TeamCrewTablePage.svelte +18 -16
  95. package/dist/components/network/UserCardsNFTPage.svelte +11 -9
  96. package/dist/components/profile/ActivityTimeline.svelte +10 -8
  97. package/dist/components/profile/ProfileCard.svelte +13 -11
  98. package/dist/components/profile/ProjectsGrid.svelte +7 -5
  99. package/dist/components/profile/PublicProfilePage.svelte +18 -16
  100. package/dist/components/profile/TeamsShowcase.svelte +7 -5
  101. package/dist/components/toast-host.svelte.d.ts +5 -0
  102. package/dist/components/toast-host.svelte.js +21 -0
  103. package/dist/components/ui/command/command-dialog.svelte +29 -13
  104. package/dist/components/ui/command/command-dialog.svelte.d.ts +4 -3
  105. package/dist/components/ui/command/command-empty.svelte +18 -0
  106. package/dist/components/ui/command/command-empty.svelte.d.ts +4 -0
  107. package/dist/components/ui/command/command-group.svelte +41 -0
  108. package/dist/components/ui/command/command-group.svelte.d.ts +8 -0
  109. package/dist/components/ui/command/command-input.svelte +23 -0
  110. package/dist/components/ui/command/command-input.svelte.d.ts +4 -0
  111. package/dist/components/ui/command/command-item.svelte +43 -0
  112. package/dist/components/ui/command/command-item.svelte.d.ts +11 -0
  113. package/dist/components/ui/command/command-list.svelte +18 -0
  114. package/dist/components/ui/command/command-list.svelte.d.ts +4 -0
  115. package/dist/components/ui/command/command-loading.svelte +12 -0
  116. package/dist/components/ui/command/command-loading.svelte.d.ts +4 -0
  117. package/dist/components/ui/command/command-separator.svelte +23 -0
  118. package/dist/components/ui/command/command-separator.svelte.d.ts +7 -0
  119. package/dist/components/ui/command/command.svelte +26 -0
  120. package/dist/components/ui/command/command.svelte.d.ts +4 -0
  121. package/dist/components/ui/command/index.d.ts +55 -2
  122. package/dist/components/ui/command/index.js +21 -2
  123. package/dist/components/ui/sheet/sheet-content.svelte +1 -4
  124. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +1 -3
  125. package/dist/components/ui/tabs/tabs-content.svelte +6 -2
  126. package/dist/components/ui/tabs/tabs-context.svelte.d.ts +5 -0
  127. package/dist/components/ui/tabs/tabs-context.svelte.js +2 -0
  128. package/dist/components/ui/tabs/tabs-trigger.svelte +49 -4
  129. package/dist/components/ui/tabs/tabs.svelte +12 -1
  130. package/dist/components/ui/tooltip/index.d.ts +4 -1
  131. package/dist/components/ui/tooltip/index.js +3 -1
  132. package/dist/components/ui/tooltip/tooltip-content.svelte +308 -35
  133. package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +42 -3
  134. package/dist/components/ui/tooltip/tooltip-context.svelte.d.ts +37 -0
  135. package/dist/components/ui/tooltip/tooltip-context.svelte.js +220 -0
  136. package/dist/components/ui/tooltip/tooltip-layer-registry.svelte.d.ts +11 -0
  137. package/dist/components/ui/tooltip/tooltip-layer-registry.svelte.js +93 -0
  138. package/dist/components/ui/tooltip/tooltip-trigger.svelte +56 -3
  139. package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +23 -2
  140. package/dist/components/ui/tooltip/tooltip.svelte +63 -3
  141. package/dist/components/ui/tooltip/tooltip.svelte.d.ts +23 -2
  142. package/dist/router-state.svelte.d.ts +13 -0
  143. package/dist/router-state.svelte.js +72 -37
  144. package/package.json +13 -11
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { useGetIdentity, useUpdatePassword, getAuthProvider } from '@svadmin/core';
3
- import { t } from '@svadmin/core/i18n';
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 { t } from '@svadmin/core/i18n';
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 { navigate } from '@svadmin/core/router';
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 { t } from '@svadmin/core/i18n';
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 { t } from '@svadmin/core/i18n';
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 { t } from '@svadmin/core/i18n';
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>
@@ -1,7 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { Component, Snippet } from "svelte";
3
- import { t } from "@svadmin/core/i18n";
4
- import { navigate } from "@svadmin/core/router";
3
+ import { captureAdminContext, getAuthProvider } from "@svadmin/core";
4
+ import { useTranslation } from "@svadmin/core/i18n";
5
+
5
6
  import { User, Palette, Info, Shield, FileSearch, Lock, Puzzle, Bell, Key } from "@lucide/svelte";
6
7
  import ProfilePage from "./ProfilePage.svelte";
7
8
  import AppearanceSettings from "./AppearanceSettings.svelte";
@@ -12,8 +13,8 @@
12
13
  import IntegrationsSettings from "./IntegrationsSettings.svelte";
13
14
  import NotificationsSettings from "./NotificationsSettings.svelte";
14
15
  import ApiSettings from "./ApiSettings.svelte";
15
- import { getAuthProvider } from "@svadmin/core";
16
16
 
17
+ const adminContext = captureAdminContext();
17
18
  const authProvider = getAuthProvider({ optional: true });
18
19
 
19
20
  interface SettingsItem {
@@ -92,6 +93,8 @@
92
93
 
93
94
  import { getParams, getRoute } from "../router-state.svelte.js";
94
95
 
96
+ const i18n = useTranslation();
97
+
95
98
  let activeKey = $derived.by(() => {
96
99
  const tab = getParams().tab;
97
100
  if (!tab) return "profile";
@@ -103,7 +106,7 @@
103
106
  // Default redirect to profile
104
107
  $effect(() => {
105
108
  if (getRoute() === "/settings") {
106
- navigate("/settings/profile");
109
+ adminContext.navigate("/settings/profile");
107
110
  }
108
111
  });
109
112
  </script>
@@ -119,10 +122,10 @@
119
122
  <button
120
123
  class="flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md whitespace-nowrap transition-colors
121
124
  {active ? 'bg-background text-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground hover:bg-background/50'}"
122
- onclick={() => navigate(item.path)}
125
+ onclick={() => adminContext.navigate(item.path)}
123
126
  >
124
127
  <item.icon class="h-4 w-4" />
125
- {t(item.label)}
128
+ {i18n.t(item.label)}
126
129
  </button>
127
130
  {/each}
128
131
  {/each}
@@ -131,13 +134,13 @@
131
134
  <!-- Desktop: vertical nav with groups -->
132
135
  <div class="hidden lg:block py-6 px-3 space-y-6">
133
136
  <div class="px-3">
134
- <h2 class="text-lg font-semibold text-foreground">{t("settings.title")}</h2>
135
- <p class="text-xs text-muted-foreground mt-1">{t("settings.settingsDescription")}</p>
137
+ <h2 class="text-lg font-semibold text-foreground">{i18n.t("settings.title")}</h2>
138
+ <p class="text-xs text-muted-foreground mt-1">{i18n.t("settings.settingsDescription")}</p>
136
139
  </div>
137
140
  {#each resolvedSections as section, _i (_i)}
138
141
  <div>
139
142
  <h3 class="px-3 mb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
140
- {t(section.group)}
143
+ {i18n.t(section.group)}
141
144
  </h3>
142
145
  <div class="space-y-0.5">
143
146
  {#each section.items as item, _j (_j)}
@@ -147,10 +150,10 @@
147
150
  {active
148
151
  ? 'bg-primary/10 text-primary font-medium'
149
152
  : 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'}"
150
- onclick={() => navigate(item.path)}
153
+ onclick={() => adminContext.navigate(item.path)}
151
154
  >
152
155
  <item.icon class="h-4 w-4 shrink-0" />
153
- {t(item.label)}
156
+ {i18n.t(item.label)}
154
157
  </button>
155
158
  {/each}
156
159
  </div>
@@ -1,7 +1,6 @@
1
1
  <script lang="ts">
2
- import { useShow, getResource } from '@svadmin/core';
3
- import { navigate } from '@svadmin/core/router';
4
- import { t } from '@svadmin/core/i18n';
2
+ import { captureAdminContext, useShow, getResource } from '@svadmin/core';
3
+ import { useTranslation } from '@svadmin/core/i18n';
5
4
  import * as Card from './ui/card/index.js';
6
5
  import { Skeleton } from './ui/skeleton/index.js';
7
6
  import PageHeader from './PageHeader.svelte';
@@ -12,7 +11,10 @@
12
11
  import CloneButton from './buttons/CloneButton.svelte';
13
12
  import RefreshButton from './buttons/RefreshButton.svelte';
14
13
 
14
+ const i18n = useTranslation();
15
+
15
16
  let { resourceName, id, class: className = '' } = $props<{ resourceName: string; id: string | number; class?: string }>();
17
+ const adminContext = captureAdminContext();
16
18
 
17
19
  const resource = $derived(getResource(resourceName));
18
20
  const showFields = $derived(resource.fields.filter(f => f.showInShow !== false));
@@ -21,7 +23,7 @@
21
23
  </script>
22
24
 
23
25
  <div class="space-y-6 {className}">
24
- <PageHeader title="{resource.label} {t('common.detail')}">
26
+ <PageHeader title="{resource.label} {i18n.t('common.detail')}">
25
27
  {#snippet actions()}
26
28
  <ListButton resource={resourceName} hideText />
27
29
  {#if resource.canEdit !== false}
@@ -31,7 +33,7 @@
31
33
  <CloneButton resource={resourceName} recordItemId={id} hideText />
32
34
  {/if}
33
35
  {#if resource.canDelete !== false}
34
- <DeleteButton resource={resourceName} recordItemId={id} hideText onSuccess={() => navigate(`/${resourceName}`)} />
36
+ <DeleteButton resource={resourceName} recordItemId={id} hideText onSuccess={() => adminContext.navigate(`/${resourceName}`)} />
35
37
  {/if}
36
38
  <RefreshButton resource={resourceName} hideText />
37
39
  {/snippet}
@@ -74,9 +76,9 @@
74
76
  {:else}
75
77
  <Card.Root class="overflow-hidden border-border/40 shadow-sm">
76
78
  <Card.Content class="p-8 text-center">
77
- <p class="text-muted-foreground">{t('common.noData')}</p>
79
+ <p class="text-muted-foreground">{i18n.t('common.noData')}</p>
78
80
  {#if query.isError}
79
- <p class="text-sm text-destructive mt-2">{(query.error as Error)?.message ?? t('common.operationFailed')}</p>
81
+ <p class="text-sm text-destructive mt-2">{(query.error as Error)?.message ?? i18n.t('common.operationFailed')}</p>
80
82
  {/if}
81
83
  </Card.Content>
82
84
  </Card.Root>