From 910628fdabd500b0913e29440e1bbbe43d2a33e2 Mon Sep 17 00:00:00 2001 From: kcwww Date: Sat, 16 Nov 2024 23:06:24 +0900 Subject: [PATCH] fix: router error --- app/(protected)/main/page.tsx | 1 + .../nickname/_components/NicknameForm.tsx | 146 ++++++++++++++++ app/(protected)/nickname/page.tsx | 165 +++--------------- app/globals.css | 2 +- auth.ts | 3 +- shared/database/mongodb/models/userModel.ts | 4 +- shared/types/user.d.ts | 2 +- 7 files changed, 178 insertions(+), 145 deletions(-) create mode 100644 app/(protected)/nickname/_components/NicknameForm.tsx diff --git a/app/(protected)/main/page.tsx b/app/(protected)/main/page.tsx index de21e29..3d66f04 100644 --- a/app/(protected)/main/page.tsx +++ b/app/(protected)/main/page.tsx @@ -32,6 +32,7 @@ const getUserData = async () => { const uuid = uuidv4(); const initUser: IUser = { email: user.email, + uid: user.uid, crystal_id: [], uuid, username: null, diff --git a/app/(protected)/nickname/_components/NicknameForm.tsx b/app/(protected)/nickname/_components/NicknameForm.tsx new file mode 100644 index 0000000..5fc52b7 --- /dev/null +++ b/app/(protected)/nickname/_components/NicknameForm.tsx @@ -0,0 +1,146 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +import { useRouter } from 'next/navigation'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { toast } from 'sonner'; + +import { sessionUser } from '@/shared/types/user'; +import { BACKEND_ROUTES, ROUTES } from '@/shared/constants/routes'; +import clientComponentFetch from '@/shared/utils/fetch/clientComponentFetch'; + +import { Button } from '@/components/ui/button'; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Label } from '@/components/ui/label'; +import Link from 'next/link'; +import { cn } from '@/lib/utils'; + +const formSchema = z.object({ + username: z + .string() + .min(1, { + message: '유저 이름은 최소 1글자 이상이어야 합니다.', + }) + .max(10, { + message: '유저 이름은 최대 10글자 이하여야 합니다.', + }), +}); + +const NicknameForm = ({ user }: { user: sessionUser }) => { + const [check, setCheck] = useState(false); + const [submit, setSubmit] = useState(false); + const router = useRouter(); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + username: '', + }, + }); + + useEffect(() => { + return () => { + setCheck(false); + setSubmit(false); + }; + }, []); + + const onSubmit = async (values: z.infer) => { + setSubmit(true); + const data = { + username: values.username, + email: user.email, + provider: user.provider, + }; + + try { + const response = await clientComponentFetch(BACKEND_ROUTES.NICKNAME, { + method: 'POST', + body: JSON.stringify(data), + }); + if (response.ok) { + router.replace(ROUTES.MAKE); + router.refresh(); + } + } catch (error) { + toast.error('유저 이름을 저장하는데 실패했습니다.'); + setSubmit(false); + console.error(error); + } + }; + + const isLoading = form.formState.isSubmitting; + + return ( +
+ + ( + + + 사용하실 이름을 입력해주세요 + + + + + + 이 이름은 다른 사용자들에게 보여지는 이름이 됩니다. +
+ 부적절한 닉네임은 서비스 이용에 제한이 있을 수 있습니다. +
+ +
+ )} + /> +
+ setCheck(!check)} + id="terms&policy" + /> + +
+ + + + + ); +}; + +export default NicknameForm; diff --git a/app/(protected)/nickname/page.tsx b/app/(protected)/nickname/page.tsx index 8badb70..cafb3b7 100644 --- a/app/(protected)/nickname/page.tsx +++ b/app/(protected)/nickname/page.tsx @@ -1,95 +1,33 @@ -'use client'; - -import { useState, useEffect } from 'react'; - -import { useRouter } from 'next/navigation'; -import { useSession } from 'next-auth/react'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; -import { toast } from 'sonner'; - -import { Button } from '@/components/ui/button'; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import { sessionUser } from '@/shared/types/user'; -import { BACKEND_ROUTES, ROUTES } from '@/shared/constants/routes'; -import clientComponentFetch from '@/shared/utils/fetch/clientComponentFetch'; +import { auth, signOut } from '@/auth'; import { Toaster } from 'sonner'; -import { Checkbox } from '@/components/ui/checkbox'; -import { Label } from '@/components/ui/label'; -import Link from 'next/link'; -import { cn } from '@/lib/utils'; - -const formSchema = z.object({ - username: z - .string() - .min(1, { - message: '유저 이름은 최소 1글자 이상이어야 합니다.', - }) - .max(10, { - message: '유저 이름은 최대 10글자 이하여야 합니다.', - }), -}); -const Nickname = () => { - const { data: session } = useSession(); - const [check, setCheck] = useState(false); - const [submit, setSubmit] = useState(false); - - const router = useRouter(); - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - username: '', - }, - }); - - useEffect(() => { - if (!session) { - router.replace(ROUTES.LANDING); - } - - return () => { - setCheck(false); - setSubmit(false); - }; - }, []); - - const onSubmit = async (values: z.infer) => { - setSubmit(true); - const user = session?.user as sessionUser; - const data = { - username: values.username, +import NicknameForm from '@/app/(protected)/nickname/_components/NicknameForm'; +import { sessionUser, User as UserType } from '@/shared/types/user'; +import { redirect } from 'next/navigation'; +import { ROUTES } from '@/shared/constants/routes'; +import User from '@/shared/database/mongodb/models/userModel'; +import { connectToMongoDB } from '@/shared/database/mongodb/config'; + +const Nickname = async () => { + const session = await auth(); + if (!session) return redirect(ROUTES.LANDING); + + const user = session.user as sessionUser; + await connectToMongoDB(); + const result = ( + await User.findOne({ email: user.email, + uid: user.uid, provider: user.provider, - }; + }) + )?.toObject() as UserType; - try { - const response = await clientComponentFetch(BACKEND_ROUTES.NICKNAME, { - method: 'POST', - body: JSON.stringify(data), - }); - if (response.ok) { - router.replace(ROUTES.MAKE); - } - } catch (error) { - toast.error('유저 이름을 저장하는데 실패했습니다.'); - setSubmit(false); - console.error(error); - } - }; + if (!result) { + signOut(); + redirect(ROUTES.LANDING); + } - const isLoading = form.formState.isSubmitting; + if (result.username !== null) redirect(ROUTES.MAIN); return ( <> @@ -97,60 +35,7 @@ const Nickname = () => {

수정 구슬 속 내 마음

-
- - ( - - - 사용하실 이름을 입력해주세요 - - - - - - 이 이름은 다른 사용자들에게 보여지는 이름이 됩니다. -
- 부적절한 닉네임은 서비스 이용에 제한이 있을 수 있습니다. -
- -
- )} - /> -
- setCheck(!check)} - id="terms&policy" - /> - -
- - - - +
diff --git a/app/globals.css b/app/globals.css index 794c887..1c53b59 100644 --- a/app/globals.css +++ b/app/globals.css @@ -144,7 +144,7 @@ html::-webkit-scrollbar { } .ui-section { - @apply pointer-events-none relative flex min-h-svh w-full flex-col items-center justify-between overflow-hidden px-10 pb-20 pt-10; + @apply pointer-events-none relative flex min-h-dvh w-full flex-col items-center justify-between overflow-hidden px-10 pb-20 pt-10; z-index: 1; } diff --git a/auth.ts b/auth.ts index bb36af8..7ec6106 100644 --- a/auth.ts +++ b/auth.ts @@ -51,9 +51,8 @@ export const { auth, handlers, signIn, signOut, unstable_update } = NextAuth({ }, session: async ({ session, token }: any) => { - session.user.name = token.id; + session.user.uid = token.id; session.user.provider = token.provider; - return session; }, }, diff --git a/shared/database/mongodb/models/userModel.ts b/shared/database/mongodb/models/userModel.ts index 1c6b9a1..17c26dc 100644 --- a/shared/database/mongodb/models/userModel.ts +++ b/shared/database/mongodb/models/userModel.ts @@ -4,6 +4,7 @@ import Crystal from '@/shared/database/mongodb/models/crystalModel'; export interface IUser { email: string; uuid: string; + uid: string; crystal_id: mongoose.Schema.Types.ObjectId[] | string[] | []; username: string | null; provider: string; @@ -19,6 +20,7 @@ export interface IUserDocument extends IUser, Document { const userSchema = new mongoose.Schema( { email: { type: String, required: true }, + uid: { type: String, required: true }, uuid: { type: String, required: true }, crystal_id: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Crystal' }], username: { type: String, default: null }, @@ -30,7 +32,7 @@ const userSchema = new mongoose.Schema( ); // 복합 인덱스 생성 -userSchema.index({ email: 1, provider: 1 }, { unique: true }); +userSchema.index({ email: 1, provider: 1, uid: 1 }, { unique: true }); // User 모델의 프리 훅을 사용하여 관련 Crystal 및 Message 모두 삭제 userSchema.pre('findOneAndDelete', async function (next) { diff --git a/shared/types/user.d.ts b/shared/types/user.d.ts index 1a54c09..101271d 100644 --- a/shared/types/user.d.ts +++ b/shared/types/user.d.ts @@ -7,7 +7,7 @@ export type User = { } & IUser; export type sessionUser = { - name: string; + uid: string; provider: string; email: string; };