diff --git a/tone-page-server/src/user/dto/update-user-password.dto.ts b/tone-page-server/src/user/dto/update-user-password.dto.ts new file mode 100644 index 0000000..e437759 --- /dev/null +++ b/tone-page-server/src/user/dto/update-user-password.dto.ts @@ -0,0 +1,10 @@ +import { IsString, Length, Matches } from "class-validator"; + +export class UpdateUserPasswordDto { + @IsString({ message: '密码不得为空' }) + @Length(6, 32, { message: '密码长度只能为6~32' }) + @Matches(/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{6,32}$/, + { message: '密码必须包含字母和数字,且长度在6~32之间' } + ) + password: string; +} \ No newline at end of file diff --git a/tone-page-server/src/user/user.controller.ts b/tone-page-server/src/user/user.controller.ts index 4a14c4a..78603d6 100644 --- a/tone-page-server/src/user/user.controller.ts +++ b/tone-page-server/src/user/user.controller.ts @@ -1,21 +1,30 @@ -import { Controller, Get, Injectable, Request, UnauthorizedException, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Put, Request, UseGuards } from '@nestjs/common'; import { UserService } from './user.service'; import { AuthGuard } from '@nestjs/passport'; +import { UpdateUserPasswordDto } from './dto/update-user-password.dto'; +import { AuthService } from 'src/auth/auth.service'; @Controller('user') export class UserController { constructor( - private readonly userService: UserService + private readonly userService: UserService, + private readonly authService: AuthService, ) { } @UseGuards(AuthGuard('jwt')) @Get('me') async getMe(@Request() req) { const { user } = req; - if (!user || !user.userId) { - throw new UnauthorizedException('Unauthorized'); - } return this.userService.findOne({ userId: user.userId }); } + + @UseGuards(AuthGuard('jwt')) + @Put('password') + async update( + @Request() req, + @Body() dto: UpdateUserPasswordDto, + ) { + return this.userService.setPassword(req.user.userId, dto.password); + } } diff --git a/tone-page-web/components/nav-user.tsx b/tone-page-web/components/nav-user.tsx index d7f041c..8ce6421 100644 --- a/tone-page-web/components/nav-user.tsx +++ b/tone-page-web/components/nav-user.tsx @@ -32,6 +32,8 @@ import { Skeleton } from "./ui/skeleton" import { toast } from "sonner" import { useRouter } from "next/navigation" import { ApiError } from "next/dist/server/api-utils" +import SetPassword from "./nav-user/SetPassword" +import { useState } from "react" export function NavUser({ }: {}) { const { isMobile } = useSidebar(); @@ -68,86 +70,92 @@ export function NavUser({ }: {}) { } } + const [passwordOpen, setPasswordOpen] = useState(false); + return ( - - - - - + + + + + + { + user && <> + + + U + +
+ {user.nickname} + {user.username} +
+ + } + { + isLoading &&
+ +
+ + +
+
+ } + +
+
+ - { - user && <> - - - U - -
- {user.nickname} - {user.username} + + { + user && +
+ + + U + +
+ {user.nickname} + {user.username} +
- - } - { - isLoading &&
- -
- - + } + { + isLoading &&
+ +
+ + +
-
- } - - - - - - { - user && -
- - - U - -
- {user.nickname} - {user.username} -
-
- } - { - isLoading &&
- -
- - -
-
- } -
- - - - 账户信息 - - - - 修改密码 - - - - - 登出 - -
- - - + } + + + + + 账户信息 + + setPasswordOpen(true)}> + + 修改密码 + + + + + 登出 + + + + + + + + ) } diff --git a/tone-page-web/components/nav-user/SetPassword.tsx b/tone-page-web/components/nav-user/SetPassword.tsx new file mode 100644 index 0000000..cf0740b --- /dev/null +++ b/tone-page-web/components/nav-user/SetPassword.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { SeparatorProps } from "@radix-ui/react-separator"; +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { FC } from "react"; +import { DialogProps } from "@radix-ui/react-dialog"; +import { toast } from "sonner"; +import { UserApi } from "@/lib/api"; +import { ApiError } from "next/dist/server/api-utils"; + +export default function SetPassword({ onOpenChange, ...props }: React.ComponentProps>) { + async function handleSetPassword(password: string) { + if (! /^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{6,32}$/.test(password)) { + toast.error('新密码不符合规范,请重新输入'); + return; + } + + try { + await UserApi.updatePassword(password); + toast.success('新密码设置成功'); + onOpenChange?.(false); + } catch (error) { + toast.error((error as ApiError).message || '新密码设置失败'); + } + } + + return ( + + + + 修改密码 + + 新密码长度在6-32位之间,且至少包含一个字母和一个数字,可以包含特殊字符 + + + +
{ + e.preventDefault(); + const formData = new FormData(e.currentTarget); + const password = formData.get('password') as string; + + handleSetPassword(password); + }}> +
+
+ + +
+
+ + + + +
+
+
+ ) +} \ No newline at end of file diff --git a/tone-page-web/lib/api/user/index.ts b/tone-page-web/lib/api/user/index.ts index 57dfe1e..ccc906e 100644 --- a/tone-page-web/lib/api/user/index.ts +++ b/tone-page-web/lib/api/user/index.ts @@ -1 +1,2 @@ -export * from './me'; \ No newline at end of file +export * from './me'; +export * from './updatePassword'; \ No newline at end of file diff --git a/tone-page-web/lib/api/user/updatePassword.ts b/tone-page-web/lib/api/user/updatePassword.ts new file mode 100644 index 0000000..04ba91d --- /dev/null +++ b/tone-page-web/lib/api/user/updatePassword.ts @@ -0,0 +1,10 @@ +import fetcher from "../fetcher"; + +export async function updatePassword(password: string) { + return fetcher(`/api/user/password`, { + method: 'PUT', + body: JSON.stringify({ + password: password, + }), + }) +} \ No newline at end of file