@h-ai/cli 1.0.0-beta.17 → 1.0.0-beta.19

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 (195) hide show
  1. package/README.en.md +6 -0
  2. package/README.ja.md +6 -0
  3. package/README.md +6 -0
  4. package/dist/{chunk-EWTIVFHN.js → chunk-2ZL5ORQ2.js} +94 -28
  5. package/dist/chunk-2ZL5ORQ2.js.map +1 -0
  6. package/dist/{chunk-S2ZP7BHG.js → chunk-L53P43B2.js} +138 -32
  7. package/dist/chunk-L53P43B2.js.map +1 -0
  8. package/dist/cli-config-templates-LWMD2I6V.js +5 -0
  9. package/dist/{cli-config-templates-MKVYSK3E.js.map → cli-config-templates-LWMD2I6V.js.map} +1 -1
  10. package/dist/{cli-skill-templates-M64VPXZ4.js → cli-skill-templates-D6S2OQ6X.js} +3 -3
  11. package/dist/{cli-skill-templates-M64VPXZ4.js.map → cli-skill-templates-D6S2OQ6X.js.map} +1 -1
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.js +2797 -666
  14. package/dist/index.js.map +1 -1
  15. package/dist/metafile-esm.json +1 -1
  16. package/package.json +22 -22
  17. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -10
  18. package/templates/apps/admin/src/routes/+page.svelte.hbs +4 -4
  19. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +17 -7
  20. package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +26 -6
  21. package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +2 -2
  22. package/templates/apps/api/apps/{{projectName}}-contract/src/app-schemas.ts.hbs +6 -1
  23. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +13 -5
  24. package/templates/apps/api/apps/{{projectName}}-contract/tests/contract.test.ts.hbs +13 -5
  25. package/templates/apps/api/apps/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
  26. package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +71 -32
  27. package/templates/apps/api/apps/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
  28. package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +131 -54
  29. package/templates/apps/api/apps/{{projectName}}-serv/src/server/procedures/index.ts.hbs +39 -13
  30. package/templates/apps/api/apps/{{projectName}}-serv/tests/service.test.ts.hbs +78 -31
  31. package/templates/apps/api/apps/{{projectName}}-serv/tsup.config.ts.hbs +3 -1
  32. package/templates/apps/api/apps/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
  33. package/templates/apps/api/e2e/api-serv.spec.ts.hbs +20 -3
  34. package/templates/apps/api/e2e/capture-evidence.ts.hbs +4 -1
  35. package/templates/apps/api/eslint.config.js.hbs +3 -1
  36. package/templates/apps/api/images/serv.Dockerfile.hbs +3 -4
  37. package/templates/apps/api/playwright.config.ts.hbs +23 -19
  38. package/templates/apps/api/pnpm-workspace.yaml.hbs +27 -16
  39. package/templates/apps/fullstack/apps/{{projectName}}-app/capacitor.config.ts.hbs +1 -3
  40. package/templates/apps/fullstack/apps/{{projectName}}-app/eslint.config.js.hbs +10 -2
  41. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +10 -2
  42. package/templates/apps/fullstack/apps/{{projectName}}-app/src/build-metadata.ts.hbs +0 -1
  43. package/templates/apps/fullstack/apps/{{projectName}}-app/src/lib/capacitor.ts.hbs +0 -1
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/svelte.config.js.hbs +4 -1
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +16 -4
  46. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/App.svelte.hbs +8 -2
  47. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src/build-metadata.ts.hbs +0 -1
  48. package/templates/apps/fullstack/apps/{{projectName}}-desktop/svelte.config.js.hbs +4 -1
  49. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +16 -4
  50. package/templates/apps/fullstack/apps/{{projectName}}-web/eslint.config.js.hbs +10 -2
  51. package/templates/apps/fullstack/apps/{{projectName}}-web/src/build-metadata.ts.hbs +0 -1
  52. package/templates/apps/fullstack/apps/{{projectName}}-web/src/main.ts.hbs +0 -2
  53. package/templates/apps/fullstack/apps/{{projectName}}-web/svelte.config.js.hbs +4 -1
  54. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +19 -5
  55. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +111 -26
  56. package/templates/apps/fullstack/e2e/session-close.spec.ts.hbs +19 -5
  57. package/templates/apps/fullstack/eslint.config.js.hbs +3 -1
  58. package/templates/apps/fullstack/images/serv.Dockerfile.hbs +3 -4
  59. package/templates/apps/fullstack/images/web.Dockerfile.hbs +0 -2
  60. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +114 -23
  61. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +57 -11
  62. package/templates/apps/fullstack/packages/{{projectName}}-contract/tsup.config.ts.hbs +3 -1
  63. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +65 -30
  64. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/build-metadata.ts.hbs +0 -1
  65. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +69 -21
  66. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/business-settings.ts.hbs +21 -5
  67. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +63 -11
  68. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/app-procedures.ts.hbs +6 -1
  69. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -1
  70. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +139 -49
  71. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/security-roles.ts.hbs +43 -12
  72. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +139 -28
  73. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/runtime.test.ts.hbs +33 -10
  74. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +141 -25
  75. package/templates/apps/fullstack/packages/{{projectName}}-serv/tsup.config.ts.hbs +6 -2
  76. package/templates/apps/fullstack/packages/{{projectName}}-serv/vitest.config.ts.hbs +3 -1
  77. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +9 -2
  78. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/feedback-client.ts.hbs +0 -3
  79. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/sensitive-actions.svelte.ts.hbs +15 -3
  80. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +33 -7
  81. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/FeedbackEntry.svelte.hbs +16 -2
  82. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +104 -13
  83. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/feedback.svelte.hbs +37 -7
  84. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/roles.svelte.hbs +9 -3
  85. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +25 -3
  86. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +133 -19
  87. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/security/role-label.ts.hbs +14 -2
  88. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/guide-store.ts.hbs +66 -34
  89. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +132 -30
  90. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/theme-store.svelte.ts.hbs +8 -2
  91. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +23 -5
  92. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +143 -21
  93. package/templates/apps/fullstack/packages/{{projectName}}-shared/vitest.config.ts.hbs +7 -2
  94. package/templates/apps/fullstack/playwright.config.ts.hbs +9 -3
  95. package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +30 -15
  96. package/templates/apps/h5/src/routes/+layout.svelte.hbs +57 -21
  97. package/templates/apps/h5/src/routes/+page.svelte.hbs +2 -2
  98. package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +2 -2
  99. package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +2 -2
  100. package/templates/apps/mobile-app/src/App.svelte.hbs +17 -5
  101. package/templates/apps/mobile-app/src/lib/api.ts.hbs +14 -3
  102. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +8 -1
  103. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +6 -2
  104. package/templates/apps/mobile-app/src/lib/pages/tasks.svelte.hbs +5 -1
  105. package/templates/apps/website/scripts/static-website.mjs.hbs +16 -3
  106. package/templates/apps/website/src/routes/+layout.svelte.hbs +44 -23
  107. package/templates/apps/website/src/routes/+page.svelte.hbs +4 -4
  108. package/templates/apps/website/src/routes/about/+page.svelte.hbs +2 -2
  109. package/templates/apps/website/src/routes/contact/+page.svelte.hbs +2 -2
  110. package/templates/apps/website/src/routes/services/+page.svelte.hbs +20 -5
  111. package/templates/base/e2e/app.spec.ts.hbs +49 -16
  112. package/templates/base/eslint-rules.js +44 -25
  113. package/templates/base/eslint.config.js.hbs +3 -1
  114. package/templates/base/images/web.Dockerfile.hbs +3 -4
  115. package/templates/base/package.json.hbs +5 -4
  116. package/templates/base/patches/daisyui-5.7.50.patch +8 -0
  117. package/templates/base/patches/deepmerge-4.3.1.patch +51 -0
  118. package/templates/base/playwright.config.ts +3 -1
  119. package/templates/base/pnpm-workspace.yaml.hbs +33 -16
  120. package/templates/base/src/app.d.ts.hbs +1 -1
  121. package/templates/base/src/hooks.server.ts.hbs +36 -27
  122. package/templates/base/src/lib/build-metadata.ts.hbs +0 -1
  123. package/templates/base/src/lib/server/init.ts.hbs +124 -51
  124. package/templates/base/src/lib/server/kit-config.ts.hbs +7 -4
  125. package/templates/base/src/routes/+layout.server.ts.hbs +2 -2
  126. package/templates/base/svelte.config.js.hbs +10 -100
  127. package/templates/base/tests/scaffold.test.ts.hbs +12 -3
  128. package/templates/base/tsconfig.json.hbs +7 -4
  129. package/templates/base/vite.config.ts.hbs +108 -14
  130. package/templates/base/vitest.config.ts.hbs +6 -2
  131. package/templates/features/ai/routes/api/chat/+server.ts +3 -1
  132. package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +3 -3
  133. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +3 -3
  134. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +3 -3
  135. package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +3 -3
  136. package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +3 -3
  137. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +3 -3
  138. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +3 -3
  139. package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +3 -3
  140. package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +3 -3
  141. package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +3 -3
  142. package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +3 -3
  143. package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +3 -3
  144. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +11 -2
  145. package/templates/shared/browser-check/startup.js +32 -6
  146. package/templates/shared/browser-startup.spec.ts +3 -1
  147. package/templates/shared/build-metadata.ts +18 -4
  148. package/templates/shared/e2e-env.ts +22 -6
  149. package/templates/shared/native-name.mjs +9 -2
  150. package/templates/shared/public-base-path.mjs +17 -4
  151. package/templates/shared/run-e2e.mjs +40 -9
  152. package/templates/shared/run-node.mjs +15 -4
  153. package/templates/shared/run-tauri.mjs +37 -8
  154. package/templates/shared/vite-runtime-config.ts +9 -3
  155. package/templates/skills/AGENTS.md +10 -73
  156. package/templates/skills/bridges/admin/AGENTS.md +9 -65
  157. package/templates/skills/bridges/api/AGENTS.md +8 -59
  158. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +9 -54
  159. package/templates/skills/bridges/h5/AGENTS.md +8 -63
  160. package/templates/skills/bridges/mobile-app/AGENTS.md +8 -62
  161. package/templates/skills/bridges/website/AGENTS.md +8 -63
  162. package/templates/skills/hai-ai/acp.md +9 -1
  163. package/templates/skills/hai-ai/guide.md +17 -6
  164. package/templates/skills/hai-ai/reference.md +21 -12
  165. package/templates/skills/hai-api-client/reference.md +1 -1
  166. package/templates/skills/hai-app-create/development.md +3 -1
  167. package/templates/skills/hai-app-create/project.md +33 -0
  168. package/templates/skills/hai-app-create/reference.md +8 -8
  169. package/templates/skills/hai-app-tests/reference.md +1 -1
  170. package/templates/skills/hai-build/SKILL.md +3 -2
  171. package/templates/skills/hai-build/paths.md +1 -1
  172. package/templates/skills/hai-build/reference.md +11 -7
  173. package/templates/skills/hai-capacitor/reference.md +2 -0
  174. package/templates/skills/hai-ci/SKILL.md +1 -0
  175. package/templates/skills/hai-core/reference.md +3 -0
  176. package/templates/skills/hai-crypto/reference.md +2 -0
  177. package/templates/skills/hai-deploy/SKILL.md +7 -1
  178. package/templates/skills/hai-deploy/middleware.md +3 -1
  179. package/templates/skills/hai-framework-sync/SKILL.md +13 -9
  180. package/templates/skills/hai-framework-sync/sync.md +2 -0
  181. package/templates/skills/hai-kit/reference.md +16 -4
  182. package/templates/skills/hai-payment/reference.md +2 -0
  183. package/templates/skills/hai-reach/SKILL.md +2 -1
  184. package/templates/skills/hai-reach/reference.md +1 -1
  185. package/templates/skills/hai-reldb/SKILL.md +1 -1
  186. package/templates/skills/hai-reldb/reference.md +4 -0
  187. package/templates/skills/hai-serv/reference.md +12 -2
  188. package/templates/skills/hai-storage/reference.md +12 -3
  189. package/templates/skills/hai-ui/SKILL.md +3 -2
  190. package/templates/skills/hai-ui/guide.md +1 -1
  191. package/templates/skills/hai-ui/reference.md +23 -35
  192. package/templates/skills/hai-vecdb/reference.md +1 -1
  193. package/dist/chunk-EWTIVFHN.js.map +0 -1
  194. package/dist/chunk-S2ZP7BHG.js.map +0 -1
  195. package/dist/cli-config-templates-MKVYSK3E.js +0 -5
