Skip to content

Commit d3aea58

Browse files
bugfix
1 parent 2c0afcb commit d3aea58

File tree

11 files changed

+415
-142
lines changed

11 files changed

+415
-142
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use server'
2+
3+
import { apiClient } from '@/lib/api-client'
4+
import { authOptions } from '@/lib/auth'
5+
import { getServerSession } from 'next-auth'
6+
import type { z } from 'zod'
7+
import { changePasswordFormSchema } from '@/lib/validation'
8+
9+
export type ChangePasswordFormSchema = z.infer<typeof changePasswordFormSchema>
10+
11+
export async function changePasswordAction(
12+
data: ChangePasswordFormSchema
13+
): Promise<true | { [key: string]: string[] }> {
14+
try {
15+
// 获取当前用户的会话以获取 token
16+
const session = await getServerSession(authOptions)
17+
18+
if (!session?.accessToken) {
19+
return {
20+
password: ['Authentication required']
21+
}
22+
}
23+
24+
// 调用 API 修改密码,传递 token
25+
await apiClient.post(
26+
'/api/users/change-password/',
27+
{
28+
password: data.password,
29+
password_new: data.passwordNew,
30+
password_retype: data.passwordRetype
31+
},
32+
{
33+
token: session.accessToken
34+
}
35+
)
36+
37+
return true
38+
} catch (error: any) {
39+
// 处理 API 错误
40+
if (error.data && typeof error.data === 'object') {
41+
return error.data
42+
}
43+
44+
return {
45+
password: ['Failed to change password']
46+
}
47+
}
48+
}
49+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use server'
2+
3+
import { apiClient } from '@/lib/api-client'
4+
import { authOptions } from '@/lib/auth'
5+
import { getServerSession } from 'next-auth'
6+
import type { z } from 'zod'
7+
import { deleteAccountFormSchema } from '@/lib/validation'
8+
9+
export type DeleteAccountFormSchema = z.infer<typeof deleteAccountFormSchema>
10+
11+
export async function deleteAccountAction(
12+
data: DeleteAccountFormSchema
13+
): Promise<boolean> {
14+
try {
15+
// 获取当前用户的会话以获取 token
16+
const session = await getServerSession(authOptions)
17+
18+
if (!session?.accessToken) {
19+
return false
20+
}
21+
22+
// 调用 API 删除账户,传递 token
23+
await apiClient.delete('/api/users/delete-account/', {
24+
token: session.accessToken
25+
})
26+
27+
return true
28+
} catch (error: any) {
29+
console.error('Failed to delete account:', error)
30+
return false
31+
}
32+
}
33+
Lines changed: 86 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,125 @@
1-
'use client'
1+
"use client";
22

3-
import type { changePasswordAction } from '@/actions/change-password-action'
4-
import { fieldApiError } from '@/lib/forms'
5-
import { changePasswordFormSchema } from '@/lib/validation'
6-
import { FormHeader } from '@frontend/ui/forms/form-header'
7-
import { SubmitField } from '@frontend/ui/forms/submit-field'
8-
import { TextField } from '@frontend/ui/forms/text-field'
9-
import { SuccessMessage } from '@frontend/ui/messages/success-message'
10-
import { zodResolver } from '@hookform/resolvers/zod'
11-
import { useState } from 'react'
12-
import { useForm } from 'react-hook-form'
13-
import type { z } from 'zod'
3+
import type { changePasswordAction } from "@/actions/change-password-action";
4+
import { changePasswordFormSchema } from "@/lib/validation";
5+
import { FormHeader } from "@frontend/ui/forms/form-header";
6+
import { SubmitField } from "@frontend/ui/forms/submit-field";
7+
import { TextField } from "@frontend/ui/forms/text-field";
8+
import { ErrorMessage } from "@frontend/ui/messages/error-message";
9+
import { SuccessMessage } from "@frontend/ui/messages/success-message";
10+
import { zodResolver } from "@hookform/resolvers/zod";
11+
import { useState } from "react";
12+
import { useForm } from "react-hook-form";
13+
import type { z } from "zod";
1414

15-
export type ChangePasswordFormSchema = z.infer<typeof changePasswordFormSchema>
15+
export type ChangePasswordFormSchema = z.infer<typeof changePasswordFormSchema>;
1616

