From 73b15fc5625dee2bdba5fbffc474a7492f470765 Mon Sep 17 00:00:00 2001
From: yyh <92089059+lyzno1@users.noreply.github.com>
Date: Wed, 19 Aug 2026 06:30:37 +0000
Subject: [PATCH] refactor(web): compose reset password fields (#40952)
---
oxlint-suppressions.json | 10 --
.../set-password/page.tsx | 108 ++++++++++--------
.../set-password/__tests__/page.spec.tsx | 31 +++++
web/app/reset-password/set-password/page.tsx | 108 ++++++++++--------
4 files changed, 149 insertions(+), 108 deletions(-)
diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json
index 447d378d032..0bfb65d1eb8 100644
--- a/oxlint-suppressions.json
+++ b/oxlint-suppressions.json
@@ -103,11 +103,6 @@
"count": 1
}
},
- "web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx": {
- "no-restricted-imports": {
- "count": 1
- }
- },
"web/app/(shareLayout)/webapp-signin/check-code/page.tsx": {
"no-restricted-imports": {
"count": 1
@@ -5184,11 +5179,6 @@
"count": 1
}
},
- "web/app/reset-password/set-password/page.tsx": {
- "no-restricted-imports": {
- "count": 1
- }
- },
"web/app/signin/layout.tsx": {
"typescript/no-explicit-any": {
"count": 1
diff --git a/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx b/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx
index 08200092f9d..204954e9e19 100644
--- a/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx
+++ b/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx
@@ -1,12 +1,15 @@
'use client'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
+import { Field, FieldDescription, FieldLabel } from '@langgenius/dify-ui/field'
+import { Form } from '@langgenius/dify-ui/form'
+import { IconButton } from '@langgenius/dify-ui/icon-button'
+import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCheckboxCircleFill } from '@remixicon/react'
import { useCountDown } from 'ahooks'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
-import Input from '@/app/components/base/input'
import { validPassword } from '@/config'
import useDocumentTitle from '@/hooks/use-document-title'
import { useRouter, useSearchParams } from '@/next/navigation'
@@ -96,68 +99,75 @@ const ChangePasswordForm = () => {
-
- {/* Password */}
-
-
-
-
+
+ setPassword(e.target.value)}
+ onValueChange={setPassword}
placeholder={t(($) => $.passwordPlaceholder, { ns: 'login' }) || ''}
/>
-
-
-
-
-
-
+
+
+
+
+
{t(($) => $['error.passwordInvalid'], { ns: 'login' })}
-
-
- {/* Confirm Password */}
-
-
-
-
+
+ setConfirmPassword(e.target.value)}
+ onValueChange={setConfirmPassword}
placeholder={t(($) => $.confirmPasswordPlaceholder, { ns: 'login' }) || ''}
/>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
)}
@@ -165,7 +175,7 @@ const ChangePasswordForm = () => {
-
+
{t(($) => $.passwordChangedTip, { ns: 'login' })}
diff --git a/web/app/reset-password/set-password/__tests__/page.spec.tsx b/web/app/reset-password/set-password/__tests__/page.spec.tsx
index bf7f0463fea..2f9d96f4e6e 100644
--- a/web/app/reset-password/set-password/__tests__/page.spec.tsx
+++ b/web/app/reset-password/set-password/__tests__/page.spec.tsx
@@ -1,4 +1,5 @@
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
import useDocumentTitle from '@/hooks/use-document-title'
import { useRouter, useSearchParams } from '@/next/navigation'
import { changePasswordWithToken } from '@/service/common'
@@ -81,6 +82,36 @@ describe('Reset Password Set Password Page', () => {
expect(mockUseDocumentTitle).toHaveBeenCalledWith('login.changePassword')
})
+ it('supports password reveal and native form submission', async () => {
+ const user = userEvent.setup()
+ render()
+
+ const passwordInput = screen.getByLabelText('common.account.newPassword')
+ const confirmPasswordInput = screen.getByLabelText('common.account.confirmPassword')
+
+ expect(passwordInput).toHaveAttribute('autocomplete', 'new-password')
+ expect(confirmPasswordInput).toHaveAttribute('autocomplete', 'new-password')
+
+ await user.type(passwordInput, 'ValidPass123!')
+ await user.click(screen.getAllByRole('button', { name: 'login.showPassword' })[0]!)
+
+ expect(passwordInput).toHaveAttribute('type', 'text')
+ expect(screen.getByRole('button', { name: 'login.hidePassword' })).toBeInTheDocument()
+
+ await user.type(confirmPasswordInput, 'ValidPass123!{Enter}')
+
+ await waitFor(() => {
+ expect(mockChangePasswordWithToken).toHaveBeenCalledWith({
+ url: '/forgot-password/resets',
+ body: {
+ token: 'reset-token',
+ new_password: 'ValidPass123!',
+ password_confirm: 'ValidPass123!',
+ },
+ })
+ })
+ })
+
describe('Post-reset navigation', () => {
it('should preserve redirect_url when the user returns to sign in manually', async () => {
setSearchParams({ token: 'reset-token', redirect_url: redirectUrl })
diff --git a/web/app/reset-password/set-password/page.tsx b/web/app/reset-password/set-password/page.tsx
index e5f09fc4a66..7ea375cf4a8 100644
--- a/web/app/reset-password/set-password/page.tsx
+++ b/web/app/reset-password/set-password/page.tsx
@@ -1,12 +1,15 @@
'use client'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
+import { Field, FieldDescription, FieldLabel } from '@langgenius/dify-ui/field'
+import { Form } from '@langgenius/dify-ui/form'
+import { IconButton } from '@langgenius/dify-ui/icon-button'
+import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCheckboxCircleFill } from '@remixicon/react'
import { useCountDown } from 'ahooks'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
-import Input from '@/app/components/base/input'
import { validPassword } from '@/config'
import useDocumentTitle from '@/hooks/use-document-title'
import { useRouter, useSearchParams } from '@/next/navigation'
@@ -109,68 +112,75 @@ const ChangePasswordForm = () => {
-
- {/* Password */}
-
-
-
-
+
+ setPassword(e.target.value)}
+ onValueChange={setPassword}
placeholder={t(($) => $.passwordPlaceholder, { ns: 'login' }) || ''}
/>
-
-
-
-
-
-
+
+
+
+
+
{t(($) => $['error.passwordInvalid'], { ns: 'login' })}
-
-
- {/* Confirm Password */}
-
-
-
-
+
+ setConfirmPassword(e.target.value)}
+ onValueChange={setConfirmPassword}
placeholder={t(($) => $.confirmPasswordPlaceholder, { ns: 'login' }) || ''}
/>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
)}
@@ -178,7 +188,7 @@ const ChangePasswordForm = () => {
-
+
{t(($) => $.passwordChangedTip, { ns: 'login' })}