@ploykit/ui 0.6.2 → 0.7.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.
@@ -1,4 +1,8 @@
1
1
  export interface ResetPasswordPageProps {
2
2
  onDone?: string;
3
+ /** 页面标题(i18n 覆盖点,走查 #5)。 */
4
+ title?: string;
5
+ /** 成功态文案(i18n 覆盖点)。 */
6
+ successHint?: string;
3
7
  }
4
- export declare function ResetPasswordPage({ onDone }: ResetPasswordPageProps): import("react").JSX.Element;
8
+ export declare function ResetPasswordPage({ onDone, title, successHint, }: ResetPasswordPageProps): import("react").JSX.Element;
@@ -7,7 +7,7 @@ import { Input } from '../../components/ui/input.js';
7
7
  import { usePasswordReset } from '@ploykit/hooks';
8
8
  import { passwordSchema } from './RegisterPage.js';
9
9
  const REDIRECT_DELAY_MS = 3000;
10
- export function ResetPasswordPage({ onDone }) {
10
+ export function ResetPasswordPage({ onDone, title = '设置新密码', successHint = '密码已重置,全部旧会话已失效。', }) {
11
11
  const { reset } = usePasswordReset();
12
12
  const [params, setParams] = useState(null);
13
13
  const email = params?.get('email') ?? '';
@@ -67,5 +67,5 @@ export function ResetPasswordPage({ onDone }) {
67
67
  setLoading(false);
68
68
  }
69
69
  };
70
- return (_jsx(AuthShell, { children: _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsx("div", { className: "space-y-1 text-center", children: _jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "\u8BBE\u7F6E\u65B0\u5BC6\u7801" }) }), done ? (_jsxs("div", { className: "space-y-2 text-center", "data-testid": "reset-done", children: [_jsx("p", { className: "text-sm text-(--success)", children: "\u5BC6\u7801\u5DF2\u91CD\u7F6E\uFF0C\u5168\u90E8\u65E7\u4F1A\u8BDD\u5DF2\u5931\u6548\u3002" }), _jsx("p", { className: "text-sm text-muted-foreground", children: countdown > 0 ? `${countdown} 秒后跳转登录页…` : '正在跳转…' })] })) : params === null ? (_jsx("p", { className: "text-center text-sm text-muted-foreground", children: "\u6B63\u5728\u6253\u5F00\u94FE\u63A5\u2026" })) : !email || !token ? (_jsx("p", { className: "text-center text-sm text-destructive", "data-testid": "reset-invalid", children: "\u94FE\u63A5\u65E0\u6548\uFF1A\u7F3A\u5C11\u90AE\u7BB1\u6216\u4EE4\u724C\u53C2\u6570\uFF0C\u8BF7\u4ECE\u90AE\u4EF6\u4E2D\u7684\u94FE\u63A5\u8FDB\u5165\u3002" })) : (_jsxs(_Fragment, { children: [_jsxs("p", { className: "text-sm text-muted-foreground", children: ["\u6B63\u5728\u4E3A ", email, " \u8BBE\u7F6E\u65B0\u5BC6\u7801"] }), _jsx(FormField, { label: "\u65B0\u5BC6\u7801", htmlFor: "reset-new", children: _jsx(Input, { id: "reset-new", type: "password", value: password, onChange: (e) => setPassword(e.target.value), placeholder: "\u81F3\u5C11 10 \u4F4D\uFF0C\u542B\u4E09\u7C7B\u5B57\u7B26", autoComplete: "new-password", required: true }) }), _jsx(FormField, { label: "\u786E\u8BA4\u65B0\u5BC6\u7801", error: confirmError ?? undefined, htmlFor: "reset-confirm", children: _jsx(Input, { id: "reset-confirm", type: "password", value: confirm, onChange: (e) => setConfirm(e.target.value), placeholder: "\u518D\u8F93\u5165\u4E00\u6B21", autoComplete: "new-password", "aria-invalid": confirmError ? true : undefined, required: true }) }), serverError && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: serverError })), _jsx(Button, { type: "submit", className: "w-full", disabled: loading || !password || !confirm, children: loading ? '提交中…' : '重置密码' })] }))] }) }));
70
+ return (_jsx(AuthShell, { children: _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsx("div", { className: "space-y-1 text-center", children: _jsx("h1", { className: "text-2xl font-bold tracking-tight", children: title }) }), done ? (_jsxs("div", { className: "space-y-2 text-center", "data-testid": "reset-done", children: [_jsx("p", { className: "text-sm text-(--success)", children: successHint }), _jsx("p", { className: "text-sm text-muted-foreground", children: countdown > 0 ? `${countdown} 秒后跳转登录页…` : '正在跳转…' })] })) : params === null ? (_jsx("p", { className: "text-center text-sm text-muted-foreground", children: "\u6B63\u5728\u6253\u5F00\u94FE\u63A5\u2026" })) : !email || !token ? (_jsx("p", { className: "text-center text-sm text-destructive", "data-testid": "reset-invalid", children: "\u94FE\u63A5\u65E0\u6548\uFF1A\u7F3A\u5C11\u90AE\u7BB1\u6216\u4EE4\u724C\u53C2\u6570\uFF0C\u8BF7\u4ECE\u90AE\u4EF6\u4E2D\u7684\u94FE\u63A5\u8FDB\u5165\u3002" })) : (_jsxs(_Fragment, { children: [_jsxs("p", { className: "text-sm text-muted-foreground", children: ["\u6B63\u5728\u4E3A ", email, " \u8BBE\u7F6E\u65B0\u5BC6\u7801"] }), _jsx(FormField, { label: "\u65B0\u5BC6\u7801", htmlFor: "reset-new", children: _jsx(Input, { id: "reset-new", type: "password", value: password, onChange: (e) => setPassword(e.target.value), placeholder: "\u81F3\u5C11 10 \u4F4D\uFF0C\u542B\u4E09\u7C7B\u5B57\u7B26", autoComplete: "new-password", required: true }) }), _jsx(FormField, { label: "\u786E\u8BA4\u65B0\u5BC6\u7801", error: confirmError ?? undefined, htmlFor: "reset-confirm", children: _jsx(Input, { id: "reset-confirm", type: "password", value: confirm, onChange: (e) => setConfirm(e.target.value), placeholder: "\u518D\u8F93\u5165\u4E00\u6B21", autoComplete: "new-password", "aria-invalid": confirmError ? true : undefined, required: true }) }), serverError && (_jsx("p", { role: "alert", className: "text-sm text-destructive", children: serverError })), _jsx(Button, { type: "submit", className: "w-full", disabled: loading || !password || !confirm, children: loading ? '提交中…' : '重置密码' })] }))] }) }));
71
71
  }
