Files
sam-react-prod/src/components/auth/SignupPage.tsx

589 lines
23 KiB
TypeScript
Raw Normal View History

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { LanguageSelect } from "@/components/LanguageSelect";
import { ThemeSelect } from "@/components/ThemeSelect";
import { companyInfoSchema, userInfoSchema, planSelectionSchema } from "@/lib/validations/auth";
import {
ArrowLeft,
Building2,
User,
Mail,
Phone,
Lock,
Briefcase,
Users,
FileText
} from "lucide-react";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
export function SignupPage() {
const router = useRouter();
const [step, setStep] = useState(1);
const [formData, setFormData] = useState({
// 회사 정보
companyName: "",
businessNumber: "",
industry: "",
companySize: "",
// 담당자 정보
name: "",
position: "",
email: "",
phone: "",
userId: "",
password: "",
passwordConfirm: "",
// 플랜 및 추천인
plan: "demo",
salesCode: "",
// 약관
agreeTerms: false,
agreePrivacy: false,
});
const handleInputChange = (field: string, value: string | boolean) => {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleSubmit = () => {
// 회원가입 처리 (실제로는 API 호출)
const userData = {
...formData,
role: "CEO", // 기본 역할
};
// Save user data to localStorage (client-side only)
if (typeof window !== "undefined") {
localStorage.setItem("user", JSON.stringify(userData));
}
// Navigate to dashboard
router.push("/dashboard");
};
const [stepErrors, setStepErrors] = useState<{ [key: string]: string }>({});
const validateStep1 = () => {
const result = companyInfoSchema.safeParse({
companyName: formData.companyName,
businessNumber: formData.businessNumber,
industry: formData.industry,
companySize: formData.companySize,
});
if (!result.success) {
const firstError = result.error.issues[0];
setStepErrors({ step1: firstError.message });
return false;
}
setStepErrors({});
return true;
};
const validateStep2 = () => {
const result = userInfoSchema.safeParse({
name: formData.name,
position: formData.position,
email: formData.email,
phone: formData.phone,
userId: formData.userId,
password: formData.password,
passwordConfirm: formData.passwordConfirm,
});
if (!result.success) {
const firstError = result.error.issues[0];
setStepErrors({ step2: firstError.message });
return false;
}
setStepErrors({});
return true;
};
const validateStep3 = () => {
const result = planSelectionSchema.safeParse({
plan: formData.plan,
salesCode: formData.salesCode,
agreeTerms: formData.agreeTerms,
agreePrivacy: formData.agreePrivacy,
});
if (!result.success) {
const firstError = result.error.issues[0];
setStepErrors({ step3: firstError.message });
return false;
}
setStepErrors({});
return true;
};
const isStep1Valid = formData.companyName && formData.businessNumber && formData.industry && formData.companySize;
const isStep2Valid = formData.name && formData.email && formData.phone && formData.userId && formData.password && formData.password === formData.passwordConfirm;
const isStep3Valid = formData.agreeTerms && formData.agreePrivacy;
return (
<div className="min-h-screen bg-background flex flex-col">
{/* Header */}
<header className="clean-glass border-b border-border">
<div className="container mx-auto px-6 py-4">
<div className="flex items-center justify-between">
<button
onClick={() => router.push("/")}
className="flex items-center space-x-3 hover:opacity-80 transition-opacity"
>
<div className="w-10 h-10 rounded-xl flex items-center justify-center clean-shadow relative overflow-hidden" style={{ backgroundColor: '#3B82F6' }}>
<div className="text-white font-bold text-lg">S</div>
<div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent opacity-30"></div>
</div>
<div>
<h1 className="text-xl font-bold tracking-wide">SAM</h1>
<p className="text-xs text-muted-foreground"></p>
</div>
</button>
<div className="flex items-center gap-3">
<ThemeSelect />
<LanguageSelect />
<Button variant="ghost" onClick={() => router.push("/login")} className="rounded-xl">
</Button>
</div>
</div>
</div>
</header>
{/* Main Content */}
<div className="flex-1 container mx-auto px-6 py-12">
<div className="max-w-2xl mx-auto">
{/* Progress Steps */}
<div className="mb-12">
<div className="flex items-center justify-between mb-4">
{[1, 2, 3].map((stepNumber) => (
<div key={stepNumber} className={`flex items-center ${stepNumber === 2 ? 'justify-center' : 'flex-1'}`}>
{(stepNumber === 2 || stepNumber === 3) && (
<div className={`flex-1 h-1 mx-4 rounded transition-colors ${
step > stepNumber - 1 ? "bg-primary" : "bg-muted"
}`} />
)}
<div className={`w-10 h-10 rounded-full flex items-center justify-center font-semibold transition-colors ${
step >= stepNumber
? "bg-primary text-white"
: "bg-muted text-muted-foreground"
}`}>
{stepNumber}
</div>
{(stepNumber === 1 || stepNumber === 2) && (
<div className={`flex-1 h-1 mx-4 rounded transition-colors ${
step > stepNumber ? "bg-primary" : "bg-muted"
}`} />
)}
</div>
))}
</div>
<div className="flex justify-between text-sm">
<span className={step >= 1 ? "text-foreground font-medium" : "text-muted-foreground"}>
</span>
<span className={step >= 2 ? "text-foreground font-medium" : "text-muted-foreground"}>
</span>
<span className={step >= 3 ? "text-foreground font-medium" : "text-muted-foreground"}>
</span>
</div>
</div>
{/* Step 1: 회사 정보 */}
{step === 1 && (
<div className="clean-glass rounded-2xl p-8 clean-shadow space-y-6">
<div>
<h2 className="mb-2 text-foreground"> </h2>
<p className="text-muted-foreground">MES </p>
</div>
<div className="space-y-4">
<div>
<Label htmlFor="companyName" className="flex items-center space-x-2 mb-2">
<Building2 className="w-4 h-4" />
<span> *</span>
</Label>
<Input
id="companyName"
placeholder="예: 삼성전자"
value={formData.companyName}
onChange={(e) => handleInputChange("companyName", e.target.value)}
className="clean-input"
/>
</div>
<div>
<Label htmlFor="businessNumber" className="flex items-center space-x-2 mb-2">
<FileText className="w-4 h-4" />
<span> *</span>
</Label>
<Input
id="businessNumber"
placeholder="000-00-00000"
value={formData.businessNumber}
onChange={(e) => handleInputChange("businessNumber", e.target.value)}
className="clean-input"
/>
</div>
<div>
<Label htmlFor="industry" className="flex items-center space-x-2 mb-2">
<Briefcase className="w-4 h-4" />
<span> *</span>
</Label>
<Select value={formData.industry} onValueChange={(value) => handleInputChange("industry", value)}>
<SelectTrigger className="clean-input">
<SelectValue placeholder="업종을 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="electronics">/</SelectItem>
<SelectItem value="machinery">/</SelectItem>
<SelectItem value="automotive">/</SelectItem>
<SelectItem value="chemical">/</SelectItem>
<SelectItem value="food">/</SelectItem>
<SelectItem value="textile">/</SelectItem>
<SelectItem value="metal">/</SelectItem>
<SelectItem value="other"> </SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="companySize" className="flex items-center space-x-2 mb-2">
<Users className="w-4 h-4" />
<span> *</span>
</Label>
<Select value={formData.companySize} onValueChange={(value) => handleInputChange("companySize", value)}>
<SelectTrigger className="clean-input">
<SelectValue placeholder="기업 규모를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectItem value="small"> ( 10-50)</SelectItem>
<SelectItem value="medium"> ( 50-300)</SelectItem>
<SelectItem value="large"> ( 300 )</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{stepErrors.step1 && (
<div className="bg-destructive/10 border border-destructive/30 rounded-xl p-4">
<p className="text-sm text-destructive text-center">{stepErrors.step1}</p>
</div>
)}
<Button
onClick={() => {
if (validateStep1()) {
setStep(2);
}
}}
disabled={!isStep1Valid}
className="w-full rounded-xl bg-primary hover:bg-primary/90"
>
</Button>
</div>
)}
{/* Step 2: 담당자 정보 */}
{step === 2 && (
<div className="clean-glass rounded-2xl p-8 clean-shadow space-y-6">
<div>
<h2 className="mb-2 text-foreground"> </h2>
<p className="text-muted-foreground"> </p>
</div>
<div className="space-y-4">
<div>
<Label htmlFor="name" className="flex items-center space-x-2 mb-2">
<User className="w-4 h-4"/>
<span> *</span>
</Label>
<Input
id="name"
placeholder="홍길동"
value={formData.name}
onChange={(e) => handleInputChange("name", e.target.value)}
className="clean-input"
/>
</div>
<div>
<Label htmlFor="position" className="flex items-center space-x-2 mb-2">
<Briefcase className="w-4 h-4"/>
<span></span>
</Label>
<Input
id="position"
placeholder="예: 생산관리팀장"
value={formData.position}
onChange={(e) => handleInputChange("position", e.target.value)}
className="clean-input"
/>
</div>
<div>
<Label htmlFor="email" className="flex items-center space-x-2 mb-2">
<Mail className="w-4 h-4"/>
<span> *</span>
</Label>
<Input
id="email"
type="email"
placeholder="example@company.com"
value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)}
className="clean-input"
/>
</div>
<div>
<Label htmlFor="phone" className="flex items-center space-x-2 mb-2">
<Phone className="w-4 h-4"/>
<span> *</span>
</Label>
<Input
id="phone"
placeholder="010-0000-0000"
value={formData.phone}
onChange={(e) => handleInputChange("phone", e.target.value)}
className="clean-input"
/>
</div>
<div>
<Label htmlFor="userId" className="flex items-center space-x-2 mb-2">
<User className="w-4 h-4"/>
<span> *</span>
</Label>
<Input
id="userId"
placeholder="영문, 숫자 조합 6자 이상"
value={formData.userId}
onChange={(e) => handleInputChange("userId", e.target.value)}
className="clean-input"
/>
</div>
<div>
<Label htmlFor="password" className="flex items-center space-x-2 mb-2">
<Lock className="w-4 h-4"/>
<span> *</span>
</Label>
<Input
id="password"
type="password"
placeholder="8자 이상 입력"
value={formData.password}
onChange={(e) => handleInputChange("password", e.target.value)}
className="clean-input"
/>
</div>
<div>
<Label htmlFor="passwordConfirm" className="flex items-center space-x-2 mb-2">
<Lock className="w-4 h-4"/>
<span> *</span>
</Label>
<Input
id="passwordConfirm"
type="password"
placeholder="비밀번호 재입력"
value={formData.passwordConfirm}
onChange={(e) => handleInputChange("passwordConfirm", e.target.value)}
className="clean-input"
/>
{formData.passwordConfirm && formData.password !== formData.passwordConfirm && (
<p className="text-sm text-destructive mt-1"> </p>
)}
</div>
</div>
{stepErrors.step2 && (
<div className="bg-destructive/10 border border-destructive/30 rounded-xl p-4">
<p className="text-sm text-destructive text-center">{stepErrors.step2}</p>
</div>
)}
<div className="flex space-x-4">
<Button
variant="outline"
onClick={() => setStep(1)}
className="flex-1 rounded-xl"
>
<ArrowLeft className="w-4 h-4 mr-2"/>
</Button>
<Button
onClick={() => {
if (validateStep2()) {
setStep(3);
}
}}
disabled={!isStep2Valid}
className="flex-1 rounded-xl bg-primary hover:bg-primary/90"
>
</Button>
</div>
</div>
)}
{/* Step 3: 플랜 선택 */}
{step === 3 && (
<div className="space-y-6">
<div className="clean-glass rounded-2xl p-8 clean-shadow space-y-6">
{/*
<div>
<h2 className="mb-2 text-foreground"> </h2>
<p className="text-muted-foreground"> 30 </p>
</div>
<div className="space-y-3">
{[
{ id: "demo", name: "데모 체험판", desc: "30일 무료 체험 (모든 기능 이용)", badge: "추천" },
{ id: "standard", name: "스탠다드", desc: "중소기업 최적화 플랜" },
{ id: "premium", name: "프리미엄", desc: "중견기업 맞춤형 플랜" },
].map((plan) => (
<button
key={plan.id}
onClick={() => handleInputChange("plan", plan.id)}
className={`w-full p-4 rounded-xl border-2 transition-all text-left ${
formData.plan === plan.id
? "border-primary bg-primary/5"
: "border-border hover:border-primary/50"
}`}
>
<div className="flex items-center justify-between">
<div>
<div className="flex items-center space-x-2">
<span className="font-semibold">{plan.name}</span>
{plan.badge && (
<Badge className="bg-primary text-white text-xs">
{plan.badge}
</Badge>
)}
</div>
<p className="text-sm text-muted-foreground mt-1">{plan.desc}</p>
</div>
{formData.plan === plan.id && (
<CheckCircle2 className="w-6 h-6 text-primary" />
)}
</div>
</button>
))}
</div>
<div>
<Label htmlFor="salesCode" className="flex items-center space-x-2 mb-2">
<Tag className="w-4 h-4" />
<span> ()</span>
</Label>
<div className="relative">
<Input
id="salesCode"
placeholder="추천코드를 입력하면 할인 혜택을 받을 수 있습니다"
value={formData.salesCode}
onChange={(e) => handleSalesCodeChange(e.target.value)}
className={`clean-input pr-10 ${
salesCodeValid === true ? "border-green-500" :
salesCodeValid === false ? "border-destructive" : ""
}`}
/>
{salesCodeValid === true && (
<CheckCircle2 className="w-5 h-5 text-green-500 absolute right-3 top-1/2 -translate-y-1/2" />
)}
</div>
{salesCodeValid === true && (
<p className="text-sm text-green-600 mt-2 flex items-center space-x-2">
<CheckCircle2 className="w-4 h-4" />
<span> ! {discount}% </span>
</p>
)}
{salesCodeValid === false && (
<p className="text-sm text-destructive mt-2"> </p>
)}
<p className="text-xs text-muted-foreground mt-2">
💡 코드: SALES2024 (20%), PARTNER30 (30%), VIP50 (50%)
</p>
</div>
*/}
<div className="space-y-3 pt-4 border-t border-border">
<label className="flex items-start space-x-3 cursor-pointer">
<input
type="checkbox"
checked={formData.agreeTerms}
onChange={(e) => handleInputChange("agreeTerms", e.target.checked)}
className="mt-1 w-4 h-4 rounded border-border"
/>
<span className="text-sm">
<span className="font-medium text-foreground">[]</span>
</span>
</label>
<label className="flex items-start space-x-3 cursor-pointer">
<input
type="checkbox"
checked={formData.agreePrivacy}
onChange={(e) => handleInputChange("agreePrivacy", e.target.checked)}
className="mt-1 w-4 h-4 rounded border-border"
/>
<span className="text-sm">
<span className="font-medium text-foreground">[]</span>
</span>
</label>
</div>
</div>
<div className="flex space-x-4">
<Button
variant="outline"
onClick={() => setStep(2)}
className="flex-1 rounded-xl"
>
<ArrowLeft className="w-4 h-4 mr-2" />
</Button>
<Button
onClick={() => {
if (validateStep3()) {
handleSubmit();
}
}}
disabled={!isStep3Valid}
className="flex-1 rounded-xl bg-primary hover:bg-primary/90"
>
</Button>
</div>
</div>
)}
{/* Login Link */}
<div className="text-center mt-6">
<p className="text-sm text-muted-foreground">
?{" "}
<button
onClick={() => router.push("/login")}
className="text-primary font-medium hover:underline"
>
</button>
</p>
</div>
</div>
</div>
</div>
);
}