@h-ai/cli 0.1.0-alpha.58 → 0.1.0-alpha.60

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 (93) hide show
  1. package/README.en.md +43 -20
  2. package/README.ja.md +43 -20
  3. package/README.md +43 -20
  4. package/dist/{chunk-WUSFJFW5.js → chunk-P6X63D3I.js} +64 -6
  5. package/dist/chunk-P6X63D3I.js.map +1 -0
  6. package/dist/{chunk-F4IBPVZ5.js → chunk-WWG3U5ZD.js} +20 -2
  7. package/dist/chunk-WWG3U5ZD.js.map +1 -0
  8. package/dist/cli-config-templates-EFABFSCY.js +5 -0
  9. package/dist/{cli-config-templates-6XA7V6CC.js.map → cli-config-templates-EFABFSCY.js.map} +1 -1
  10. package/dist/{cli-skill-templates-45CK4RQ2.js → cli-skill-templates-2KDUACT6.js} +3 -3
  11. package/dist/{cli-skill-templates-45CK4RQ2.js.map → cli-skill-templates-2KDUACT6.js.map} +1 -1
  12. package/dist/index.js +96 -73
  13. package/dist/index.js.map +1 -1
  14. package/package.json +9 -7
  15. package/templates/README.md +8 -0
  16. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +12 -0
  17. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +4 -7
  18. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +11 -1
  19. package/templates/apps/api/apps/{{projectName}}-service/tests/service.test.ts.hbs +2 -1
  20. package/templates/apps/api/e2e/api-service.spec.ts.hbs +72 -6
  21. package/templates/apps/api/package.json.hbs +4 -0
  22. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +10 -0
  23. package/templates/apps/fullstack/apps/{{projectName}}-app/project.inlang/settings.json.hbs +1 -1
  24. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +10 -0
  25. package/templates/apps/fullstack/apps/{{projectName}}-desktop/project.inlang/settings.json.hbs +1 -1
  26. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +2 -0
  27. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/project.inlang/settings.json.hbs +1 -1
  28. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +10 -0
  29. package/templates/apps/fullstack/apps/{{projectName}}-web/project.inlang/settings.json.hbs +1 -1
  30. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +63 -1
  31. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +1 -1
  32. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-contract.ts.hbs +8 -1
  33. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  34. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +10 -0
  35. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_iam.yml.hbs +46 -0
  36. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +21 -0
  37. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +1 -0
  38. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +1 -1
  39. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +2 -2
  40. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +7 -3
  41. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +11 -4
  42. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +12 -0
  43. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/en-US.json.hbs +2 -2
  44. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/zh-CN.json.hbs +2 -2
  45. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +1 -0
  46. package/templates/apps/fullstack/packages/{{projectName}}-shared/project.inlang/settings.json.hbs +1 -1
  47. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/api/api-client.ts.hbs +3 -0
  48. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +32 -23
  49. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/ProfileSettings.svelte.hbs +24 -24
  50. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/UserManagement.svelte.hbs +1 -1
  51. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/stores/management-store.svelte.ts.hbs +20 -20
  52. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/management-store.test.ts.hbs +7 -8
  53. package/templates/apps/mobile-app/src/App.svelte.hbs +43 -0
  54. package/templates/base/e2e/app.spec.ts.hbs +105 -0
  55. package/templates/base/package.json.hbs +1 -1
  56. package/templates/base/src/hooks.server.ts.hbs +2 -2
  57. package/templates/base/src/lib/server/init.ts.hbs +1 -1
  58. package/templates/base/src/routes/+layout.server.ts.hbs +6 -0
  59. package/templates/base/tests/scaffold.test.ts.hbs +3 -0
  60. package/templates/base/vite.config.ts.hbs +1 -0
  61. package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +2 -16
  62. package/templates/features/iam/routes-admin/(auth)/auth/forgot-password/+page.svelte.hbs +10 -0
  63. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +6 -95
  64. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +6 -117
  65. package/templates/features/iam/routes-admin/(auth)/auth/reset-password/+page.svelte.hbs +10 -0
  66. package/templates/features/iam/routes-h5/auth/forgot-password/+page.svelte.hbs +10 -0
  67. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +6 -97
  68. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +6 -112
  69. package/templates/features/iam/routes-h5/auth/reset-password/+page.svelte.hbs +10 -0
  70. package/templates/features/iam/routes-shared/api/auth/config/+server.ts +4 -0
  71. package/templates/features/iam/routes-shared/api/auth/login/+server.ts +1 -33
  72. package/templates/features/iam/routes-shared/api/auth/login/ldap/+server.ts +4 -0
  73. package/templates/features/iam/routes-shared/api/auth/login/otp/+server.ts +4 -0
  74. package/templates/features/iam/routes-shared/api/auth/otp/send/+server.ts +4 -0
  75. package/templates/features/iam/routes-shared/api/auth/password-reset/confirm/+server.ts +4 -0
  76. package/templates/features/iam/routes-shared/api/auth/password-reset/request/+server.ts +4 -0
  77. package/templates/features/iam/routes-shared/api/auth/register/+server.ts +1 -23
  78. package/templates/features/iam/routes-website/auth/forgot-password/+page.svelte.hbs +10 -0
  79. package/templates/features/iam/routes-website/auth/login/+page.svelte.hbs +11 -0
  80. package/templates/features/iam/routes-website/auth/register/+page.svelte.hbs +11 -0
  81. package/templates/features/iam/routes-website/auth/reset-password/+page.svelte.hbs +10 -0
  82. package/templates/skills/hai-api-client/SKILL.md +5 -0
  83. package/templates/skills/hai-app-create/SKILL.md +2 -0
  84. package/templates/skills/hai-app-create/development.md +15 -0
  85. package/templates/skills/hai-deploy/SKILL.md +2 -0
  86. package/templates/skills/hai-framework-sync/SKILL.md +4 -0
  87. package/templates/skills/hai-iam/SKILL.md +9 -2
  88. package/templates/skills/hai-iam/reference.md +76 -11
  89. package/templates/skills/hai-reach/SKILL.md +2 -0
  90. package/templates/skills/hai-ui/reference.md +45 -0
  91. package/dist/chunk-F4IBPVZ5.js.map +0 -1
  92. package/dist/chunk-WUSFJFW5.js.map +0 -1
  93. package/dist/cli-config-templates-6XA7V6CC.js +0 -5