@@ -1 +1,9 @@
1
- export declare function VerifyEmailPage(): import("react").JSX.Element;
1
+ export interface VerifyEmailPageProps {
2
+ /** 三态标题(i18n 覆盖点,走查 #5):成功 / 失败 / 验证中。 */
3
+ titles?: {
4
+ ok?: string;
5
+ failed?: string;
6
+ verifying?: string;
7
+ };
8
+ }
9
+ export declare function VerifyEmailPage({ titles, }: VerifyEmailPageProps): import("react").JSX.Element;
@@ -5,7 +5,7 @@ import { AuthShell } from '../../layouts/AuthShell.js';
5
5
  import { buttonVariants } from '../../components/ui/button.js';
6
6
  import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent, } from '../../components/ui/empty.js';
7
7
  import { MailCheck, LoaderCircle, CircleX } from 'lucide-react';
8
- export function VerifyEmailPage() {
8
+ export function VerifyEmailPage({ titles = { ok: '邮箱验证成功', failed: '验证失败', verifying: '正在验证…' }, }) {
9
9
  const [state, setState] = useState('verifying');
10
10
  const [message, setMessage] = useState(null);
11
11
  const fired = useRef(false);
@@ -37,7 +37,7 @@ export function VerifyEmailPage() {
37
37
  setMessage(err instanceof Error ? err.message : '验证失败,链接可能已失效');
38
38
  });
39
39
  }, []);