1717
export function ChangePaswordForm({
18-
onSubmitHandler
18+
onSubmitHandler,
1919
}: {
20-
onSubmitHandler: typeof changePasswordAction
20+
onSubmitHandler: typeof changePasswordAction;
2121
}) {
22-
const [success, setSuccess] = useState<boolean>(false)
22+
const [message, setMessage] = useState<{
23+
type: "success" | "error";
24+
content: string | string[];
25+
} | null>(null);
2326

24-
const { formState, handleSubmit, register, reset, setError } =
27+
const { formState, handleSubmit, register, reset } =
2528
useForm<ChangePasswordFormSchema>({
26-
resolver: zodResolver(changePasswordFormSchema)
27-
})
29+
resolver: zodResolver(changePasswordFormSchema),
30+
shouldUseNativeValidation: false,
31+
defaultValues: {
32+
password: "",
33+
passwordNew: "",
34+
passwordRetype: "",
35+
},
36+
});
2837

2938
return (
3039
<>
3140
<FormHeader
32-
title="Set new account password"
33-
description="Change sign in access password"
41+
title='Set new account password'
42+
description='Change sign in access password'
3443
/>
3544

36-
{success && (
37-
<SuccessMessage>Password has been successfully changed</SuccessMessage>
45+
{message?.type === "success" && (
46+
<SuccessMessage>{message.content}</SuccessMessage>
47+
)}
48+
49+
{message?.type === "error" && (
50+
<ErrorMessage>
51+
{Array.isArray(message.content) ? (
52+
<ul className='list-disc list-inside'>
53+
{message.content.map((msg, index) => (
54+
<li key={index}>{msg}</li>
55+
))}
56+
</ul>
57+
) : (
58+
message.content
59+
)}
60+
</ErrorMessage>
3861
)}
3962

4063
<form
41-
method="post"
64+
method='post'
65+
noValidate
66+
autoComplete='off'
4267
onSubmit={handleSubmit(async (data) => {
43-
const res = await onSubmitHandler(data)
68+
setMessage(null);
69+
70+
const res = await onSubmitHandler(data);
4471

45-
if (res !== true && typeof res !== 'boolean') {
46-
setSuccess(false)
47-
fieldApiError('password', 'password', res, setError)
48-
fieldApiError('password_new', 'passwordNew', res, setError)
49-
fieldApiError('password_retype', 'passwordRetype', res, setError)
50-
} else {
51-
reset()
52-
setSuccess(true)
72+
if (res === true) {
73+
reset();
74+
setMessage({
75+
type: "success",
76+
content: "Password has been successfully changed",
77+
});
78+
} else if (res && typeof res === "object") {
79+
// 收集所有错误消息
80+
const errors: string[] = [];
81+
for (const [field, messages] of Object.entries(res)) {
82+
if (Array.isArray(messages)) {
83+
errors.push(...messages);
84+
} else {
85+
errors.push(String(messages));
86+
}
87+
}
88+
setMessage({
89+
type: "error",
90+
content: errors,
91+
});
5392
}
5493
})}
5594
>
5695
<TextField
57-
type="text"
58-
register={register('password')}
59-
label="Current password"
96+
type='password'
97+
register={register("password")}
98+
label='Current password'
6099
formState={formState}
100+
autoComplete='current-password'
61101
/>
62102

63103
<TextField
64-
type="text"
65-
register={register('passwordNew')}
66-
label="New password"
104+
type='password'
105+
register={register("passwordNew")}
106+
label='New password'
67107
formState={formState}
108+
autoComplete='new-password'
68109
/>
69110

70111
<TextField
71-
type="text"
72-
register={register('passwordRetype')}
73-
label="Retype password"
112+
type='password'
113+
register={register("passwordRetype")}
114+
label='Retype password'
74115
formState={formState}
116+
autoComplete='new-password'
75117
/>
76118

77-
<SubmitField isLoading={formState.isLoading}>
119+
<SubmitField isLoading={formState.isSubmitting}>
78120
Change password
79121
</SubmitField>
80122
</form>
81123
</>
82-
)
124+
);
83125
}
Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,103 @@
1-
'use client'
1+
"use client";
22

33
import type {
44
DeleteAccountFormSchema,
5-
deleteAccountAction
6-
} from '@/actions/delete-account-action'
7-
import { deleteAccountFormSchema } from '@/lib/validation'
8-
import { FormHeader } from '@frontend/ui/forms/form-header'
9-
import { SubmitField } from '@frontend/ui/forms/submit-field'
10-
import { TextField } from '@frontend/ui/forms/text-field'
11-
import { zodResolver } from '@hookform/resolvers/zod'
12-
import { signOut, useSession } from 'next-auth/react'
13-
import { useEffect } from 'react'
14-
import { useForm } from 'react-hook-form'
5+
deleteAccountAction,
6+
} from "@/actions/delete-account-action";
7+
import { deleteAccountFormSchema } from "@/lib/validation";
8+
import { FormHeader } from "@frontend/ui/forms/form-header";
9+
import { SubmitField } from "@frontend/ui/forms/submit-field";
10+
import { TextField } from "@frontend/ui/forms/text-field";
11+
import { ErrorMessage } from "@frontend/ui/messages/error-message";
12+
import { zodResolver } from "@hookform/resolvers/zod";
13+
import { signOut, useSession } from "next-auth/react";
14+
import { useEffect, useState } from "react";
15+
import { useForm } from "react-hook-form";
1516

