You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
6.3 KiB
195 lines
6.3 KiB
"use client";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormMessage,
|
|
} from "@/components/ui/form";
|
|
import { Input } from "@/components/ui/input";
|
|
import { api } from "@/lib/api";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import axios from "axios";
|
|
import Link from "next/link";
|
|
import { useRouter } from "next/navigation";
|
|
import { useState } from "react";
|
|
import { useForm } from "react-hook-form";
|
|
import z, { email } from "zod";
|
|
|
|
const SignupSchema = z
|
|
.object({
|
|
name: z.string().min(1, "아이디를 입력하세요."),
|
|
password: z.string().min(4, "비밀번호는 4자 이상이어야 합니다."),
|
|
confirmPassword: z.string().min(1, "비밀번호 확인을 입력하세요."),
|
|
email: z.email("이메일 형식이 아닙니다.").optional(),
|
|
})
|
|
.refine((v) => v.password === v.confirmPassword, {
|
|
path: ["confirmPassword"],
|
|
message: "비밀번호가 일치하지 않습니다.",
|
|
});
|
|
|
|
type SignupInput = z.infer<typeof SignupSchema>;
|
|
|
|
export default function SignupPage() {
|
|
const form = useForm<SignupInput>({
|
|
resolver: zodResolver(SignupSchema),
|
|
defaultValues: {
|
|
name: "",
|
|
password: "",
|
|
confirmPassword: "",
|
|
email: undefined,
|
|
},
|
|
mode: "onTouched",
|
|
});
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [ok, setOk] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const router = useRouter();
|
|
|
|
const onSubmit = async (values: SignupInput) => {
|
|
setError(null);
|
|
setOk(null);
|
|
setLoading(true);
|
|
try {
|
|
const payload: Record<string, unknown> = {
|
|
name: values.name.trim(),
|
|
password: values.password,
|
|
};
|
|
const emailTrim = values.email?.trim();
|
|
if (emailTrim) payload.email = emailTrim;
|
|
|
|
await api.post("/auth/signup", payload);
|
|
setOk("회원가입이 완료되어습니다. 로그인 페이지로 이동합니다.");
|
|
setTimeout(() => router.replace("/login"), 800);
|
|
} catch (e: unknown) {
|
|
if (axios.isAxiosError(e)) {
|
|
setError(
|
|
e.request?.data?.message ?? e.message ?? "회원가입에 실패했습니다."
|
|
);
|
|
} else if (e instanceof Error) {
|
|
setError(e.message);
|
|
} else {
|
|
setError("회원가입에 실패했습니다.");
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<div className="mx-auto w-full max-w-sm">
|
|
<Card className="shadow-lg">
|
|
<CardHeader>
|
|
<CardTitle>회원가입</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-y-2 pb-4">
|
|
<Form {...form}>
|
|
<form
|
|
className="space-y-4"
|
|
onSubmit={form.handleSubmit(onSubmit)}
|
|
>
|
|
{/* name */}
|
|
<FormField
|
|
control={form.control}
|
|
name="name"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<Input
|
|
placeholder="아이디"
|
|
autoComplete="username"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
{/* email */}
|
|
<FormField
|
|
control={form.control}
|
|
name="email"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<Input
|
|
placeholder="이메일(선택)"
|
|
type="email"
|
|
autoComplete="email"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
{/* password */}
|
|
<FormField
|
|
control={form.control}
|
|
name="password"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<Input
|
|
placeholder="비밀번호"
|
|
type="password"
|
|
autoComplete="new-password"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
{/* confirmPassword */}
|
|
<FormField
|
|
control={form.control}
|
|
name="confirmPassword"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormControl>
|
|
<Input
|
|
placeholder="비밀번호 확인"
|
|
type="password"
|
|
autoComplete="new-password"
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
<Button type="submit" className="w-full" disabled={loading}>
|
|
{loading ? "처리 중..." : "가입하기"}
|
|
</Button>
|
|
|
|
{error && (
|
|
<p className="text-center text-sm text-red-600">{error}</p>
|
|
)}
|
|
{ok && (
|
|
<p className="text-center text-sm text-green-600">{ok}</p>
|
|
)}
|
|
|
|
<p className="text-center text-xs text-gray-500">
|
|
이미 계정이 있으신가요?{" "}
|
|
<Link href="/login" className="underline">
|
|
로그인
|
|
</Link>
|
|
</p>
|
|
</form>
|
|
</Form>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|