@@ -13,16 +13,32 @@
13
13
  import * as m from '../paraglide/messages.js'
14
14
 
15
15
  const { store }: { store: ManagementStore } = $props()
16
- let page = $state({ items: [] as FeedbackReportView[], total: 0, page: 1, pageSize: 20 })
16
+ let page = $state({
17
+ items: [] as FeedbackReportView[],
18
+ total: 0,
19
+ page: 1,
20
+ pageSize: 20,
21
+ })
17
22
  let query = $state<FeedbackListQuery>({ page: 1, pageSize: 20 })
18
23
  let selected = $state<Detail>()
19
- let evidence = $state({ items: [] as FeedbackEvidenceInput[], total: 0, page: 1, pageSize: 20 })
24
+ let evidence = $state({
25
+ items: [] as FeedbackEvidenceInput[],
26
+ total: 0,
27
+ page: 1,
28
+ pageSize: 20,
29
+ })
20
30
  let loading = $state(false)
21
31
  let error = $state('')
22
32
  let requestVersion = 0
23
33
  let detailVersion = 0
24
34
  let imageUrls = $state<Record<string, string>>({})
25
- const permissions = $derived({ read: store.hasPermission('feedback:read'), process: store.hasPermission('feedback:process'), reopen: store.hasPermission('feedback:reopen'), export: store.hasPermission('feedback:export'), retry: store.hasPermission('feedback:process') })
35
+ const permissions = $derived({
36
+ read: store.hasPermission('feedback:read'),
37
+ process: store.hasPermission('feedback:process'),
38
+ reopen: store.hasPermission('feedback:reopen'),
39
+ export: store.hasPermission('feedback:export'),
40
+ retry: store.hasPermission('feedback:process'),
41
+ })
26
42
  function view(report: FeedbackReport): FeedbackReportView {
27
43
  return { ...report, description: report.description || report.summary || '', appVersion: report.appVersion || '' }
28
44
  }
