264 lines
9.0 KiB
TypeScript
264 lines
9.0 KiB
TypeScript
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Eye, EyeOff, Loader2, ArrowLeft } from "lucide-react";
|
|
import { SignupFormData } from "@/types/auth";
|
|
import { ErrorMessage } from "./ErrorMessage";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
interface SignupFormProps {
|
|
formData: SignupFormData;
|
|
isLoading: boolean;
|
|
error: string;
|
|
showPassword: boolean;
|
|
validationErrors: Record<string, string>;
|
|
touchedFields: Record<string, boolean>;
|
|
isFormValid: boolean;
|
|
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
onBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
onSubmit: (e: React.FormEvent) => void;
|
|
onTogglePassword: () => void;
|
|
}
|
|
|
|
/**
|
|
* 회원가입 폼 컴포넌트
|
|
*/
|
|
export function SignupForm({
|
|
formData,
|
|
isLoading,
|
|
error,
|
|
showPassword,
|
|
validationErrors,
|
|
touchedFields,
|
|
isFormValid,
|
|
onInputChange,
|
|
onBlur,
|
|
onSubmit,
|
|
onTogglePassword,
|
|
}: SignupFormProps) {
|
|
const router = useRouter();
|
|
|
|
return (
|
|
<Card className="border-0 shadow-xl">
|
|
<CardHeader className="space-y-1">
|
|
<CardTitle className="text-center text-2xl">회원가입</CardTitle>
|
|
<CardDescription className="text-center">새로운 계정을 만들어보세요</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ErrorMessage message={error} />
|
|
|
|
<form onSubmit={onSubmit} className="space-y-4">
|
|
{/* 아이디 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="userId">아이디 *</Label>
|
|
<Input
|
|
id="userId"
|
|
name="userId"
|
|
type="text"
|
|
placeholder="아이디를 입력하세요"
|
|
value={formData.userId}
|
|
onChange={onInputChange}
|
|
onBlur={onBlur}
|
|
disabled={isLoading}
|
|
className="h-11"
|
|
required
|
|
/>
|
|
{touchedFields?.userId && validationErrors.userId && (
|
|
<p className="text-destructive text-xs">{validationErrors.userId}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 비밀번호 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="password">비밀번호 *</Label>
|
|
<div className="relative">
|
|
<Input
|
|
id="password"
|
|
name="password"
|
|
type={showPassword ? "text" : "password"}
|
|
placeholder="비밀번호를 입력하세요"
|
|
value={formData.password}
|
|
onChange={onInputChange}
|
|
onBlur={onBlur}
|
|
disabled={isLoading}
|
|
className="h-11 pr-10"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={onTogglePassword}
|
|
className="absolute top-1/2 right-3 -translate-y-1/2 transform cursor-pointer text-slate-400 transition-colors hover:text-slate-600"
|
|
disabled={isLoading}
|
|
>
|
|
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
</button>
|
|
</div>
|
|
{touchedFields?.password && validationErrors.password && (
|
|
<p className="text-destructive text-xs">{validationErrors.password}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 비밀번호 확인 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="passwordConfirm">비밀번호 확인 *</Label>
|
|
<div className="relative">
|
|
<Input
|
|
id="passwordConfirm"
|
|
name="passwordConfirm"
|
|
type={showPassword ? "text" : "password"}
|
|
placeholder="비밀번호를 다시 입력하세요"
|
|
value={formData.passwordConfirm}
|
|
onChange={onInputChange}
|
|
onBlur={onBlur}
|
|
disabled={isLoading}
|
|
className="h-11 pr-10"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={onTogglePassword}
|
|
className="absolute top-1/2 right-3 -translate-y-1/2 transform cursor-pointer text-slate-400 transition-colors hover:text-slate-600"
|
|
disabled={isLoading}
|
|
>
|
|
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
</button>
|
|
</div>
|
|
{touchedFields?.passwordConfirm && validationErrors.passwordConfirm && (
|
|
<p className="text-destructive text-xs">{validationErrors.passwordConfirm}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 이름 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="userName">이름 *</Label>
|
|
<Input
|
|
id="userName"
|
|
name="userName"
|
|
type="text"
|
|
placeholder="이름을 입력하세요"
|
|
value={formData.userName}
|
|
onChange={onInputChange}
|
|
onBlur={onBlur}
|
|
disabled={isLoading}
|
|
className="h-11"
|
|
required
|
|
/>
|
|
{touchedFields?.userName && validationErrors.userName && (
|
|
<p className="text-destructive text-xs">{validationErrors.userName}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 연락처 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="phoneNumber">연락처 *</Label>
|
|
<Input
|
|
id="phoneNumber"
|
|
name="phoneNumber"
|
|
type="text"
|
|
placeholder="010-1234-5678"
|
|
value={formData.phoneNumber}
|
|
onChange={onInputChange}
|
|
onBlur={onBlur}
|
|
disabled={isLoading}
|
|
className="h-11"
|
|
required
|
|
/>
|
|
{touchedFields?.phoneNumber && validationErrors.phoneNumber && (
|
|
<p className="text-destructive text-xs">{validationErrors.phoneNumber}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 면허번호 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="licenseNumber">면허번호 *</Label>
|
|
<Input
|
|
id="licenseNumber"
|
|
name="licenseNumber"
|
|
type="text"
|
|
placeholder="12-34-567890-12"
|
|
value={formData.licenseNumber}
|
|
onChange={onInputChange}
|
|
onBlur={onBlur}
|
|
disabled={isLoading}
|
|
className="h-11"
|
|
required
|
|
/>
|
|
{touchedFields?.licenseNumber && validationErrors.licenseNumber && (
|
|
<p className="text-destructive text-xs">{validationErrors.licenseNumber}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 차량번호 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="vehicleNumber">차량번호 *</Label>
|
|
<Input
|
|
id="vehicleNumber"
|
|
name="vehicleNumber"
|
|
type="text"
|
|
placeholder="12가1234"
|
|
value={formData.vehicleNumber}
|
|
onChange={onInputChange}
|
|
onBlur={onBlur}
|
|
disabled={isLoading}
|
|
className="h-11"
|
|
required
|
|
/>
|
|
{touchedFields?.vehicleNumber && validationErrors.vehicleNumber && (
|
|
<p className="text-destructive text-xs">{validationErrors.vehicleNumber}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 차량 타입 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="vehicleType">차량 타입 *</Label>
|
|
<Input
|
|
id="vehicleType"
|
|
name="vehicleType"
|
|
type="text"
|
|
placeholder="예: 1톤, 5톤, 11톤, 25톤"
|
|
value={formData.vehicleType}
|
|
onChange={onInputChange}
|
|
onBlur={onBlur}
|
|
disabled={isLoading}
|
|
className="h-11"
|
|
required
|
|
/>
|
|
{touchedFields?.vehicleType && validationErrors.vehicleType && (
|
|
<p className="text-destructive text-xs">{validationErrors.vehicleType}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* 회원가입 버튼 */}
|
|
<Button
|
|
type="submit"
|
|
className="h-11 w-full bg-slate-900 font-medium text-white hover:bg-slate-800"
|
|
disabled={isLoading || !isFormValid}
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
가입 중...
|
|
</>
|
|
) : (
|
|
"회원가입(공차중계)"
|
|
)}
|
|
</Button>
|
|
|
|
{/* 로그인으로 돌아가기 버튼 */}
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
className="h-11 w-full font-medium"
|
|
onClick={() => router.push("/login")}
|
|
disabled={isLoading}
|
|
>
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
로그인으로 돌아가기
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|