@svadmin/ui 0.29.0 → 0.29.2

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 (224) hide show
  1. package/package.json +20 -9
  2. package/src/README.md +0 -18
  3. package/src/actions.ts +0 -110
  4. package/src/app.css +0 -519
  5. package/src/component-registry.svelte.ts +0 -60
  6. package/src/components/AICommandBar.svelte +0 -180
  7. package/src/components/AboutSettings.svelte +0 -106
  8. package/src/components/AdminApp.svelte +0 -199
  9. package/src/components/AnimatedCounter.svelte +0 -46
  10. package/src/components/AnomalyBadge.svelte +0 -65
  11. package/src/components/AppearanceSettings.svelte +0 -183
  12. package/src/components/AuditLogDrawer.svelte +0 -93
  13. package/src/components/AuditLogViewer.svelte +0 -226
  14. package/src/components/Authenticated.svelte +0 -51
  15. package/src/components/AutoForm.svelte +0 -242
  16. package/src/components/AutoSaveIndicator.svelte +0 -35
  17. package/src/components/AutoTable.svelte +0 -839
  18. package/src/components/Breadcrumbs.svelte +0 -58
  19. package/src/components/CanAccess.svelte +0 -23
  20. package/src/components/CatchAllNavigate.svelte +0 -21
  21. package/src/components/ChatDialog.svelte +0 -535
  22. package/src/components/ComboboxField.svelte +0 -129
  23. package/src/components/CommandPalette.svelte +0 -135
  24. package/src/components/ConfigErrorScreen.svelte +0 -126
  25. package/src/components/ConfirmDialog.svelte +0 -53
  26. package/src/components/CopilotPanel.svelte +0 -163
  27. package/src/components/CreatePage.svelte +0 -39
  28. package/src/components/DevTools.svelte +0 -149
  29. package/src/components/DraggableGrid.svelte +0 -86
  30. package/src/components/DraggableHeader.svelte +0 -112
  31. package/src/components/DrawerForm.svelte +0 -35
  32. package/src/components/EditPage.svelte +0 -52
  33. package/src/components/EmptyState.svelte +0 -31
  34. package/src/components/ErrorBoundary.svelte +0 -49
  35. package/src/components/ErrorComponent.svelte +0 -36
  36. package/src/components/FieldRenderer.svelte +0 -357
  37. package/src/components/ForgotPasswordPage.svelte +0 -112
  38. package/src/components/Header.svelte +0 -94
  39. package/src/components/InferencerPanel.svelte +0 -192
  40. package/src/components/InfiniteList.svelte +0 -93
  41. package/src/components/InlineEdit.svelte +0 -88
  42. package/src/components/InsightCard.svelte +0 -98
  43. package/src/components/KeyboardShortcuts.svelte +0 -61
  44. package/src/components/Layout.svelte +0 -167
  45. package/src/components/ListPage.svelte +0 -71
  46. package/src/components/LiveIndicator.svelte +0 -58
  47. package/src/components/LoginPage.svelte +0 -163
  48. package/src/components/MarkdownRenderer.svelte +0 -127
  49. package/src/components/ModalForm.svelte +0 -38
  50. package/src/components/NavigateToResource.svelte +0 -20
  51. package/src/components/PageHeader.svelte +0 -39
  52. package/src/components/PageSkeleton.svelte +0 -71
  53. package/src/components/PasswordInput.svelte +0 -96
  54. package/src/components/PermissionMatrix.svelte +0 -185
  55. package/src/components/ProfilePage.svelte +0 -284
  56. package/src/components/RegisterPage.svelte +0 -112
  57. package/src/components/RolesSettings.svelte +0 -146
  58. package/src/components/SettingsPage.svelte +0 -113
  59. package/src/components/ShowPage.svelte +0 -73
  60. package/src/components/Sidebar.svelte +0 -417
  61. package/src/components/SidebarItem.svelte +0 -129
  62. package/src/components/SmartSuggest.svelte +0 -158
  63. package/src/components/StatsCard.svelte +0 -79
  64. package/src/components/StepsForm.svelte +0 -192
  65. package/src/components/TaskQueueDrawer.svelte +0 -158
  66. package/src/components/TenantSwitcher.svelte +0 -84
  67. package/src/components/ThemedTitle.svelte +0 -32
  68. package/src/components/Toast.svelte +0 -46
  69. package/src/components/TooltipButton.svelte +0 -42
  70. package/src/components/UndoableNotification.svelte +0 -69
  71. package/src/components/UpdatePasswordPage.svelte +0 -89
  72. package/src/components/VirtualTable.svelte +0 -76
  73. package/src/components/VoiceInput.svelte +0 -120
  74. package/src/components/buttons/CloneButton.svelte +0 -30
  75. package/src/components/buttons/CreateButton.svelte +0 -29
  76. package/src/components/buttons/DeleteButton.svelte +0 -59
  77. package/src/components/buttons/EditButton.svelte +0 -30
  78. package/src/components/buttons/ExportButton.svelte +0 -30
  79. package/src/components/buttons/ImportButton.svelte +0 -56
  80. package/src/components/buttons/ListButton.svelte +0 -23
  81. package/src/components/buttons/RefreshButton.svelte +0 -36
  82. package/src/components/buttons/SaveButton.svelte +0 -27
  83. package/src/components/buttons/ShowButton.svelte +0 -31
  84. package/src/components/buttons/index.ts +0 -10
  85. package/src/components/charts/BarChart.svelte +0 -69
  86. package/src/components/charts/LineChart.svelte +0 -115
  87. package/src/components/charts/PieChart.svelte +0 -95
  88. package/src/components/charts/index.ts +0 -3
  89. package/src/components/fieldComponentMap.ts +0 -79
  90. package/src/components/fields/ArrayField.svelte +0 -86
  91. package/src/components/fields/BooleanField.svelte +0 -13
  92. package/src/components/fields/DateField.svelte +0 -6
  93. package/src/components/fields/EmailField.svelte +0 -9
  94. package/src/components/fields/FileField.svelte +0 -14
  95. package/src/components/fields/ImageField.svelte +0 -9
  96. package/src/components/fields/JsonField.svelte +0 -72
  97. package/src/components/fields/MarkdownField.svelte +0 -9
  98. package/src/components/fields/MultiSelectField.svelte +0 -21
  99. package/src/components/fields/NumberField.svelte +0 -6
  100. package/src/components/fields/RelationField.svelte +0 -24
  101. package/src/components/fields/RichTextField.svelte +0 -35
  102. package/src/components/fields/SelectField.svelte +0 -14
  103. package/src/components/fields/TagField.svelte +0 -12
  104. package/src/components/fields/TextField.svelte +0 -11
  105. package/src/components/fields/UrlField.svelte +0 -14
  106. package/src/components/ui/alert/alert-description.svelte +0 -17
  107. package/src/components/ui/alert/alert-title.svelte +0 -17
  108. package/src/components/ui/alert/alert.svelte +0 -44
  109. package/src/components/ui/alert/index.ts +0 -15
  110. package/src/components/ui/alert-dialog/alert-dialog-content.svelte +0 -29
  111. package/src/components/ui/alert-dialog/alert-dialog-description.svelte +0 -16
  112. package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +0 -11
  113. package/src/components/ui/alert-dialog/alert-dialog-header.svelte +0 -11
  114. package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +0 -16
  115. package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +0 -6
  116. package/src/components/ui/alert-dialog/alert-dialog-title.svelte +0 -16
  117. package/src/components/ui/alert-dialog/index.ts +0 -20
  118. package/src/components/ui/avatar/avatar.svelte +0 -63
  119. package/src/components/ui/avatar/index.ts +0 -2
  120. package/src/components/ui/badge/badge.svelte +0 -49
  121. package/src/components/ui/badge/index.ts +0 -3
  122. package/src/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +0 -18
  123. package/src/components/ui/breadcrumb/breadcrumb-item.svelte +0 -15
  124. package/src/components/ui/breadcrumb/breadcrumb-link.svelte +0 -15
  125. package/src/components/ui/breadcrumb/breadcrumb-list.svelte +0 -15
  126. package/src/components/ui/breadcrumb/breadcrumb-page.svelte +0 -18
  127. package/src/components/ui/breadcrumb/breadcrumb-separator.svelte +0 -22
  128. package/src/components/ui/breadcrumb/breadcrumb.svelte +0 -11
  129. package/src/components/ui/breadcrumb/index.ts +0 -7
  130. package/src/components/ui/button/button.svelte +0 -90
  131. package/src/components/ui/button/index.ts +0 -18
  132. package/src/components/ui/card/card-action.svelte +0 -23
  133. package/src/components/ui/card/card-content.svelte +0 -20
  134. package/src/components/ui/card/card-description.svelte +0 -20
  135. package/src/components/ui/card/card-footer.svelte +0 -20
  136. package/src/components/ui/card/card-header.svelte +0 -23
  137. package/src/components/ui/card/card-title.svelte +0 -15
  138. package/src/components/ui/card/card.svelte +0 -22
  139. package/src/components/ui/card/index.ts +0 -25
  140. package/src/components/ui/checkbox/checkbox.svelte +0 -39
  141. package/src/components/ui/checkbox/index.ts +0 -6
  142. package/src/components/ui/collapsible/index.ts +0 -7
  143. package/src/components/ui/command/command-dialog.svelte +0 -31
  144. package/src/components/ui/command/index.ts +0 -2
  145. package/src/components/ui/context-menu/index.ts +0 -11
  146. package/src/components/ui/dialog/dialog-close.svelte +0 -11
  147. package/src/components/ui/dialog/dialog-content.svelte +0 -48
  148. package/src/components/ui/dialog/dialog-description.svelte +0 -17
  149. package/src/components/ui/dialog/dialog-footer.svelte +0 -32
  150. package/src/components/ui/dialog/dialog-header.svelte +0 -20
  151. package/src/components/ui/dialog/dialog-overlay.svelte +0 -17
  152. package/src/components/ui/dialog/dialog-portal.svelte +0 -7
  153. package/src/components/ui/dialog/dialog-title.svelte +0 -17
  154. package/src/components/ui/dialog/dialog-trigger.svelte +0 -11
  155. package/src/components/ui/dialog/dialog.svelte +0 -7
  156. package/src/components/ui/dialog/index.ts +0 -34
  157. package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -43
  158. package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +0 -35
  159. package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +0 -36
  160. package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +0 -10
  161. package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +0 -27
  162. package/src/components/ui/dropdown-menu/dropdown-menu.svelte +0 -45
  163. package/src/components/ui/dropdown-menu/index.ts +0 -21
  164. package/src/components/ui/input/index.ts +0 -7
  165. package/src/components/ui/input/input.svelte +0 -48
  166. package/src/components/ui/label/index.ts +0 -1
  167. package/src/components/ui/label/label.svelte +0 -18
  168. package/src/components/ui/pagination/index.ts +0 -7
  169. package/src/components/ui/pagination/pagination-content.svelte +0 -11
  170. package/src/components/ui/pagination/pagination-ellipsis.svelte +0 -12
  171. package/src/components/ui/pagination/pagination-item.svelte +0 -11
  172. package/src/components/ui/pagination/pagination-link.svelte +0 -26
  173. package/src/components/ui/pagination/pagination-next.svelte +0 -22
  174. package/src/components/ui/pagination/pagination-previous.svelte +0 -22
  175. package/src/components/ui/pagination/pagination.svelte +0 -11
  176. package/src/components/ui/popover/index.ts +0 -12
  177. package/src/components/ui/popover/popover-content.svelte +0 -25
  178. package/src/components/ui/progress/index.ts +0 -1
  179. package/src/components/ui/progress/progress.svelte +0 -22
  180. package/src/components/ui/scroll-area/index.ts +0 -1
  181. package/src/components/ui/scroll-area/scroll-area.svelte +0 -20
  182. package/src/components/ui/select/index.ts +0 -2
  183. package/src/components/ui/select/select.svelte +0 -36
  184. package/src/components/ui/separator/index.ts +0 -7
  185. package/src/components/ui/separator/separator.svelte +0 -23
  186. package/src/components/ui/sheet/index.ts +0 -16
  187. package/src/components/ui/sheet/sheet-content.svelte +0 -28
  188. package/src/components/ui/sheet/sheet-description.svelte +0 -22
  189. package/src/components/ui/sheet/sheet-footer.svelte +0 -22
  190. package/src/components/ui/sheet/sheet-header.svelte +0 -22
  191. package/src/components/ui/sheet/sheet-title.svelte +0 -22
  192. package/src/components/ui/sheet/sheet.svelte +0 -78
  193. package/src/components/ui/skeleton/index.ts +0 -2
  194. package/src/components/ui/skeleton/skeleton.svelte +0 -21
  195. package/src/components/ui/switch/index.ts +0 -2
  196. package/src/components/ui/switch/switch.svelte +0 -49
  197. package/src/components/ui/table/index.ts +0 -28
  198. package/src/components/ui/table/table-body.svelte +0 -15
  199. package/src/components/ui/table/table-caption.svelte +0 -20
  200. package/src/components/ui/table/table-cell.svelte +0 -15
  201. package/src/components/ui/table/table-footer.svelte +0 -20
  202. package/src/components/ui/table/table-head.svelte +0 -15
  203. package/src/components/ui/table/table-header.svelte +0 -20
  204. package/src/components/ui/table/table-row.svelte +0 -15
  205. package/src/components/ui/table/table.svelte +0 -17
  206. package/src/components/ui/tabs/index.ts +0 -15
  207. package/src/components/ui/tabs/tabs-content.svelte +0 -30
  208. package/src/components/ui/tabs/tabs-list.svelte +0 -26
  209. package/src/components/ui/tabs/tabs-trigger.svelte +0 -37
  210. package/src/components/ui/tabs/tabs.svelte +0 -27
  211. package/src/components/ui/textarea/index.ts +0 -2
  212. package/src/components/ui/textarea/textarea.svelte +0 -24
  213. package/src/components/ui/tooltip/index.ts +0 -19
  214. package/src/components/ui/tooltip/tooltip-content.svelte +0 -52
  215. package/src/components/ui/tooltip/tooltip-portal.svelte +0 -7
  216. package/src/components/ui/tooltip/tooltip-provider.svelte +0 -7
  217. package/src/components/ui/tooltip/tooltip-trigger.svelte +0 -7
  218. package/src/components/ui/tooltip/tooltip.svelte +0 -10
  219. package/src/editor-config.svelte.ts +0 -23
  220. package/src/index.ts +0 -155
  221. package/src/markdown-deps.d.ts +0 -21
  222. package/src/router-state.svelte.ts +0 -65
  223. package/src/types.ts +0 -33
  224. package/src/utils.ts +0 -28