@@ -35,8 +51,12 @@
35
51
  const result = await managementApi.feedback.reports.list(next)
36
52
  if (!result.success)
37
53
  throw new Error(result.error.message, { cause: result.error })
38
- if (version === requestVersion)
39
- page = { ...result.data, items: result.data.items.map(view) }
54
+ if (version === requestVersion) {
55
+ page = {
56
+ ...result.data,
57
+ items: result.data.items.map(view),
58
+ }
59
+ }
40
60
  }
41
61
  catch (cause) {
42
62
  if (version === requestVersion)
@@ -114,7 +134,10 @@
114
134
  if (result.data.contentType.startsWith('image/') || result.data.contentType.startsWith('video/')) {
115
135
  if (imageUrls[id])
116
136
  URL.revokeObjectURL(imageUrls[id])
117
- imageUrls = { ...imageUrls, [id]: URL.createObjectURL(new Blob([new Uint8Array(feedbackDataBytes(result.data)).buffer], { type: result.data.contentType })) }
137
+ imageUrls = {
138
+ ...imageUrls,
139
+ [id]: URL.createObjectURL(new Blob([new Uint8Array(feedbackDataBytes(result.data)).buffer], { type: result.data.contentType })),
140
+ }
118
141
  }
119
142
  else {
120
143
  await downloadFeedback(feedbackDataBytes(result.data), result.data.name, result.data.contentType)
@@ -146,7 +169,14 @@
146
169
  <section data-testid='feedback-page' class='flex min-h-0 flex-1 flex-col gap-3'>
147
170
  <PageHeader title={m.nav_feedback()} class='mb-0' />
148
171
  {#if selected}
149
- <FeedbackDetail report={view(selected.report)} webhook={selected.jobs.find(job => job.type === 'webhook')} {evidence} history={selected.history.map(item => ({ id: item.id, actorId: item.userId, fromStatus: item.from, toStatus: item.to, createdAt: item.timestamp, note: item.note }))} {permissions}
172
+ <FeedbackDetail report={view(selected.report)} webhook={selected.jobs.find(job => job.type === 'webhook')} {evidence} history={selected.history.map(item => ({
173
+ id: item.id,
174
+ actorId: item.userId,
175
+ fromStatus: item.from,
176
+ toStatus: item.to,
177
+ createdAt: item.timestamp,
178
+ note: item.note,
179
+ }))} {permissions}
150
180
  attachments={selected.attachments.map(item => ({ ...item, mimeType: item.contentType, url: imageUrls[item.id] }))}
151
181
  onevidencequery={loadEvidence} onreplay={loadReplay} onstatus={status} onexport={exportBundle} onretry={retry}
152
182
  onattachment={item => attachment(item.id)} onclose={closeDetail} />
@@ -14,7 +14,9 @@
14
14
  let description = $state('')
15
15
  let permission = $state<AdminPermissionPolicy | null>(null)
16
16
  async function savePolicy(operations: string[]) {
17
- return permission ? store.savePermissionPolicy(permission.id, operations) : err(HaiCommonError.CONFLICT, m.home_error())
17
+ return permission
18
+ ? store.savePermissionPolicy(permission.id, operations)
19
+ : err(HaiCommonError.CONFLICT, m.home_error())
18
20
  }
19
21
  function edit(role: AdminRole) {
20
22
  editing = role
@@ -35,9 +37,13 @@
35
37
  <div class="grid gap-3 md:grid-cols-2">
36
38
  {#each store.roles as role (role.id)}
37
39
  <Card bordered shadow={false}>
38
- <div class="flex items-center justify-between gap-3"><h2 class="text-lg font-semibold">{roleLabel(role.code)}</h2><Badge variant={role.code === 'admin' ? 'primary' : 'ghost'}>{role.code}</Badge></div>
40
+ <div class="flex items-center justify-between gap-3"><h2 class="text-lg font-semibold">{roleLabel(role.code)}</h2><Badge variant={role.code === 'admin'
41
+ ? 'primary'
42
+ : 'ghost'}>{role.code}</Badge></div>
39
43
  <p class="mt-3 text-sm leading-6 text-base-content/60">{roleCapabilities(role.code)}</p>
40
- <p class="mt-4 text-xs text-base-content/45">{m.role_members({ count: String(role.userCount) })}</p>
44
+ <p class="mt-4 text-xs text-base-content/45">{m.role_members({
45
+ count: String(role.userCount),
46
+ })}</p>
41
47
  {#if role.description}<p class="mt-3 text-sm text-base-content/70">{role.description}</p>{/if}
42
48
  {#if role.code === 'admin' || role.code === 'user'}<div class="mt-5"><Button variant="outline" size="sm" onclick={() => edit(role)}>{m.role_edit()}</Button></div>{/if}
43
49
  </Card>
@@ -11,8 +11,24 @@
11
11
 
12
12
  interface Props { store: ManagementStore }
13
13
  const { store }: Props = $props()
14
- const languageOptions = $derived(locales.filter(locale => store.info?.supportedLocales.includes(locale)).map(locale => ({ value: locale, label: locale === 'zh-CN' ? '简体中文' : locale === 'ja-JP' ? '日本語' : 'English' })))
15
- const themeOptions = $derived(AVAILABLE_THEMES.map(theme => ({ value: theme, label: theme === 'high-contrast' ? m.theme_high_contrast() : theme === 'formal' ? m.theme_formal() : theme === 'eye-care' ? m.theme_eye_care() : getThemeInfo(theme)?.name ?? theme })))
14
+ const languageOptions = $derived(locales.filter(locale => store.info?.supportedLocales.includes(locale)).map(locale => ({
15
+ value: locale,
16
+ label: locale === 'zh-CN'
17
+ ? '简体中文'
18
+ : locale === 'ja-JP'
19
+ ? '日本語'
20
+ : 'English',
21
+ })))
22
+ const themeOptions = $derived(AVAILABLE_THEMES.map(theme => ({
23
+ value: theme,
24
+ label: theme === 'high-contrast'
25
+ ? m.theme_high_contrast()
26
+ : theme === 'formal'
27
+ ? m.theme_formal()
28
+ : theme === 'eye-care'
29
+ ? m.theme_eye_care()
30
+ : getThemeInfo(theme)?.name ?? theme,
31
+ })))
16
32
  </script>
17
33
 
18
34
  <section class='grid w-full min-w-0 gap-4 xl:grid-cols-2' data-testid='settings-page'>
@@ -21,7 +37,13 @@
21
37
  <Card class='xl:row-span-2' title={m.profile_title()} bordered shadow={false} data-guide='admin-profile-form'>
22
38
  <div class='mb-5 flex flex-wrap items-center gap-3 border-b border-base-content/8 pb-4'>
23
39
  <p class='min-w-0 break-all text-sm text-base-content/55'>@{store.user.username}</p>
24
- <Badge size='sm' variant='ghost'>{store.user.roles.filter((code): code is import('{{fullstack.contractPackageName}}').AdminRoleCode => ['admin', 'audit_admin', 'config_admin', 'business_operator', 'user'].includes(code)).map(roleLabel).join(', ')}</Badge>
40
+ <Badge size='sm' variant='ghost'>{store.user.roles.filter((code): code is import('{{fullstack.contractPackageName}}').AdminRoleCode => [
41
+ 'admin',
42
+ 'audit_admin',
43
+ 'config_admin',
44
+ 'business_operator',
45
+ 'user',
46
+ ].includes(code)).map(roleLabel).join(', ')}</Badge>
25
47
  </div>
26
48
  <UserProfile columns={2} user={store.user} editable alwaysEditable fields={['avatar', 'displayName', 'email']} avatarAccept='image/png,image/jpeg,image/webp' avatarHint={m.avatar_hint()} loading={store.saving} onsubmit={data => store.saveProfile(data)} onavatarchange={file => void store.uploadAvatar(file)} data-testid='profile-form' />
27
49
  </Card>
@@ -27,14 +27,53 @@
27
27
  let accountStatus = $state<'active' | 'dormant' | 'closed'>('active')
28
28
  let expiresAt = $state('')
29
29
  const columns = $derived([
30
- { key: 'username', sortable: true, label: m.user_account() },
31
- { key: 'email', sortable: true, label: m.user_email() },
32
- { key: 'roles', label: m.user_role() },
33
- { key: 'accountStatus', sortable: true, label: m.account_state() },
34
- { key: 'createdAt', sortable: true, label: m.created_at() },
30
+ {
31
+ key: 'username',
32
+ sortable: true,
33
+ label: m.user_account(),
34
+ },
35
+ {
36
+ key: 'email',
37
+ sortable: true,
38
+ label: m.user_email(),
39
+ },
40
+ {
41
+ key: 'roles',
42
+ label: m.user_role(),
43
+ },
44
+ {
45
+ key: 'accountStatus',
46
+ sortable: true,
47
+ label: m.account_state(),
48
+ },
49
+ {
50
+ key: 'createdAt',
51
+ sortable: true,
52
+ label: m.created_at(),
53
+ },
54
+ ])
55
+ const roleOptions = $derived(AdminRoleCodeSchema.options.map(value => ({
56
+ value,
57
+ label: roleLabel(value),
58
+ })))
59
+ const statusOptions = $derived([
60
+ {
61
+ value: 'all',
62
+ label: m.all_statuses(),
63
+ },
64
+ {
65
+ value: 'active',
66
+ label: m.account_active(),
67
+ },
68
+ {
69
+ value: 'dormant',
70
+ label: m.account_dormant(),
71
+ },
72
+ {
73
+ value: 'closed',
74
+ label: m.account_closed(),
75
+ },
35
76
  ])
36
- const roleOptions = $derived(AdminRoleCodeSchema.options.map(value => ({ value, label: roleLabel(value) })))
37
- const statusOptions = $derived([{ value: 'all', label: m.all_statuses() }, { value: 'active', label: m.account_active() }, { value: 'dormant', label: m.account_dormant() }, { value: 'closed', label: m.account_closed() }])
38
77
 
39
78
  function openEditor(user: AdminUser | null, useInvitation = false) {
40
79
  editing = user
@@ -47,17 +86,44 @@
47
86
  role = user?.roles[0] ?? 'user'
48
87
  accountType = user?.accountType ?? 'permanent'
49
88
  accountStatus = user?.accountStatus ?? 'active'
50
- expiresAt = user?.accountExpiresAt ? store.localDateTime(user.accountExpiresAt) : ''
89
+ expiresAt = user?.accountExpiresAt
90
+ ? store.localDateTime(user.accountExpiresAt)
91
+ : ''
51
92
  store.error = ''
52
93
  editorOpen = true
53
94
  }
54
95
 
55
96
  async function submit(event: SubmitEvent) {
56
97
  event.preventDefault()
57
- const fields = { displayName: displayName.trim(), role, accountType, accountStatus, accountExpiresAt: expiresAt ? new Date(expiresAt).toISOString() : null }
98
+ const fields = {
99
+ displayName: displayName.trim(),
100
+ role,
101
+ accountType,
102
+ accountStatus,
103
+ accountExpiresAt: expiresAt
104
+ ? new Date(expiresAt).toISOString()
105
+ : null,
106
+ }
58
107
  const saved = invitation && !editing
59
- ? await store.inviteUser({ username: username.trim(), email: email.trim() || undefined, phone: phone.trim() || undefined, ...fields, accountStatus: 'active' })
60
- : await store.saveUser(editing ? { id: editing.id, email: email.trim(), ...fields } : { username: username.trim(), password, email: email.trim() || undefined, ...fields })
108
+ ? await store.inviteUser({
109
+ username: username.trim(),
110
+ email: email.trim() || undefined,
111
+ phone: phone.trim() || undefined,
112
+ ...fields,
113
+ accountStatus: 'active',
114
+ })
115
+ : await store.saveUser(editing
116
+ ? {
117
+ id: editing.id,
118
+ email: email.trim(),
119
+ ...fields,
120
+ }
121
+ : {
122
+ username: username.trim(),
123
+ password,
124
+ email: email.trim() || undefined,
125
+ ...fields,
126
+ })
61
127
  if (saved) {
62
128
  editorOpen = false
63
129
  password = ''
@@ -94,7 +160,9 @@
94
160
  event.preventDefault()
95
161
  void store.queryUsers()
96
162
  }}>
97
- <FilterBar activeCount={store.usersStatus === 'all' ? 0 : 1} onreset={() => {
163
+ <FilterBar activeCount={store.usersStatus === 'all'
164
+ ? 0
165
+ : 1} onreset={() => {
98
166
  store.usersSearch = ''
99
167
  store.usersStatus = 'all'
100
168
  store.usersSortBy = 'createdAt'
@@ -115,9 +183,19 @@
115
183
  <div><p class='font-medium'>{user.displayName || user.username}</p><p class='text-xs text-base-content/45'>@{user.username}</p></div>
116
184
  </div>
117
185
  {:else if key === 'roles'}
118
- <Badge variant={user.roles.includes('admin') ? 'primary' : 'ghost'}>{user.roles.map(roleLabel).join(', ')}</Badge>
186
+ <Badge variant={user.roles.includes('admin')
187
+ ? 'primary'
188
+ : 'ghost'}>{user.roles.map(roleLabel).join(', ')}</Badge>
119
189
  {:else if key === 'accountStatus'}
120
- <Badge variant={user.accountStatus === 'active' ? 'success' : user.accountStatus === 'dormant' ? 'warning' : 'ghost'}>{user.accountStatus === 'active' ? m.account_active() : user.accountStatus === 'dormant' ? m.account_dormant() : m.account_closed()}</Badge>
190
+ <Badge variant={user.accountStatus === 'active'
191
+ ? 'success'
192
+ : user.accountStatus === 'dormant'
193
+ ? 'warning'
194
+ : 'ghost'}>{user.accountStatus === 'active'
195
+ ? m.account_active()
196
+ : user.accountStatus === 'dormant'
197
+ ? m.account_dormant()
198
+ : m.account_closed()}</Badge>
121
199
  {:else if key === 'createdAt'}
122
200
  <span class='whitespace-nowrap text-xs text-base-content/60'>{store.formatDate(user.createdAt)}</span>
123
201
  {:else if key === 'email'}
@@ -140,11 +218,21 @@
140
218
  <div class='flex items-center gap-3'>
141
219
  <Avatar name={user.displayName || user.username} src={user.avatarUrl} size='sm' />
142
220
  <div class='min-w-0 flex-1'><p class='truncate font-medium'>{user.displayName || user.username}</p><p class='truncate text-xs text-base-content/45'>@{user.username}</p></div>
143
- <Badge size='sm' variant={user.accountStatus === 'active' ? 'success' : user.accountStatus === 'dormant' ? 'warning' : 'ghost'}>{user.accountStatus === 'active' ? m.account_active() : user.accountStatus === 'dormant' ? m.account_dormant() : m.account_closed()}</Badge>
221
+ <Badge size='sm' variant={user.accountStatus === 'active'
222
+ ? 'success'
223
+ : user.accountStatus === 'dormant'
224
+ ? 'warning'
225
+ : 'ghost'}>{user.accountStatus === 'active'
226
+ ? m.account_active()
227
+ : user.accountStatus === 'dormant'
228
+ ? m.account_dormant()
229
+ : m.account_closed()}</Badge>
144
230
  </div>
145
231
  <p class='mt-3 truncate text-xs text-base-content/60' title={user.email}>{user.email || '—'}</p>
146
232
  <div class='mt-3 flex flex-wrap items-center justify-between gap-2'>
147
- <Badge size='sm' variant={user.roles.includes('admin') ? 'primary' : 'ghost'}>{user.roles.map(roleLabel).join(', ')}</Badge>
233
+ <Badge size='sm' variant={user.roles.includes('admin')
234
+ ? 'primary'
235
+ : 'ghost'}>{user.roles.map(roleLabel).join(', ')}</Badge>
148
236
  {@render userActions(user)}
149
237
  </div>
150
238
  </Card>
@@ -157,7 +245,11 @@
157
245
  </div>
158
246
  </section>
159
247
 
160
- <Modal bind:open={editorOpen} title={editing ? m.user_edit() : invitation ? m.invitation_title() : m.user_create()} closeOnBackdrop={!store.saving} closeOnEscape={!store.saving} size='3xl' data-testid='user-editor'>
248
+ <Modal bind:open={editorOpen} title={editing
249
+ ? m.user_edit()
250
+ : invitation
251
+ ? m.invitation_title()
252
+ : m.user_create()} closeOnBackdrop={!store.saving} closeOnEscape={!store.saving} size='3xl' data-testid='user-editor'>
161
253
  <form onsubmit={submit}>
162
254
  {#if invitation}<p class='mb-4 text-sm text-base-content/60'>{m.invitation_hint()}</p>{/if}
163
255
  <FormLayout columns={2} labelPosition='left' labelWidth='6rem'>
@@ -170,8 +262,30 @@
170
262
  <FormField label={m.user_password()} hint={m.initial_password_rules({ min: store.authConfig?.password.minLength ?? 8 })} required><PasswordInput required bind:value={password} autocomplete='new-password' minLength={store.authConfig?.password.minLength ?? 8} data-testid='user-password' /></FormField>
171
263
  {/if}
172
264
  {#if invitation && store.authConfig?.passwordReset.mode === 'phone'}<FormField label={m.invitation_phone()} required><Input type='tel' bind:value={phone} required maxlength={32} data-testid='invite-phone' /></FormField>{/if}
173
- <FormField label={m.account_type()}><Select bind:value={accountType} options={[{ value: 'permanent', label: m.account_permanent() }, { value: 'temporary', label: m.account_temporary() }]} data-testid='account-type' /></FormField>
174
- <FormField label={m.account_state()}><Select bind:value={accountStatus} disabled={invitation || editing?.id === store.user?.id} options={[{ value: 'active', label: m.account_active() }, { value: 'dormant', label: m.account_dormant() }, { value: 'closed', label: m.account_closed() }]} data-testid='account-state' /></FormField>
265
+ <FormField label={m.account_type()}><Select bind:value={accountType} options={[
266
+ {
267
+ value: 'permanent',
268
+ label: m.account_permanent(),
269
+ },
270
+ {
271
+ value: 'temporary',
272
+ label: m.account_temporary(),
273
+ },
274
+ ]} data-testid='account-type' /></FormField>
275
+ <FormField label={m.account_state()}><Select bind:value={accountStatus} disabled={invitation || editing?.id === store.user?.id} options={[
276
+ {
277
+ value: 'active',
278
+ label: m.account_active(),
279
+ },
280
+ {
281
+ value: 'dormant',
282
+ label: m.account_dormant(),
283
+ },
284
+ {
285
+ value: 'closed',
286
+ label: m.account_closed(),
287
+ },
288
+ ]} data-testid='account-state' /></FormField>
175
289
  <FormField label={m.account_expires()} required={accountType === 'temporary'}><Input type='datetime-local' bind:value={expiresAt} required={accountType === 'temporary'} data-testid='account-expires' /></FormField>
176
290
  <FormField label={m.user_role()}><Select bind:value={role} options={roleOptions} disabled={editing?.id === store.user?.id} data-testid='user-role' /></FormField>
177
291
  <div class='flex justify-end gap-2 border-t border-base-content/8 pt-4'>
@@ -2,11 +2,23 @@ import type { AdminRoleCode } from '{{fullstack.contractPackageName}}'
2
2
  import * as m from '../paraglide/messages.js'
3
3
 
4
4
  export function roleLabel(code: AdminRoleCode): string {
5
- const labels = { admin: m.role_admin, audit_admin: m.role_audit, config_admin: m.role_config, business_operator: m.role_operator, user: m.role_user }
5
+ const labels = {
6
+ admin: m.role_admin,
7
+ audit_admin: m.role_audit,
8
+ config_admin: m.role_config,
9
+ business_operator: m.role_operator,
10
+ user: m.role_user,
11
+ }
6
12
  return labels[code]()
7
13
  }
8
14
 
9
15
  export function roleCapabilities(code: AdminRoleCode): string {
10
- const labels = { admin: m.role_admin_capabilities, audit_admin: m.role_audit_capabilities, config_admin: m.role_config_capabilities, business_operator: m.role_operator_capabilities, user: m.role_user_capabilities }
16
+ const labels = {
17
+ admin: m.role_admin_capabilities,
18
+ audit_admin: m.role_audit_capabilities,
19
+ config_admin: m.role_config_capabilities,
20
+ business_operator: m.role_operator_capabilities,
21
+ user: m.role_user_capabilities,
22
+ }
11
23
  return labels[code]()
12
24
  }
@@ -17,39 +17,71 @@ export function createManagementGuide(store: ManagementStore, platform: 'web' |
17
17
  return createGuide({
18
18
  nav,
19
19
  persistKey: '{{projectName}}-guide',
20
- persistence: platform === 'web' ? 'cookie' : 'localStorage',
21
- tours: [{
22
- id: 'overview',
23
- title: () => m.guide_overview_title(),
24
- description: () => m.guide_overview_description(),
25
- steps: [
26
- { target: 'admin-content', title: () => m.guide_content_title(), description: () => m.guide_content_description() },
27
- { target: 'admin-profile', title: () => m.guide_profile_title(), description: () => m.guide_profile_description() },
28
- { target: 'admin-help', title: () => m.guide_help_title(), description: () => m.guide_help_description() },
29
- ],
30
- }, {
31
- id: 'profile',
32
- title: () => m.guide_profile_tour_title(),
33
- description: () => m.guide_profile_tour_description(),
34
- route: '/settings',
35
- steps: [
36
- { target: 'admin-profile-form', title: () => m.guide_profile_title(), description: () => m.guide_profile_form_description() },
37
- { target: 'admin-preferences', title: () => m.preferences_title(), description: () => m.guide_preferences_description() },
38
- { target: 'admin-password', title: () => m.guide_password_title(), description: () => m.guide_password_description() },
39
- ],
40
- }],
41
- articles: [{
42
- id: 'overview',
43
- title: () => m.guide_overview_title(),
44
- description: () => m.guide_overview_description(),
45
- content: () => m.guide_overview_article(),
46
- tourId: 'overview',
47
- }, {
48
- id: 'profile',
49
- title: () => m.guide_profile_tour_title(),
50
- description: () => m.guide_profile_tour_description(),
51
- content: () => m.guide_profile_article(),
52
- tourId: 'profile',
53
- }],
20
+ persistence: platform === 'web'
21
+ ? 'cookie'
22
+ : 'localStorage',
23
+ tours: [
24
+ {
25
+ id: 'overview',
26
+ title: () => m.guide_overview_title(),
27
+ description: () => m.guide_overview_description(),
28
+ steps: [
29
+ {
30
+ target: 'admin-content',
31
+ title: () => m.guide_content_title(),
32
+ description: () => m.guide_content_description(),
33
+ },
34
+ {
35
+ target: 'admin-profile',
36
+ title: () => m.guide_profile_title(),
37
+ description: () => m.guide_profile_description(),
38
+ },
39
+ {
40
+ target: 'admin-help',
41
+ title: () => m.guide_help_title(),
42
+ description: () => m.guide_help_description(),
43
+ },
44
+ ],
45
+ },
46
+ {
47
+ id: 'profile',
48
+ title: () => m.guide_profile_tour_title(),
49
+ description: () => m.guide_profile_tour_description(),
50
+ route: '/settings',
51
+ steps: [
52
+ {
53
+ target: 'admin-profile-form',
54
+ title: () => m.guide_profile_title(),
55
+ description: () => m.guide_profile_form_description(),
56
+ },
57
+ {
58
+ target: 'admin-preferences',
59
+ title: () => m.preferences_title(),
60
+ description: () => m.guide_preferences_description(),
61
+ },
62
+ {
63
+ target: 'admin-password',
64
+ title: () => m.guide_password_title(),
65
+ description: () => m.guide_password_description(),
66
+ },
67
+ ],
68
+ },
69
+ ],
70
+ articles: [
71
+ {
72
+ id: 'overview',
73
+ title: () => m.guide_overview_title(),
74
+ description: () => m.guide_overview_description(),
75
+ content: () => m.guide_overview_article(),
76
+ tourId: 'overview',
77
+ },
78
+ {
79
+ id: 'profile',
80
+ title: () => m.guide_profile_tour_title(),
81
+ description: () => m.guide_profile_tour_description(),
82
+ content: () => m.guide_profile_article(),
83
+ tourId: 'profile',
84
+ },
85
+ ],
54
86
  })
55
87
  }