40
- return (_jsx(AuthShell, { children: _jsxs(Empty, { className: "border-none p-0", children: [_jsxs(EmptyHeader, { children: [_jsxs(EmptyMedia, { variant: "icon", "data-testid": "verify-state", children: [state === 'ok' && _jsx(MailCheck, { className: "text-(--success)", "aria-hidden": "true" }), state === 'verifying' && _jsx(LoaderCircle, { className: "animate-spin", "aria-hidden": "true" }), state === 'failed' && _jsx(CircleX, { className: "text-destructive", "aria-hidden": "true" })] }), _jsx(EmptyTitle, { children: state === 'ok' ? '邮箱验证成功' : state === 'failed' ? '验证失败' : '正在验证…' }), _jsx(EmptyDescription, { children: state === 'ok'
40
+ return (_jsx(AuthShell, { children: _jsxs(Empty, { className: "border-none p-0", children: [_jsxs(EmptyHeader, { children: [_jsxs(EmptyMedia, { variant: "icon", "data-testid": "verify-state", children: [state === 'ok' && _jsx(MailCheck, { className: "text-(--success)", "aria-hidden": "true" }), state === 'verifying' && _jsx(LoaderCircle, { className: "animate-spin", "aria-hidden": "true" }), state === 'failed' && _jsx(CircleX, { className: "text-destructive", "aria-hidden": "true" })] }), _jsx(EmptyTitle, { children: state === 'ok' ? titles.ok : state === 'failed' ? titles.failed : titles.verifying }), _jsx(EmptyDescription, { children: state === 'ok'
41
41
  ? '感谢确认,现在可以正常登录了。'
42
42
  : state === 'failed'
43
43
  ? (message ?? '验证失败')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ploykit/ui",
3
- "version": "0.6.2",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -30,8 +30,8 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@base-ui/react": "^1.8.0",
33
- "@ploykit/client": "^0.6.2",
34
- "@ploykit/hooks": "^0.6.2",
33
+ "@ploykit/client": "^0.7.2",
34
+ "@ploykit/hooks": "^0.7.2",
35
35
  "class-variance-authority": "^0.7.1",
36
36
  "clsx": "^2.1.1",
37
37
  "cn": "^0.4.0",
@@ -227,3 +227,4 @@ describe('LoginPage passwordOnly(aifengkong #4:企业内部纯密码形态
227
227
  expect(screen.getByRole('tab', { name: '密码登录' })).toBeInTheDocument()
228
228
  })
229
229
  })
230
+
@@ -10,11 +10,21 @@ import { passwordSchema } from './RegisterPage'
10
10
  export interface ResetPasswordPageProps {
11
11
 
12
12
  onDone?: string
13
+
14
+ /** 页面标题(i18n 覆盖点,走查 #5)。 */
15
+ title?: string
16
+
17
+ /** 成功态文案(i18n 覆盖点)。 */
18
+ successHint?: string
13
19
  }
14
20
 
15
21
  const REDIRECT_DELAY_MS = 3000
16
22
 
17
- export function ResetPasswordPage({ onDone }: ResetPasswordPageProps) {
23
+ export function ResetPasswordPage({
24
+ onDone,
25
+ title = '设置新密码',
26
+ successHint = '密码已重置,全部旧会话已失效。',
27
+ }: ResetPasswordPageProps) {
18
28
  const { reset } = usePasswordReset()
19
29
 
20
30
  const [params, setParams] = useState<URLSearchParams | null>(null)
@@ -83,12 +93,12 @@ export function ResetPasswordPage({ onDone }: ResetPasswordPageProps) {
83
93
  <AuthShell>
84
94
  <form onSubmit={handleSubmit} className="space-y-4">
85
95
  <div className="space-y-1 text-center">
86
- <h1 className="text-2xl font-bold tracking-tight">设置新密码</h1>
96
+ <h1 className="text-2xl font-bold tracking-tight">{title}</h1>
87
97
  </div>
88
98
 
89
99
  {done ? (
90
100
  <div className="space-y-2 text-center" data-testid="reset-done">
91
- <p className="text-sm text-(--success)">密码已重置,全部旧会话已失效。</p>
101
+ <p className="text-sm text-(--success)">{successHint}</p>
92
102
  <p className="text-sm text-muted-foreground">
93
103
  {countdown > 0 ? `${countdown} 秒后跳转登录页…` : '正在跳转…'}
94
104
  </p>
@@ -10,7 +10,14 @@ import { MailCheck, LoaderCircle, CircleX } from 'lucide-react'
10
10
 
11
11
  type VerifyState = 'verifying' | 'ok' | 'failed'
12
12
 
13
- export function VerifyEmailPage() {
13
+ export interface VerifyEmailPageProps {
14
+ /** 三态标题(i18n 覆盖点,走查 #5):成功 / 失败 / 验证中。 */
15
+ titles?: { ok?: string; failed?: string; verifying?: string }
16
+ }
17
+
18
+ export function VerifyEmailPage({
19
+ titles = { ok: '邮箱验证成功', failed: '验证失败', verifying: '正在验证…' },
20
+ }: VerifyEmailPageProps) {
14
21
  const [state, setState] = useState<VerifyState>('verifying')
15
22
  const [message, setMessage] = useState<string | null>(null)
16
23
 
@@ -55,7 +62,7 @@ export function VerifyEmailPage() {
55
62
  {state === 'failed' && <CircleX className="text-destructive" aria-hidden="true" />}
56
63
  </EmptyMedia>
57
64
  <EmptyTitle>
58
- {state === 'ok' ? '邮箱验证成功' : state === 'failed' ? '验证失败' : '正在验证…'}
65
+ {state === 'ok' ? titles.ok : state === 'failed' ? titles.failed : titles.verifying}
59
66
  </EmptyTitle>
60
67
  <EmptyDescription>
61
68
  {state === 'ok'
@@ -0,0 +1,33 @@
1
+ import '@testing-library/jest-dom/vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { MemoryRouter } from 'react-router'
4
+ import { describe, expect, it, vi } from 'vitest'
5
+
6
+ // renderx-walk #5: every auth page exposes i18n override props — titles at
7
+ // minimum, so overseas products are not stuck with hardcoded copy.
8
+ vi.mock('@ploykit/client', () => ({
9
+ api: {
10
+ get: vi.fn(),
11
+ // never-settling promise keeps VerifyEmailPage in its 'verifying' state
12
+ post: vi.fn(() => new Promise(() => {})),
13
+ },
14
+ }))
15
+ vi.mock('../../../../hooks/src/hooks/usePasswordReset', () => ({
16
+ usePasswordReset: () => ({ reset: vi.fn() }),
17
+ }))
18
+
19
+ import { ResetPasswordPage } from '../ResetPasswordPage'
20
+ import { VerifyEmailPage } from '../VerifyEmailPage'
21
+ // imports after mocks (mock hoisting applies regardless of position)
22
+
23
+ describe('auth 页 i18n 覆盖面(走查 #5)', () => {
24
+ it('ResetPasswordPage:title/successHint 可覆盖', () => {
25
+ render(<ResetPasswordPage title="Set a new password" successHint="Password reset; old sessions revoked." />)
26
+ expect(screen.getByText('Set a new password')).toBeInTheDocument()
27
+ })
28
+
29
+ it('VerifyEmailPage:三态标题可覆盖(无参渲染即 failed 态)', () => {
30
+ render(<MemoryRouter><VerifyEmailPage titles={{ failed: 'Verification failed' }} /></MemoryRouter>)
31
+ expect(screen.getByText('Verification failed')).toBeInTheDocument()
32
+ })
33
+ })