@@ -1,185 +0,0 @@
1
- <script lang="ts">
2
- import { Shield, AlertCircle, Check } from '@lucide/svelte';
3
- import type { Snippet } from 'svelte';
4
- import type { RoleInfo, ResourceInfo, ActionInfo } from '../types.js';
5
-
6
- let {
7
- // Basic metadata lists
8
- roles = [],
9
- resources = [],
10
- actions = [],
11
-
12
- // Authorization state resolver
13
- // isGranted(roleCode, resourceCode, actionCode) => boolean
14
- isGranted,
15
-
16
- // Interactive states
17
- selectedRole = $bindable(roles[0]?.code || ''),
18
- loading = false,
19
-
20
- // Feedback and alerts (passed down from upper business logic)
21
- message = '',
22
- messageType = 'success', // 'success' | 'error' | 'warning'
23
- conflictCount = 0,
24
-
25
- // Custom slots for injecting multi-tenant switchers or security filters
26
- sidebarExtra,
27
- headerExtra,
28
-
29
- // Callback event
30
- onToggle
31
- }: {
32
- roles: RoleInfo[];
33
- resources: ResourceInfo[];
34
- actions: ActionInfo[];
35
- isGranted: (role: string, resource: string, action: string) => boolean;
36
- selectedRole?: string;
37
- loading?: boolean;
38
- message?: string;
39
- messageType?: 'success' | 'error' | 'warning';
40
- conflictCount?: number;
41
- sidebarExtra?: Snippet;
42
- headerExtra?: Snippet;
43
- onToggle: (role: string, resource: string, action: string, grant: boolean) => void;
44
- } = $props();
45
-
46
- </script>
47
-
48
- <div class="h-full flex flex-col pt-2">
49
- <!-- Header Title -->
50
- <div class="mb-6 flex items-center justify-between">
51
- <div>
52
- <h1 class="text-2xl font-bold text-foreground flex items-center gap-2">
53
- <Shield class="w-6 h-6 text-primary" />
54
- 统一操作权限矩阵
55
- </h1>
56
- <p class="text-muted-foreground mt-1 text-sm">直观配置系统模块与角色的鉴权规则网络。</p>
57
- </div>
58
-
59
- <!-- Toast Feedback -->
60
- {#if message}
61
- <div class={`px-4 py-2 rounded-lg text-sm font-medium shadow-sm transition-all flex items-center gap-2 ${messageType === 'success' ? 'bg-green-500/10 text-green-600 dark:text-green-400 ring-1 ring-green-500/20' : 'bg-destructive/10 text-destructive ring-1 ring-destructive/20'}`}>
62
- {#if messageType === 'success'}
63
- <Check class="w-4 h-4" />
64
- {:else}
65
- <AlertCircle class="w-4 h-4" />
66
- {/if}
67
- {message}
68
- </div>
69
- {/if}
70
- </div>
71
-
72
- <div class="flex flex-col md:flex-row gap-6 flex-1 min-h-[500px]">
73
- <!-- Left: Role Selector Sidebar -->
74
- <div class="w-full md:w-64 flex-shrink-0 bg-card rounded-xl shadow-sm border border-border p-4 flex flex-col">
75
- <h2 class="text-sm font-semibold text-muted-foreground uppercase tracking-wider mb-4 px-2">业务受众角色</h2>
76
- <div class="space-y-1 flex-1 overflow-y-auto">
77
- {#each roles as role}
78
- <button
79
- onclick={() => selectedRole = role.code}
80
- class="w-full text-left px-3 py-2.5 rounded-lg text-sm transition-all duration-200 {selectedRole === role.code ? 'bg-primary/10 text-primary font-medium shadow-sm ring-1 ring-primary/20' : 'text-foreground hover:bg-muted'}"
81
- >
82
- {role.name}
83
- <div class="text-[10px] mt-0.5 opacity-60 font-mono tracking-tighter">{role.code}</div>
84
- </button>
85
- {/each}
86
- {#if roles.length === 0}
87
- <p class="text-center text-muted-foreground text-sm p-4">暂无角色</p>
88
- {/if}
89
- </div>
90
-
91
- <!-- Slot for Upper Logic inject (e.g. Org Switcher, Security Level Filter) -->
92
- {#if sidebarExtra}
93
- <div class="mt-4 pt-4 border-t border-border">
94
- {@render sidebarExtra()}
95
- </div>
96
- {/if}
97
- </div>
98
-
99
- <!-- Right: Matrix Grid -->
100
- <div class="flex-1 bg-card rounded-xl shadow-sm border border-border overflow-hidden flex flex-col">
101
- <!-- Matrix Header -->
102
- <div class="p-5 border-b border-border bg-muted/30 flex items-center justify-between">
103
- <div class="flex items-center gap-4">
104
- <div>
105
- <h2 class="text-lg font-bold text-foreground">当前视角:
106
- <span class="text-primary">{roles.find(r => r.code === selectedRole)?.name || '未选择'}</span>
107
- </h2>
108
- <p class="text-xs text-muted-foreground mt-1">勾选以即时挂载/收回操作权限</p>
109
- </div>
110
-
111
- <!-- Slot for header extra logic -->
112
- {#if headerExtra}
113
- {@render headerExtra()}
114
- {/if}
115
- </div>
116
-
117
- {#if conflictCount > 0}
118
- <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">
119
- <AlertCircle class="w-4 h-4" /> {conflictCount} 重叠/冲突规则
120
- </div>
121
- {/if}
122
- </div>
123
-
124
- <!-- Table Area -->
125
- <div class="flex-1 overflow-auto p-4">
126
- <table class="min-w-full rounded-lg overflow-hidden shadow-sm ring-1 ring-border/10">
127
- <thead class="bg-muted/50">
128
- <tr>
129
- <th scope="col" class="py-3 pr-3 pl-4 text-left text-sm font-semibold text-foreground w-48">模块域与资源点</th>
130
- {#each actions as action}
131
- <th scope="col" class="px-2 py-3 text-center text-sm font-semibold text-foreground">
132
- <div class="flex flex-col items-center">
133
- <span>{action.name}</span>
134
- <span class="text-[10px] text-muted-foreground font-mono">{action.code}</span>
135
- </div>
136
- </th>
137
- {/each}
138
- </tr>
139
- </thead>
140
- <tbody class="bg-card">
141
- {#if resources.length === 0}
142
- <tr>
143
- <td colspan={actions.length + 1} class="py-10 text-center text-muted-foreground">暂无可用资源</td>
144
- </tr>
145
- {/if}
146
- {#each resources as resource, i}
147
- <!-- Section Grouping Header -->
148
- {#if resource.section && (i === 0 || resource.section !== resources[i-1].section)}
149
- <tr class="bg-primary/5">
150
- <td colspan="{actions.length + 1}" class="px-4 py-2 text-xs font-bold text-primary uppercase tracking-wider">{resource.section}</td>
151
- </tr>
152
- {/if}
153
-
154
- <tr class="even:bg-muted/30 hover:bg-muted/50 transition-colors">
155
- <td class="whitespace-nowrap py-3 pl-4 pr-3 text-sm">
156
- <div class="font-medium text-foreground">{resource.name}</div>
157
- <div class="text-xs text-muted-foreground font-mono">{resource.code}</div>
158
- </td>
159
-
160
- <!-- Action Checkboxes -->
161
- {#each actions as action}
162
- {@const granted = selectedRole ? isGranted(selectedRole, resource.code, action.code) : false}
163
- <td class="whitespace-nowrap px-3 py-3 text-center">
164
- <div class="flex items-center justify-center">
165
- <button
166
- disabled={loading || !selectedRole}
167
- aria-label="Toggle {action.name}"
168
- onclick={() => onToggle(selectedRole!, resource.code, action.code, !granted)}
169
- class="w-6 h-6 rounded border flex items-center justify-center transition-all focus:outline-none focus:ring-2 focus:ring-primary/50 {granted ? 'bg-primary border-primary text-primary-foreground' : 'border-input bg-background hover:bg-accent'} {loading || !selectedRole ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}"
170
- >
171
- <svg class="w-3.5 h-3.5 {granted ? 'block' : 'hidden'}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
172
- <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
173
- </svg>
174
- </button>
175
- </div>
176
- </td>
177
- {/each}
178
- </tr>
179
- {/each}
180
- </tbody>
181
- </table>
182
- </div>
183
- </div>
184
- </div>
185
- </div>
@@ -1,284 +0,0 @@
1
- <script lang="ts">
2
- import { useGetIdentity, useUpdatePassword, getAuthProvider } from '@svadmin/core';
3
- import { t } from '@svadmin/core/i18n';
4
- import { Button } from './ui/button/index.js';
5
- import { Label } from './ui/label/index.js';
6
- import { Input } from './ui/input/index.js';
7
- import * as Card from './ui/card/index.js';
8
- import * as Alert from './ui/alert/index.js';
9
- import PasswordInput from './PasswordInput.svelte';
10
- import { User, Mail, Lock, Loader2, AlertCircle, CheckCircle, Camera } from '@lucide/svelte';
11
-
12
- const identity = useGetIdentity();
13
- const updatePw = useUpdatePassword();
14
-
15
- // Check if auth provider supports updateProfile
16
- let authProvider: ReturnType<typeof getAuthProvider> | null = null;
17
- try { authProvider = getAuthProvider(); } catch { /* no auth */ }
18
- const canUpdateProfile = !!authProvider?.updateProfile;
19
-
20
- // Profile edit state
21
- let editingProfile = $state(false);
22
- let editName = $state('');
23
- let profileSaving = $state(false);
24
- let profileSuccess = $state(false);
25
- let profileError = $state('');
26
-
27
- function startEditProfile() {
28
- editName = identity.data?.name ?? '';
29
- editingProfile = true;
30
- profileSuccess = false;
31
- profileError = '';
32
- }
33
-
34
- async function saveProfile() {
35
- if (!authProvider?.updateProfile) return;
36
- profileSaving = true;
37
- profileError = '';
38
- try {
39
- const result = await authProvider.updateProfile({ name: editName });
40
- if (result.success) {
41
- profileSuccess = true;
42
- editingProfile = false;
43
- identity.refetch();
44
- } else {
45
- profileError = result.error?.message ?? t('common.operationFailed');
46
- }
47
- } catch (e) {
48
- profileError = (e as Error).message;
49
- } finally {
50
- profileSaving = false;
51
- }
52
- }
53
-
54
- // Avatar upload
55
- let avatarUploading = $state(false);
56
- let fileInput = $state<HTMLInputElement>(null!);
57
-
58
- async function handleAvatarChange(e: Event) {
59
- const file = (e.target as HTMLInputElement).files?.[0];
60
- if (!file || !authProvider?.updateProfile) return;
61
- avatarUploading = true;
62
- try {
63
- const result = await authProvider.updateProfile({ avatar: file });
64
- if (result.success) identity.refetch();
65
- } finally {
66
- avatarUploading = false;
67
- }
68
- }
69
-
70
- // Password change form
71
- let currentPassword = $state('');
72
- let newPassword = $state('');
73
- let confirmPassword = $state('');
74
- let pwError = $state('');
75
- let pwSuccess = $state(false);
76
-
77
- async function handlePasswordChange(e: SubmitEvent) {
78
- e.preventDefault();
79
- pwError = '';
80
- pwSuccess = false;
81
-
82
- if (!newPassword) { pwError = t('auth.passwordRequired'); return; }
83
- if (newPassword !== confirmPassword) { pwError = t('auth.passwordMismatch'); return; }
84
-
85
- const result = await updatePw.mutate({
86
- password: newPassword,
87
- currentPassword,
88
- confirmPassword,
89
- });
90
- if (result.success) {
91
- pwSuccess = true;
92
- currentPassword = '';
93
- newPassword = '';
94
- confirmPassword = '';
95
- } else {
96
- pwError = result.error?.message ?? t('common.operationFailed');
97
- }
98
- }
99
-
100
- // Avatar initials
101
- const initials = $derived(
102
- identity.data?.name
103
- ? identity.data.name.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2)
104
- : '?'
105
- );
106
- </script>
107
-
108
- <div class="space-y-6">
109
- <div>
110
- <h2 class="text-xl font-semibold text-foreground">{t('profile.title')}</h2>
111
- </div>
112
-
113
- <!-- Profile Info -->
114
- <Card.Card>
115
- <Card.CardHeader>
116
- <Card.CardTitle class="flex items-center gap-2 text-base">
117
- <User class="h-4 w-4 text-muted-foreground" />
118
- {t('profile.title')}
119
- </Card.CardTitle>
120
- </Card.CardHeader>
121
- <Card.CardContent>
122
- {#if identity.isLoading}
123
- <div class="flex items-center gap-2 text-muted-foreground">
124
- <Loader2 class="h-4 w-4 animate-spin" />
125
- {t('common.loading')}
126
- </div>
127
- {:else if identity.data}
128
- {#if profileSuccess}
129
- <Alert.Root class="border-success/30 bg-success/5 text-success mb-4">
130
- <CheckCircle class="h-4 w-4" />
131
- <Alert.Description>{t('common.updateSuccess')}</Alert.Description>
132
- </Alert.Root>
133
- {/if}
134
-
135
- <div class="flex items-start gap-6">
136
- <!-- Avatar -->
137
- <div class="shrink-0 relative group">
138
- {#if identity.data.avatar}
139
- <img
140
- src={identity.data.avatar}
141
- alt={identity.data.name ?? ''}
142
- class="h-20 w-20 rounded-lg object-cover ring-2 ring-primary/20"
143
- />
144
- {:else}
145
- <div class="h-20 w-20 rounded-lg bg-primary/10 text-primary flex items-center justify-center text-2xl font-bold">
146
- {initials}
147
- </div>
148
- {/if}
149
- {#if canUpdateProfile}
150
- <button
151
- class="absolute inset-0 rounded-lg bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"
152
- onclick={() => fileInput.click()}
153
- disabled={avatarUploading}
154
- >
155
- {#if avatarUploading}
156
- <Loader2 class="h-5 w-5 text-white animate-spin" />
157
- {:else}
158
- <Camera class="h-5 w-5 text-white" />
159
- {/if}
160
- </button>
161
- <input
162
- bind:this={fileInput}
163
- type="file"
164
- accept="image/*"
165
- class="hidden"
166
- onchange={handleAvatarChange}
167
- />
168
- {/if}
169
- </div>
170
-
171
- <!-- Info -->
172
- <div class="space-y-3 flex-1 min-w-0">
173
- {#if editingProfile}
174
- <div class="space-y-3 max-w-sm">
175
- <div>
176
- <Label for="edit-name" class="text-xs text-muted-foreground">{t('profile.name')}</Label>
177
- <Input id="edit-name" bind:value={editName} class="mt-1" />
178
- </div>
179
- {#if profileError}
180
- <p class="text-xs text-destructive">{profileError}</p>
181
- {/if}
182
- <div class="flex gap-2">
183
- <Button size="sm" onclick={saveProfile} disabled={profileSaving}>
184
- {#if profileSaving}<Loader2 class="h-3 w-3 animate-spin mr-1" />{/if}
185
- {t('common.save')}
186
- </Button>
187
- <Button size="sm" variant="outline" onclick={() => { editingProfile = false; }}>
188
- {t('common.cancel')}
189
- </Button>
190
- </div>
191
- </div>
192
- {:else}
193
- <div>
194
- <Label class="text-xs text-muted-foreground">{t('profile.name')}</Label>
195
- <p class="text-lg font-semibold truncate">{identity.data.name ?? '—'}</p>
196
- </div>
197
-
198
- {#if identity.data.email || identity.data.username}
199
- <div class="flex items-center gap-2 text-sm text-muted-foreground">
200
- {#if identity.data.email}
201
- <Mail class="h-4 w-4 shrink-0" />
202
- {:else}
203
- <User class="h-4 w-4 shrink-0" />
204
- {/if}
205
- <span class="truncate">{identity.data.email || identity.data.username}</span>
206
- </div>
207
- {/if}
208
-
209
- {#if identity.data.id}
210
- <div class="text-xs text-muted-foreground/60 font-mono">
211
- ID: {identity.data.id}
212
- </div>
213
- {/if}
214
-
215
- {#if canUpdateProfile}
216
- <Button size="sm" variant="outline" onclick={startEditProfile}>
217
- {t('common.edit')}
218
- </Button>
219
- {/if}
220
- {/if}
221
- </div>
222
- </div>
223
- {:else}
224
- <p class="text-sm text-muted-foreground">{t('profile.notAvailable')}</p>
225
- {/if}
226
- </Card.CardContent>
227
- </Card.Card>
228
-
229
- <!-- Change Password -->
230
- <Card.Card>
231
- <Card.CardHeader>
232
- <Card.CardTitle class="flex items-center gap-2 text-base">
233
- <Lock class="h-4 w-4 text-muted-foreground" />
234
- {t('profile.changePassword')}
235
- </Card.CardTitle>
236
- </Card.CardHeader>
237
- <Card.CardContent>
238
- <form onsubmit={handlePasswordChange} class="space-y-4 max-w-sm">
239
- {#if pwError}
240
- <Alert.Root variant="destructive">
241
- <AlertCircle class="h-4 w-4" />
242
- <Alert.Description>{pwError}</Alert.Description>
243
- </Alert.Root>
244
- {/if}
245
-
246
- {#if pwSuccess}
247
- <Alert.Root class="border-success/30 bg-success/5 text-success">
248
- <CheckCircle class="h-4 w-4" />
249
- <Alert.Description>{t('profile.passwordChanged')}</Alert.Description>
250
- </Alert.Root>
251
- {/if}
252
-
253
- <PasswordInput
254
- id="current-password"
255
- label={t('profile.currentPassword')}
256
- bind:value={currentPassword}
257
- autocomplete="current-password"
258
- />
259
-
260
- <PasswordInput
261
- id="new-password"
262
- label={t('auth.password')}
263
- bind:value={newPassword}
264
- autocomplete="new-password"
265
- showStrength
266
- />
267
-
268
- <PasswordInput
269
- id="confirm-new-password"
270
- label={t('auth.confirmPassword')}
271
- bind:value={confirmPassword}
272
- autocomplete="new-password"
273
- />
274
-
275
- <Button type="submit" disabled={updatePw.isLoading}>
276
- {#if updatePw.isLoading}
277
- <Loader2 class="h-4 w-4 animate-spin mr-2" />
278
- {/if}
279
- {t('profile.updatePassword')}
280
- </Button>
281
- </form>
282
- </Card.CardContent>
283
- </Card.Card>
284
- </div>
@@ -1,112 +0,0 @@
1
- <script lang="ts">
2
- import { useRegister } from '@svadmin/core';
3
- import { t } from '@svadmin/core/i18n';
4
- import { navigate } from '@svadmin/core/router';
5
- import { Button } from './ui/button/index.js';
6
- import { Input } from './ui/input/index.js';
7
- import { Label } from './ui/label/index.js';
8
- import * as Card from './ui/card/index.js';
9
- import * as Alert from './ui/alert/index.js';
10
- import PasswordInput from './PasswordInput.svelte';
11
- import { UserPlus, User, Loader2, AlertCircle } from '@lucide/svelte';
12
-
13
- let { title = 'Admin', onSuccess } = $props<{
14
- title?: string;
15
- onSuccess?: () => void;
16
- }>();
17
-
18
- const register = useRegister();
19
-
20
- let identifier = $state('');
21
- let password = $state('');
22
- let confirmPassword = $state('');
23
- let error = $state('');
24
-
25
- async function handleSubmit(e: SubmitEvent) {
26
- e.preventDefault();
27
- error = '';
28
-
29
- if (!identifier) { error = t('auth.usernameOrEmailRequired'); return; }
30
- if (!password) { error = t('auth.passwordRequired'); return; }
31
- if (password !== confirmPassword) { error = t('auth.passwordMismatch'); return; }
32
-
33
- const result = await register.mutate({ email: identifier, username: identifier, password });
34
- if (result.success) {
35
- onSuccess?.();
36
- } else {
37
- error = result.error?.message ?? t('common.operationFailed');
38
- }
39
- }
40
- </script>
41
-
42
- <div class="min-h-screen flex items-center justify-center bg-background p-4">
43
- <div class="w-full max-w-[380px]">
44
- <Card.Card>
45
- <Card.CardHeader class="text-center pb-2">
46
- <div class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 text-primary mx-auto mb-3">
47
- <UserPlus class="h-5 w-5" />
48
- </div>
49
- <Card.CardTitle class="text-xl">{t('auth.createAccount')}</Card.CardTitle>
50
- <p class="text-sm text-muted-foreground">{t('auth.createAccountMessage')}</p>
51
- </Card.CardHeader>
52
- <Card.CardContent>
53
- <form onsubmit={handleSubmit} class="space-y-4">
54
- {#if error}
55
- <Alert.Root variant="destructive">
56
- <AlertCircle class="h-4 w-4" />
57
- <Alert.Description>{error}</Alert.Description>
58
- </Alert.Root>
59
- {/if}
60
-
61
- <div class="space-y-2">
62
- <Label for="register-identifier">{t('auth.usernameOrEmail')}</Label>
63
- <div class="relative">
64
- <User class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none z-[1]" />
65
- <Input
66
- id="register-identifier"
67
- type="text"
68
- placeholder={t('auth.identifierPlaceholder')}
69
- bind:value={identifier}
70
- class="pl-9"
71
- autocomplete="username"
72
- />
73
- </div>
74
- </div>
75
-
76
- <PasswordInput
77
- id="register-password"
78
- label={t('auth.password')}
79
- bind:value={password}
80
- autocomplete="new-password"
81
- showStrength
82
- />
83
-
84
- <PasswordInput
85
- id="register-confirm"
86
- label={t('auth.confirmPassword')}
87
- bind:value={confirmPassword}
88
- autocomplete="new-password"
89
- />
90
-
91
- <Button type="submit" class="w-full" disabled={register.isLoading}>
92
- {#if register.isLoading}
93
- <Loader2 class="h-4 w-4 animate-spin mr-2" />
94
- {/if}
95
- {t('auth.registerButton')}
96
- </Button>
97
- </form>
98
-
99
- <div class="flex items-center justify-center gap-1 mt-5 pt-5 border-t">
100
- <span class="text-sm text-muted-foreground">{t('auth.hasAccount')}</span>
101
- <Button variant="link" class="text-sm h-auto p-0 font-medium" onclick={() => navigate('/login')}>
102
- {t('auth.login')}
103
- </Button>
104
- </div>
105
- </Card.CardContent>
106
- </Card.Card>
107
-
108
- <p class="text-xs text-muted-foreground mt-4 text-center opacity-60">
109
- Powered by {title}
110
- </p>
111
- </div>
112
- </div>