1617
export function DeleteAccountForm({
17-
onSubmitHandler
18-
}: { onSubmitHandler: typeof deleteAccountAction }) {
19-
const session = useSession()
18+
onSubmitHandler,
19+
}: {
20+
onSubmitHandler: typeof deleteAccountAction;
21+
}) {
22+
const session = useSession();
23+
const [message, setMessage] = useState<{
24+
type: "success" | "error";
25+
content: string | string[];
26+
} | null>(null);
2027

2128
const { formState, handleSubmit, register, reset, setValue } =
2229
useForm<DeleteAccountFormSchema>({
23-
resolver: zodResolver(deleteAccountFormSchema)
24-
})
30+
resolver: zodResolver(deleteAccountFormSchema),
31+
defaultValues: {
32+
username: "",
33+
usernameCurrent: "",
34+
},
35+
});
2536

2637
useEffect(() => {
2738
if (session.data?.user.username) {
28-
setValue('usernameCurrent', session.data?.user.username)
39+
setValue("usernameCurrent", session.data?.user.username);
2940
}
30-
}, [setValue, session.data?.user.username])
41+
}, [setValue, session.data?.user.username]);
3142

3243
return (
3344
<>
3445
<FormHeader
35-
title="Delete your account"
36-
description="After this action all data will be lost"
46+
title='Delete your account'
47+
description='After this action all data will be lost'
3748
/>
3849

50+
{message?.type === "error" && (
51+
<ErrorMessage>
52+
{Array.isArray(message.content) ? (
53+
<ul className='list-disc list-inside'>
54+
{message.content.map((msg, index) => (
55+
<li key={index}>{msg}</li>
56+
))}
57+
</ul>
58+
) : (
59+
message.content
60+
)}
61+
</ErrorMessage>
62+
)}
63+
3964
<form
40-
method="post"
65+
method='post'
66+
noValidate
4167
onSubmit={handleSubmit(async (data) => {
42-
const res = await onSubmitHandler(data)
68+
setMessage(null);
69+
70+
const res = await onSubmitHandler(data);
4371

44-
if (res) {
45-
reset()
46-
signOut()
72+
if (res === true) {
73+
reset();
74+
signOut();
75+
} else if (res && typeof res === "object") {
76+
const errors: string[] = [];
77+
for (const [field, messages] of Object.entries(res)) {
78+
if (Array.isArray(messages)) {
79+
errors.push(...messages);
80+
} else {
81+
errors.push(String(messages));
82+
}
83+
}
84+
setMessage({
85+
type: "error",
86+
content: errors,
87+
});
4788
}
4889
})}
4990
>
5091
<TextField
51-
type="text"
52-
register={register('username')}
53-
label="Username"
92+
type='text'
93+
register={register("username")}
94+
label='Username'
5495
formState={formState}
96+
autoComplete='off'
5597
/>
5698

5799
<SubmitField>Delete account</SubmitField>
58100
</form>
59101
</>
60-
)
102+
);
61103
}

0 commit comments

Comments
 (0)