@@ -1,3 +1,8 @@
1
+ {{#if features.iam}}
2
+ import { mkdir } from 'node:fs/promises'
3
+ import path from 'node:path'
4
+ {{/if}}
5
+ import process from 'node:process'
1
6
  import { expect, test } from '@playwright/test'
2
7
 
3
8
  /** 首页从真实浏览器页面发起,覆盖基础渲染链路。 */
@@ -9,3 +14,103 @@ test('home page renders from browser', async ({ page }) => {
9
14
  await expect(pageErrors).toEqual([])
10
15
  await expect(page.locator('main, section').first()).toBeVisible()
11
16
  })
17
+ {{#if features.iam}}
18
+
19
+ /** 实际注册和密码登录;截图只保存空白表单。 */
20
+ test('configured IAM registration and password login', async ({ page, context }) => {
21
+ test.setTimeout(90_000)
22
+ const evidence = process.env.HAI_AUTH_EVIDENCE_DIR || path.resolve('test-results/auth-screenshots')
23
+ await mkdir(evidence, { recursive: true })
24
+ const username = `scaf${Date.now()}`
25
+ const password = 'ScaffoldPassword123'
26
+ const authUrl = '{{#if isCapacitorApp}}/{{else}}/auth/login{{/if}}'
27
+ await page.goto(authUrl)
28
+ await expect(page.locator('#login-username')).toBeVisible()
29
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-login.png'), fullPage: true })
30
+ await page.getByRole('{{#if isCapacitorApp}}button{{else}}link{{/if}}', { name: /立即注册|Register now/i }).click()
31
+ await expect(page.locator('input[name="username"]')).toBeVisible()
32
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-register.png'), fullPage: true })
33
+ if (process.env.IAM_NOTIFICATION_URL) {
34
+ // 协议弹窗留在认证页,关闭后继续填写注册表单。
35
+ await page.getByRole('button', { name: /用户协议|User Agreement/i, exact: true }).click()
36
+ await expect(page.getByRole('dialog')).toBeVisible()
37
+ await expect(page.getByRole('dialog').getByRole('heading', { name: 'Configured registration agreement' })).toBeVisible()
38
+ await expect(page.locator('iframe')).toHaveCount(0)
39
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-agreement.png'), fullPage: true })
40
+ await page.keyboard.press('Escape')
41
+ await expect(page.getByRole('dialog')).not.toBeVisible()
42
+ }
43
+ await page.locator('input[name="username"]').fill(username)
44
+ await page.locator('input[name="email"]').fill(`${username}@example.com`)
45
+ await page.locator('input[type="password"]').nth(0).fill(password)
46
+ await page.locator('input[type="password"]').nth(1).fill(password)
47
+ await page.locator('button[type="submit"]').click()
48
+ await expect(page.locator('.register-form')).not.toBeVisible()
49
+ {{#unless isCapacitorApp}}
50
+ const me = await page.request.get('/api/auth/me')
51
+ expect(me.ok()).toBe(true)
52
+ await context.clearCookies()
53
+ {{/unless}}
54
+ await page.reload()
55
+ await page.goto(authUrl)
56
+ await expect(page.locator('#login-username')).toBeVisible()
57
+ await page.locator('#login-username').fill(username)
58
+ await page.locator('input[type="password"]').fill(password)
59
+ await page.locator('button[type="submit"]').click()
60
+ await expect(page.locator('.login-form')).not.toBeVisible()
61
+ expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
62
+
63
+ if (!process.env.IAM_NOTIFICATION_URL)
64
+ return
65
+ await context.clearCookies()
66
+ await page.reload()
67
+ await page.goto(authUrl)
68
+ await page.getByRole('button', { name: /邮件验证码|Email code/i, exact: true }).click()
69
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-email.png'), fullPage: true })
70
+ const email = `${username}@example.com`
71
+ await page.locator('#login-username').fill(email)
72
+ await page.getByRole('button', { name: /发送验证码|Send code/i, exact: true }).click()
73
+ await expect(page.getByRole('button', { name: /秒后重发|Resend in/ })).toBeDisabled()
74
+ async function received(template: string) {
75
+ const response = await page.request.get(`${process.env.IAM_NOTIFICATION_URL}/messages?to=${encodeURIComponent(email)}`)
76
+ const messages = await response.json() as { template: string, vars: Record<string, string> }[]
77
+ return messages.findLast(item => item.template === template)?.vars
78
+ }
79
+ const otp = await received('otp_email')
80
+ expect(Boolean(otp?.code)).toBe(true)
81
+ await page.locator('#login-code').fill(otp?.code ?? '')
82
+ await page.locator('button[type="submit"]').click()
83
+ await expect(page.locator('.login-form')).not.toBeVisible()
84
+ await context.clearCookies()
85
+ await page.reload()
86
+ await page.goto(authUrl)
87
+ await page.getByRole('button', { name: /忘记密码|Forgot password/i }).click()
88
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-forgot.png'), fullPage: true })
89
+ await page.locator('input[type="email"]').fill(email)
90
+ await page.locator('button[type="submit"]').click()
91
+ await expect(page.locator('.alert-success')).toBeVisible()
92
+ const reset = await received('password_reset')
93
+ expect(Boolean(reset?.token)).toBe(true)
94
+ await page.getByRole('button', { name: /已有重置令牌|I have a reset token/i }).click()
95
+ await page.screenshot({ path: path.join(evidence, '{{projectName}}-reset.png'), fullPage: true })
96
+ await page.goto(`{{#if isCapacitorApp}}/{{else}}/auth/reset-password{{/if}}?token=${encodeURIComponent(reset?.token ?? '')}`)
97
+ await expect(page.locator('input[type="password"]').nth(0)).toBeVisible()
98
+ await page.locator('input[type="password"]').nth(0).fill('ScaffoldReset456')
99
+ await page.locator('input[type="password"]').nth(1).fill('ScaffoldReset456')
100
+ await page.locator('button[type="submit"]').click()
101
+ await expect(page.locator('.alert-success')).toBeVisible()
102
+ await page.getByRole('{{#if isCapacitorApp}}button{{else}}link{{/if}}', { name: /^(登录|Sign in|Login)$/i }).click()
103
+ await page.locator('#login-username').fill(username)
104
+ await page.locator('input[type="password"]').fill('ScaffoldReset456')
105
+ await page.locator('button[type="submit"]').click()
106
+ await expect(page.locator('.login-form')).not.toBeVisible()
107
+ expect(new URL(page.url()).searchParams.has('token')).toBe(false)
108
+ await page.reload()
109
+ {{#if isCapacitorApp}}
110
+ // Web 预览采用内存 TokenStore,刷新后应回到登录,而不是已消费令牌的重置页面。
111
+ await expect(page.locator('#login-username')).toBeVisible()
112
+ {{else}}
113
+ await expect(await page.request.get('/api/auth/me')).toBeOK()
114
+ {{/if}}
115
+ })
116
+ {{/if}}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "engines": { "node": "^22.22.2 || ^24.15.0 || >=26.0.0" },
3
3
  "name": "{{projectName}}",
4
- "version": "0.1.0-alpha.58",
4
+ "version": "0.1.0-alpha.60",
5
5
  "private": true,
6
6
  "packageManager": "pnpm@12.11.2",
7
7
  "description": "{{#if_eq appType 'admin'}}Enterprise management system built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'website'}}Enterprise website built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'h5'}}Mobile H5 app built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'api'}}REST API service built with hai Agent Framework{{/if_eq}}{{#if_eq appType 'mobile-app'}}Mobile app built with hai Agent Framework{{/if_eq}}",
@@ -89,10 +89,10 @@ const haiHandle = kit.createHandle({
89
89
  data: session.data,
90
90
  }
91
91
  },
92
- operations: iam.auth,
93
92
  loginUrl: '/auth/login',
94
93
  protectedPaths: ['/admin/*', '/api/*'],
95
- publicPaths: ['/api/auth/*', '/api/public/*', '/api/health'],
94
+ publicPaths: [...kit.auth.publicPaths, '/api/auth/logout', '/api/public/*', '/api/health'],
95
+ operations: () => iam.auth,
96
96
  },
97
97
  {{/if}}
98
98
  rateLimit: { windowMs: 60000, maxRequests: 200 },
@@ -190,7 +190,7 @@ export async function initApp(): Promise<void> {
190
190
 
191
191
  {{#if features.iam}}
192
192
  // ─── 初始化 IAM ───
193
- const iamResult = await iam.init(iamConfig)
193
+ const iamResult = await iam.init({ ...iamConfig{{#if features.reach}}, reach{{/if}} })
194
194
  if (!iamResult.success) {
195
195
  throw new Error(`IAM initialization failed: ${iamResult.error.message}`)
196
196
  }
@@ -2,11 +2,17 @@
2
2
  {{#unless isSvelteOnlyApp}}
3
3
  import type { CoreConfig } from '@h-ai/core'
4
4
  import { core } from '@h-ai/core'
5
+ {{#if features.iam}}
6
+ import { iam } from '@h-ai/iam'
7
+ {{/if}}
5
8
 
6
9
  /** 只向浏览器提供公开配置,避免暴露日志上下文、凭证或其它模块配置。 */
7
10
  export function load() {
8
11
  const config = core.config.getOrThrow<CoreConfig>('core')
9
12
  return {
13
+ {{#if features.iam}}
14
+ iamPublicConfig: iam.publicConfig,
15
+ {{/if}}
10
16
  appConfig: {
11
17
  name: config.name,
12
18
  version: config.version,
@@ -55,6 +55,9 @@ describe('generated scaffold', () => {
55
55
  const config = core.config.getOrThrow<CoreConfig>('core')
56
56
  expect(core.logger.getLevel()).toBe(config.logging?.level ?? 'info')
57
57
  expect(load()).toEqual({
58
+ {{#if features.iam}}
59
+ iamPublicConfig: null,
60
+ {{/if}}
58
61
  appConfig: { name: config.name, version: config.version, env: config.env, defaultLocale: config.defaultLocale },
59
62
  })
60
63
  })
@@ -42,6 +42,7 @@ export default defineConfig(({ mode }) => {
42
42
  {{/if}}
43
43
 
44
44
  return {
45
+ envPrefix: ['VITE_', 'PUBLIC_'],
45
46
  {{#if isSvelteOnlyApp}}
46
47
  define: { __APP_CORE_CONFIG__: JSON.stringify(appConfig) },
47
48
  {{/if}}
@@ -1,21 +1,7 @@
1
- {{!-- 认证布局(登录/注册共用) --}}
2
1
  <script lang='ts'>
3
- /**
4
- * 认证布局(登录/注册共用)
5
- */
6
2
  import type { Snippet } from 'svelte'
7
3
 
8
- interface Props {
9
- children: Snippet
10
- }
11
-
12
- const { children }: Props = $props()
4
+ const { children }: { children: Snippet } = $props()
13
5
  </script>
14
6
 
15
- <div class='min-h-screen flex items-center justify-center bg-base-200'>
16
- <div class='card w-full max-w-md bg-base-100 shadow-xl'>
17
- <div class='card-body'>
18
- {@render children()}
19
- </div>
20
- </div>
21
- </div>
7
+ {@render children()}
@@ -0,0 +1,10 @@
1
+ <script lang='ts'>
2
+ import { resolve } from '$app/paths'
3
+ import { page } from '$app/state'
4
+ import { kit } from '@h-ai/kit'
5
+ import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
6
+
7
+ const actions = kit.client.createAuthActions()
8
+ </script>
9
+
10
+ <AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='forgot' resetToken={page.url.searchParams.get('token') ?? ''} loginUrl={resolve('/auth/login', {})} />
@@ -1,100 +1,11 @@
1
- {{!-- Admin 登录页面 --}}
2
1
  <script lang='ts'>
3
- /**
4
- * 登录页面
5
- */
2
+ import { goto } from '$app/navigation'
6
3
  import { resolve } from '$app/paths'
7
- {{#if hasI18n}}
8
- import * as m from '$lib/paraglide/messages'
9
- {{/if}}
10
- import { Alert, Button, FormField, Input } from '@h-ai/ui'
4
+ import { page } from '$app/state'
5
+ import { kit } from '@h-ai/kit'
6
+ import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
11
7
 
12
- let username = $state('')
13
- let password = $state('')
14
- let error = $state('')
15
- let loading = $state(false)
16
-
17
- async function handleLogin(event: SubmitEvent) {
18
- event.preventDefault()
19
- error = ''
20
- loading = true
21
- try {
22
- const res = await fetch('/api/auth/login', {
23
- method: 'POST',
24
- headers: { 'Content-Type': 'application/json' },
25
- body: JSON.stringify({ identifier: username, password }),
26
- })
27
- const data = await res.json()
28
- if (data.success) {
29
- window.location.href = '/admin'
30
- }
31
- else {
32
- {{#if hasI18n}}
33
- error = data.error?.message || m.auth_login_failed()
34
- {{else}}
35
- error = data.error?.message || '登录失败'
36
- {{/if}}
37
- }
38
- }
39
- catch {
40
- {{#if hasI18n}}
41
- error = m.auth_error_network()
42
- {{else}}
43
- error = '网络错误'
44
- {{/if}}
45
- }
46
- finally {
47
- loading = false
48
- }
49
- }
8
+ const actions = kit.client.createAuthActions()
50
9
  </script>
51
10
 
52
- <svelte:head>
53
- {{#if hasI18n}}
54
- <title>{m.auth_login_title()}</title>
55
- {{else}}
56
- <title>登录</title>
57
- {{/if}}
58
- </svelte:head>
59
-
60
- {{#if hasI18n}}
61
- <h2 class='text-2xl font-bold text-center mb-6'>{m.auth_login_title()}</h2>
62
- {{else}}
63
- <h2 class='text-2xl font-bold text-center mb-6'>登录</h2>
64
- {{/if}}
65
-
66
- {#if error}
67
- <Alert variant='error' class='mb-4'>{error}</Alert>
68
- {/if}
69
-
70
- <form onsubmit={handleLogin} class='space-y-4'>
71
- {{#if hasI18n}}
72
- <FormField label={m.auth_label_username()} required>
73
- <Input id='username' type='text' bind:value={username} required />
74
- </FormField>
75
- <FormField label={m.auth_label_password()} required>
76
- <Input id='password' type='password' bind:value={password} required />
77
- </FormField>
78
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
79
- {loading ? m.auth_login_loading() : m.auth_login_submit()}
80
- </Button>
81
- {{else}}
82
- <FormField label='用户名' required>
83
- <Input id='username' type='text' bind:value={username} required />
84
- </FormField>
85
- <FormField label='密码' required>
86
- <Input id='password' type='password' bind:value={password} required />
87
- </FormField>
88
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
89
- {loading ? '登录中...' : '登录'}
90
- </Button>
91
- {{/if}}
92
- </form>
93
-
94
- <div class='text-center mt-4'>
95
- {{#if hasI18n}}
96
- <a href={resolve('/auth/register', {})} class='link link-primary text-sm'>{m.auth_login_to_register()}</a>
97
- {{else}}
98
- <a href={resolve('/auth/register', {})} class='link link-primary text-sm'>没有账号?立即注册</a>
99
- {{/if}}
100
- </div>
11
+ <AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='login' loginUrl={resolve('/auth/login', {})} registerUrl={resolve('/auth/register', {})} onauthenticated={() => goto(resolve('/admin', {}))} />
@@ -1,122 +1,11 @@
1
- {{!-- Admin 注册页面 --}}
2
1
  <script lang='ts'>
3
- /**
4
- * 注册页面
5
- */
2
+ import { goto } from '$app/navigation'
6
3
  import { resolve } from '$app/paths'
7
- {{#if hasI18n}}
8
- import * as m from '$lib/paraglide/messages'
9
- {{/if}}
10
- import { Alert, Button, FormField, Input } from '@h-ai/ui'
4
+ import { page } from '$app/state'
5
+ import { kit } from '@h-ai/kit'
6
+ import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
11
7
 
12
- let username = $state('')
13
- let email = $state('')
14
- let password = $state('')
15
- let confirmPassword = $state('')
16
- let error = $state('')
17
- let loading = $state(false)
18
-
19
- async function handleRegister(event: SubmitEvent) {
20
- event.preventDefault()
21
- error = ''
22
- if (password !== confirmPassword) {
23
- {{#if hasI18n}}
24
- error = m.auth_error_password_mismatch()
25
- {{else}}
26
- error = '密码不一致'
27
- {{/if}}
28
- return
29
- }
30
- loading = true
31
- try {
32
- const res = await fetch('/api/auth/register', {
33
- method: 'POST',
34
- headers: { 'Content-Type': 'application/json' },
35
- body: JSON.stringify({ username, email, password, confirmPassword }),
36
- })
37
- const data = await res.json()
38
- if (data.success) {
39
- window.location.href = '/auth/login'
40
- }
41
- else {
42
- {{#if hasI18n}}
43
- error = data.error?.message || m.auth_register_failed()
44
- {{else}}
45
- error = data.error?.message || '注册失败'
46
- {{/if}}
47
- }
48
- }
49
- catch {
50
- {{#if hasI18n}}
51
- error = m.auth_error_network()
52
- {{else}}
53
- error = '网络错误'
54
- {{/if}}
55
- }
56
- finally {
57
- loading = false
58
- }
59
- }
8
+ const actions = kit.client.createAuthActions()
60
9
  </script>
61
10
 
62
- <svelte:head>
63
- {{#if hasI18n}}
64
- <title>{m.auth_register_title()}</title>
65
- {{else}}
66
- <title>注册</title>
67
- {{/if}}
68
- </svelte:head>
69
-
70
- {{#if hasI18n}}
71
- <h2 class='text-2xl font-bold text-center mb-6'>{m.auth_register_title()}</h2>
72
- {{else}}
73
- <h2 class='text-2xl font-bold text-center mb-6'>注册</h2>
74
- {{/if}}
75
-
76
- {#if error}
77
- <Alert variant='error' class='mb-4'>{error}</Alert>
78
- {/if}
79
-
80
- <form onsubmit={handleRegister} class='space-y-4'>
81
- {{#if hasI18n}}
82
- <FormField label={m.auth_label_username()} required>
83
- <Input id='username' type='text' bind:value={username} required />
84
- </FormField>
85
- <FormField label={m.auth_label_email()} required>
86
- <Input id='email' type='email' bind:value={email} required />
87
- </FormField>
88
- <FormField label={m.auth_label_password()} required>
89
- <Input id='password' type='password' bind:value={password} required />
90
- </FormField>
91
- <FormField label={m.auth_label_confirm_password()} required>
92
- <Input id='confirmPassword' type='password' bind:value={confirmPassword} required />
93
- </FormField>
94
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
95
- {loading ? m.auth_register_loading() : m.auth_register_submit()}
96
- </Button>
97
- {{else}}
98
- <FormField label='用户名' required>
99
- <Input id='username' type='text' bind:value={username} required />
100
- </FormField>
101
- <FormField label='邮箱' required>
102
- <Input id='email' type='email' bind:value={email} required />
103
- </FormField>
104
- <FormField label='密码' required>
105
- <Input id='password' type='password' bind:value={password} required />
106
- </FormField>
107
- <FormField label='确认密码' required>
108
- <Input id='confirmPassword' type='password' bind:value={confirmPassword} required />
109
- </FormField>
110
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
111
- {loading ? '注册中...' : '注册'}
112
- </Button>
113
- {{/if}}
114
- </form>
115
-
116
- <div class='text-center mt-4'>
117
- {{#if hasI18n}}
118
- <a href={resolve('/auth/login', {})} class='link link-primary text-sm'>{m.auth_register_to_login()}</a>
119
- {{else}}
120
- <a href={resolve('/auth/login', {})} class='link link-primary text-sm'>已有账号?立即登录</a>
121
- {{/if}}
122
- </div>
11
+ <AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='register' loginUrl={resolve('/auth/login', {})} registerUrl={resolve('/auth/register', {})} onauthenticated={() => goto(resolve('/admin', {}))} />
@@ -0,0 +1,10 @@
1
+ <script lang='ts'>
2
+ import { resolve } from '$app/paths'
3
+ import { page } from '$app/state'
4
+ import { kit } from '@h-ai/kit'
5
+ import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
6
+
7
+ const actions = kit.client.createAuthActions()
8
+ </script>
9
+
10
+ <AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='reset' resetToken={page.url.searchParams.get('token') ?? ''} loginUrl={resolve('/auth/login', {})} />
@@ -0,0 +1,10 @@
1
+ <script lang='ts'>
2
+ import { resolve } from '$app/paths'
3
+ import { page } from '$app/state'
4
+ import { kit } from '@h-ai/kit'
5
+ import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
6
+
7
+ const actions = kit.client.createAuthActions()
8
+ </script>
9
+
10
+ <AuthPanel config={page.data.iamPublicConfig} {actions} initialMode='forgot' resetToken={page.url.searchParams.get('token') ?? ''} loginUrl={resolve('/auth/login', {})} />
@@ -1,102 +1,11 @@
1
- {{!-- H5 登录页面 --}}
2
1
  <script lang='ts'>
3
- /**
4
- * H5 登录页面
5
- */
2
+ import { goto } from '$app/navigation'
6
3
  import { resolve } from '$app/paths'
7
- {{#if hasI18n}}
8
- import * as m from '$lib/paraglide/messages'
9
- {{/if}}
10
- import { Alert, Button, FormField, Input } from '@h-ai/ui'
4
+ import { page } from '$app/state'
5
+ import { kit } from '@h-ai/kit'
6
+ import { AuthPanel } from '@h-ai/ui/components/scenes/iam/index'
11
7
 
12
- let username = $state('')
13
- let password = $state('')
14
- let error = $state('')
15
- let loading = $state(false)
16
-
17
- async function handleLogin(event: SubmitEvent) {
18
- event.preventDefault()
19
- error = ''
20
- loading = true
21
- try {
22
- const res = await fetch('/api/auth/login', {
23
- method: 'POST',
24
- headers: { 'Content-Type': 'application/json' },
25
- body: JSON.stringify({ identifier: username, password }),
26
- })
27
- const data = await res.json()
28
- if (data.success) {
29
- window.location.href = '/'
30
- }
31
- else {
32
- {{#if hasI18n}}
33
- error = data.error?.message || m.auth_login_failed()
34
- {{else}}
35
- error = data.error?.message || '登录失败'
36
- {{/if}}
37
- }
38
- }
39
- catch {
40
- {{#if hasI18n}}
41
- error = m.auth_error_network()
42
- {{else}}
43
- error = '网络错误'
44
- {{/if}}
45
- }
46
- finally {
47
- loading = false
48
- }
49
- }
8
+ const authActions = kit.client.createAuthActions()
50
9
  </script>
51
10
 
52
- <svelte:head>
53
- {{#if hasI18n}}
54
- <title>{m.auth_login_title()}</title>
55
- {{else}}
56
- <title>登录</title>
57
- {{/if}}
58
- </svelte:head>
59
-
60
- <div class='p-4 pt-12 space-y-4'>
61
- {{#if hasI18n}}
62
- <h1 class='text-2xl font-black tracking-tight text-center'>{m.auth_login_title()}</h1>
63
- {{else}}
64
- <h1 class='text-2xl font-black tracking-tight text-center'>登录</h1>
65
- {{/if}}
66
-
67
- {#if error}
68
- <Alert variant='error'>{error}</Alert>
69
- {/if}
70
-
71
- <form onsubmit={handleLogin} class='space-y-4'>
72
- {{#if hasI18n}}
73
- <FormField label={m.auth_label_username()} required>
74
- <Input id='username' type='text' bind:value={username} required />
75
- </FormField>
76
- <FormField label={m.auth_label_password()} required>
77
- <Input id='password' type='password' bind:value={password} required />
78
- </FormField>
79
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
80
- {loading ? m.auth_login_loading() : m.auth_login_submit()}
81
- </Button>
82
- {{else}}
83
- <FormField label='用户名' required>
84
- <Input id='username' type='text' bind:value={username} required />
85
- </FormField>
86
- <FormField label='密码' required>
87
- <Input id='password' type='password' bind:value={password} required />
88
- </FormField>
89
- <Button type='submit' variant='primary' loading={loading} class='w-full'>
90
- {loading ? '登录中...' : '登录'}
91
- </Button>
92
- {{/if}}
93
- </form>
94
-
95
- <div class='text-center'>
96
- {{#if hasI18n}}
97
- <a href={resolve('/auth/register', {})} class='link link-primary text-sm'>{m.auth_login_to_register()}</a>
98
- {{else}}
99
- <a href={resolve('/auth/register', {})} class='link link-primary text-sm'>没有账号?立即注册</a>
100
- {{/if}}
101
- </div>
102
- </div>
11
+ <AuthPanel config={page.data.iamPublicConfig} actions={authActions} initialMode='login' loginUrl={resolve('/auth/login', {})} registerUrl={resolve('/auth/register', {})} onauthenticated={() => goto(resolve('/', {}))} />