diff --git a/app/profile-image/_components/TermsDrawer.tsx b/app/profile-image/_components/TermsDrawer.tsx index fb71e53..e2ef54d 100644 --- a/app/profile-image/_components/TermsDrawer.tsx +++ b/app/profile-image/_components/TermsDrawer.tsx @@ -1,6 +1,7 @@ "use client"; import React, { useState } from "react"; import { ChevronRight, ArrowLeft } from "lucide-react"; +import axios from "axios"; import Button from "@/components/ui/Button"; import { SelectCheckButton } from "./ProfileImageSelection"; import ProfileBottomSheet from "./ProfileBottomSheet"; @@ -8,6 +9,10 @@ import { TERMS_TEXT, PRIVACY_TEXT } from "../_constants/terms"; import { Hobby, ProfileSubmitData } from "@/lib/types/profile"; import { useProfile } from "@/providers/profile-provider"; import { useImageUpload, useProfileSignUp } from "@/hooks/useProfileSignUp"; +import { + useNicknameAvailability, + NicknameAvailabilityResponse, +} from "@/hooks/useNicknameAvailability"; import { useRouter } from "next/navigation"; import { HOBBIES, HobbyCategory } from "@/lib/constants/hobbies"; @@ -31,6 +36,7 @@ const TermsDrawer = ({ children }: TermsDrawerProps) => { const { mutateAsync: uploadImage } = useImageUpload(); const { mutate: signUp, isPending: isSubmitting } = useProfileSignUp(); + const { mutateAsync: checkNicknameAvailability } = useNicknameAvailability(); const checkGradient = "linear-gradient(220.53deg, #FF775E -18.87%, #FF4D61 62.05%, #E83ABC 125.76%)"; @@ -146,9 +152,46 @@ const TermsDrawer = ({ children }: TermsDrawerProps) => { const trigger = children ? React.cloneElement(children, { - onClick: (e: React.MouseEvent) => { + onClick: async (e: React.MouseEvent) => { children.props.onClick?.(e); - setIsOpen(true); + + const nickname = (profile.nickname || "").trim(); + + if (!nickname) { + alert("닉네임을 입력해 주세요."); + return; + } + + try { + const res = await checkNicknameAvailability(nickname); + + // 200 OK 응답 처리 + if (res.code === "GEN-000") { + const isAvailable = + typeof res.data === "object" ? res.data?.available : res.data; + if (isAvailable) { + setIsOpen(true); + } else { + alert("중복된 닉네임입니다. 다른 닉네임을 입력해 주세요."); + } + } + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const res = error.response?.data as NicknameAvailabilityResponse; + + // 백엔드에서 400 등 에러 코드를 보낼 때 (MEM-009 등) + if (res?.code === "MEM-009") { + alert("공백은 닉네임으로 사용할 수 없습니다."); + return; + } + } + + // 그 외 진짜 예상치 못한 에러 (네트워크, 500 등) + console.error("Failed to check nickname availability:", error); + alert( + "닉네임 중복 확인에 실패했습니다. 잠시 후 다시 시도해 주세요.", + ); + } }, }) : null; diff --git a/hooks/useNicknameAvailability.ts b/hooks/useNicknameAvailability.ts new file mode 100644 index 0000000..175a2f0 --- /dev/null +++ b/hooks/useNicknameAvailability.ts @@ -0,0 +1,35 @@ +import { api } from "@/lib/axios"; +import { useMutation } from "@tanstack/react-query"; + +export interface NicknameAvailabilityResponse { + code: string; + status: number; + message: string; + data?: + | boolean + | { + available?: boolean; + isAvailable?: boolean; + duplicate?: boolean; + }; +} + +const checkNicknameAvailability = async ( + nickname: string, +): Promise => { + const { data: response } = await api.get( + "/api/auth/signup/nickname/availability", + { + params: { nickname }, + }, + ); + + return response; +}; + +export const useNicknameAvailability = () => { + return useMutation({ + mutationFn: checkNicknameAvailability, + retry: false, + }); +};