From de2ac5a2860bc25180971e7a11f852d9d44675b7 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 6 Aug 2025 04:23:40 +0000 Subject: (대표님) 정기평가, 법적검토, 정책, 가입관련 처리 및 관련 컴포넌트 추가, 메뉴 변경 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/signup/join-form.tsx | 2021 +++++++++++++++++++++------------------ 1 file changed, 1070 insertions(+), 951 deletions(-) (limited to 'components/signup/join-form.tsx') diff --git a/components/signup/join-form.tsx b/components/signup/join-form.tsx index 60f600b9..e9773d28 100644 --- a/components/signup/join-form.tsx +++ b/components/signup/join-form.tsx @@ -1,32 +1,19 @@ -"use client" - -import * as React from "react" -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm, useFieldArray } from "react-hook-form" -import { useRouter, useSearchParams, useParams } from "next/navigation" - -import i18nIsoCountries from "i18n-iso-countries" -import enLocale from "i18n-iso-countries/langs/en.json" -import koLocale from "i18n-iso-countries/langs/ko.json" - -import { Button } from "@/components/ui/button" -import { Separator } from "@/components/ui/separator" -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" -import { toast } from "@/hooks/use-toast" +'use client' + +import React, { useState, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Progress } from '@/components/ui/progress'; +import { Check, ChevronRight, User, Building, FileText, Plus, X, ChevronsUpDown, Loader2 } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { useRouter, useParams, useSearchParams } from 'next/navigation'; +import { useTranslation } from '@/i18n/client'; +import { toast } from '@/hooks/use-toast'; import { Popover, PopoverTrigger, PopoverContent, -} from "@/components/ui/popover" +} from '@/components/ui/popover'; import { Command, CommandList, @@ -34,21 +21,14 @@ import { CommandEmpty, CommandGroup, CommandItem, -} from "@/components/ui/command" -import { Check, ChevronsUpDown, Loader2, Plus, X } from "lucide-react" -import { cn } from "@/lib/utils" -import { useTranslation } from "@/i18n/client" - -import { getVendorTypes } from "@/lib/vendors/service" -import { createVendorSchema, CreateVendorSchema } from "@/lib/vendors/validations" +} from '@/components/ui/command'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@/components/ui/select" - +} from '@/components/ui/select'; import { Dropzone, DropzoneZone, @@ -56,7 +36,7 @@ import { DropzoneUploadIcon, DropzoneTitle, DropzoneDescription, -} from "@/components/ui/dropzone" +} from '@/components/ui/dropzone'; import { FileList, FileListItem, @@ -66,33 +46,33 @@ import { FileListName, FileListDescription, FileListAction, -} from "@/components/ui/file-list" -import { Badge } from "@/components/ui/badge" -import { ScrollArea } from "@/components/ui/scroll-area" -import prettyBytes from "pretty-bytes" -import { Checkbox } from "../ui/checkbox" - -i18nIsoCountries.registerLocale(enLocale) -i18nIsoCountries.registerLocale(koLocale) - -const locale = "ko" -const countryMap = i18nIsoCountries.getNames(locale, { select: "official" }) +} from '@/components/ui/file-list'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import prettyBytes from 'pretty-bytes'; + +// 기존 JoinForm에서 가져온 데이터들 +import i18nIsoCountries from "i18n-iso-countries"; +import enLocale from "i18n-iso-countries/langs/en.json"; +import koLocale from "i18n-iso-countries/langs/ko.json"; +import { getVendorTypes } from '@/lib/vendors/service'; +import ConsentStep from './conset-step'; + +i18nIsoCountries.registerLocale(enLocale); +i18nIsoCountries.registerLocale(koLocale); + +const locale = "ko"; +const countryMap = i18nIsoCountries.getNames(locale, { select: "official" }); const countryArray = Object.entries(countryMap).map(([code, label]) => ({ code, label, -})) +})); -// Sort countries to put Korea first, then alphabetically const sortedCountryArray = [...countryArray].sort((a, b) => { - // Put Korea (KR) at the top if (a.code === "KR") return -1; if (b.code === "KR") return 1; - - // Otherwise sort alphabetically return a.label.localeCompare(b.label); }); -// Add English names for Korean locale const enhancedCountryArray = sortedCountryArray.map(country => ({ ...country, label: locale === "ko" && country.code === "KR" @@ -100,7 +80,6 @@ const enhancedCountryArray = sortedCountryArray.map(country => ({ : country.label })); -// Contact task options const contactTaskOptions = [ { value: "PRESIDENT_DIRECTOR", label: "회사대표 President/Director" }, { value: "SALES_MANAGEMENT", label: "영업관리 Sales Management" }, @@ -114,8 +93,7 @@ const contactTaskOptions = [ { value: "FIELD_SERVICE_ENGINEER", label: "FSE(야드작업자) Field Service Engineer" } ]; -// Comprehensive list of country dial codes -export const countryDialCodes: { [key: string]: string } = { +export const countryDialCodes = { AF: "+93", AL: "+355", DZ: "+213", AS: "+1-684", AD: "+376", AO: "+244", AI: "+1-264", AG: "+1-268", AR: "+54", AM: "+374", AW: "+297", AU: "+61", AT: "+43", AZ: "+994", BS: "+1-242", BH: "+973", BD: "+880", BB: "+1-246", @@ -153,312 +131,1102 @@ export const countryDialCodes: { [key: string]: string } = { ZW: "+263" }; -const MAX_FILE_SIZE = 3e9 - -export function JoinForm() { - const params = useParams() || {}; - const lng = params.lng ? String(params.lng) : "ko"; - const { t } = useTranslation(lng, "translation") - - const router = useRouter() - const searchParams = useSearchParams() || new URLSearchParams(); - const defaultTaxId = searchParams.get("taxID") ?? "" - - // Define VendorType interface - interface VendorType { - id: number; - code: string; - nameKo: string; - nameEn: string; - } - - // Vendor Types state with proper typing - const [vendorTypes, setVendorTypes] = React.useState([]) - const [isLoadingVendorTypes, setIsLoadingVendorTypes] = React.useState(true) - - // Individual file states - const [businessRegistrationFiles, setBusinessRegistrationFiles] = React.useState([]) - const [isoCertificationFiles, setIsoCertificationFiles] = React.useState([]) - const [creditReportFiles, setCreditReportFiles] = React.useState([]) - const [bankAccountFiles, setBankAccountFiles] = React.useState([]) - - const [isSubmitting, setIsSubmitting] = React.useState(false) - - // Fetch vendor types on component mount - React.useEffect(() => { - async function loadVendorTypes() { - setIsLoadingVendorTypes(true) - try { - const result = await getVendorTypes() - if (result.data) { - setVendorTypes(result.data) - } - } catch (error) { - console.error("Failed to load vendor types:", error) - toast({ - variant: "destructive", - title: "Error", - description: "Failed to load vendor types", - }) - } finally { - setIsLoadingVendorTypes(false) +const MAX_FILE_SIZE = 3e9; + +// 스텝 정의 +const STEPS = [ + { id: 1, title: '약관 동의', description: '서비스 이용 약관 동의', icon: FileText }, + { id: 2, title: '계정 생성', description: '개인 계정 정보 입력', icon: User }, + { id: 3, title: '업체 등록', description: '업체 정보 및 서류 제출', icon: Building } +]; + +export default function JoinForm() { + const params = useParams() || {}; + const lng = params.lng ? String(params.lng) : "ko"; + const { t } = useTranslation(lng, "translation"); + const router = useRouter(); + const searchParams = useSearchParams() || new URLSearchParams(); + const defaultTaxId = searchParams.get("taxID") ?? ""; + + const [currentStep, setCurrentStep] = useState(1); + const [completedSteps, setCompletedSteps] = useState(new Set()); + + // 각 스텝별 데이터 + const [consentData, setConsentData] = useState({ + privacy: false, + terms: false, + marketing: false + }); + + const [accountData, setAccountData] = useState({ + name: '', + email: '', + password: '', + confirmPassword: '', + phone: '' + }); + + const [vendorData, setVendorData] = useState({ + vendorName: "", + vendorTypeId: undefined, + items: "", + taxId: defaultTaxId, + address: "", + email: "", + phone: "", + country: "", + website: "", + representativeName: "", + representativeBirth: "", + representativeEmail: "", + representativePhone: "", + corporateRegistrationNumber: "", + representativeWorkExpirence: false, + contacts: [ + { + contactName: "", + contactPosition: "", + contactDepartment: "", + contactTask: "", + contactEmail: "", + contactPhone: "", + }, + ], + }); + + // 업체 타입 및 파일 상태 + const [vendorTypes, setVendorTypes] = useState([]); + const [isLoadingVendorTypes, setIsLoadingVendorTypes] = useState(true); + const [businessRegistrationFiles, setBusinessRegistrationFiles] = useState([]); + const [isoCertificationFiles, setIsoCertificationFiles] = useState([]); + const [creditReportFiles, setCreditReportFiles] = useState([]); + const [bankAccountFiles, setBankAccountFiles] = useState([]); + + const [policyVersions, setPolicyVersions] = useState({ + privacy_policy: '1.0', + terms_of_service: '1.0' + }); + + const progress = ((currentStep - 1) / (STEPS.length - 1)) * 100; + + // 정책 버전 및 업체 타입 로드 + useEffect(() => { + fetchPolicyVersions(); + loadVendorTypes(); + }, []); + + const fetchPolicyVersions = async () => { + try { + const response = await fetch('/api/consent/policy-versions'); + const versions = await response.json(); + setPolicyVersions(versions); + } catch (error) { + console.error('Failed to fetch policy versions:', error); + } + }; + + const loadVendorTypes = async () => { + setIsLoadingVendorTypes(true); + try { + const result = await getVendorTypes(); + if (result.data) { + setVendorTypes(result.data); } + } catch (error) { + console.error("Failed to load vendor types:", error); + toast({ + variant: "destructive", + title: "Error", + description: "Failed to load vendor types", + }); + } finally { + setIsLoadingVendorTypes(false); + } + }; + + const handleStepComplete = (step) => { + setCompletedSteps(prev => new Set([...prev, step])); + if (step < STEPS.length) { + setCurrentStep(step + 1); + } + }; + + const handleStepClick = (stepId) => { + if (stepId <= Math.max(...completedSteps) + 1) { + setCurrentStep(stepId); } + }; + + // 전화번호 플레이스홀더 함수들 + const getPhonePlaceholder = (countryCode) => { + if (!countryCode || !countryDialCodes[countryCode]) return "+82 010-1234-5678"; - loadVendorTypes() - }, []) - - // React Hook Form - const form = useForm({ - resolver: zodResolver(createVendorSchema), - defaultValues: { - vendorName: "", - vendorTypeId: undefined, - items: "", - taxId: defaultTaxId, - address: "", - email: "", - phone: "", - country: "", - representativeName: "", - representativeBirth: "", - representativeEmail: "", - representativePhone: "", - corporateRegistrationNumber: "", - representativeWorkExpirence: false, - // contacts (updated with new fields) - contacts: [ - { - contactName: "", - contactPosition: "", - contactDepartment: "", - contactTask: "", - contactEmail: "", - contactPhone: "", - }, - ], + const dialCode = countryDialCodes[countryCode]; + + switch (countryCode) { + case 'KR': return `${dialCode} 010-1234-5678`; + case 'US': + case 'CA': return `${dialCode} 555-123-4567`; + case 'JP': return `${dialCode} 90-1234-5678`; + case 'CN': return `${dialCode} 138-0013-8000`; + case 'GB': return `${dialCode} 20-7946-0958`; + case 'DE': return `${dialCode} 30-12345678`; + case 'FR': return `${dialCode} 1-42-86-83-16`; + default: return `${dialCode} 전화번호`; + } + }; + + const getPhoneDescription = (countryCode) => { + if (!countryCode) return "국가를 먼저 선택해주세요."; + + const dialCode = countryDialCodes[countryCode]; + + switch (countryCode) { + case 'KR': return `${dialCode}로 시작하는 국제번호 또는 010으로 시작하는 국내번호를 입력하세요.`; + case 'US': + case 'CA': return `${dialCode}로 시작하는 10자리 번호를 입력하세요.`; + case 'JP': return `${dialCode}로 시작하는 일본 전화번호를 입력하세요.`; + case 'CN': return `${dialCode}로 시작하는 중국 전화번호를 입력하세요.`; + default: return `${dialCode}로 시작하는 국제 전화번호를 입력하세요.`; + } + }; + + return ( +
+ {/* 진행률 표시 */} +
+
+

파트너 등록

+ + {currentStep} / {STEPS.length} + +
+ + + {/* 스텝 네비게이션 */} +
+ {STEPS.map((step, index) => { + const Icon = step.icon; + const isCompleted = completedSteps.has(step.id); + const isCurrent = currentStep === step.id; + const isAccessible = step.id <= Math.max(...completedSteps) + 1; + + return ( + +
handleStepClick(step.id)} + > +
+ {isCompleted ? ( + + ) : ( + + )} +
+
+
+ {step.title} +
+
+ {step.description} +
+
+
+ + {index < STEPS.length - 1 && ( + + )} +
+ ); + })} +
+
+ + {/* 스텝 콘텐츠 */} +
+ {currentStep === 1 && ( + handleStepComplete(1)} + policyVersions={policyVersions} + /> + )} + + {currentStep === 2 && ( + handleStepComplete(2)} + onBack={() => setCurrentStep(1)} + /> + )} + + {currentStep === 3 && ( + setCurrentStep(2)} + onComplete={() => { + handleStepComplete(3); + // 완료 후 대시보드로 이동 + router.push(`/${lng}/partners/dashboard`); + }} + accountData={accountData} + consentData={consentData} + vendorTypes={vendorTypes} + isLoadingVendorTypes={isLoadingVendorTypes} + businessRegistrationFiles={businessRegistrationFiles} + setBusinessRegistrationFiles={setBusinessRegistrationFiles} + isoCertificationFiles={isoCertificationFiles} + setIsoCertificationFiles={setIsoCertificationFiles} + creditReportFiles={creditReportFiles} + setCreditReportFiles={setCreditReportFiles} + bankAccountFiles={bankAccountFiles} + setBankAccountFiles={setBankAccountFiles} + getPhonePlaceholder={getPhonePlaceholder} + getPhoneDescription={getPhoneDescription} + enhancedCountryArray={enhancedCountryArray} + contactTaskOptions={contactTaskOptions} + lng={lng} + policyVersions={policyVersions} + /> + )} +
+
+ ); +} + + +// Step 2: 계정 생성 +function AccountStep({ data, onChange, onNext, onBack }) { + const [isLoading, setIsLoading] = useState(false); + + const isValid = data.name && data.email && data.password && + data.confirmPassword && data.phone && + data.password === data.confirmPassword; + + const handleInputChange = (field, value) => { + onChange(prev => ({ ...prev, [field]: value })); + }; + + const handleNext = async () => { + if (!isValid) return; + + setIsLoading(true); + try { + // 이메일 중복 확인 + const response = await fetch('/api/auth/check-email', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email: data.email }) + }); + + const result = await response.json(); + + if (!response.ok) { + if (result.error === 'EMAIL_EXISTS') { + alert('이미 사용 중인 이메일입니다.'); + return; + } + throw new Error(result.error); + } + + onNext(); + } catch (error) { + console.error('Email check error:', error); + alert('이메일 확인 중 오류가 발생했습니다.'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+

계정 정보 입력

+

+ 서비스 이용을 위한 개인 계정을 생성합니다. +

+
+ +
+
+ + handleInputChange('name', e.target.value)} + /> +
+ +
+ + handleInputChange('email', e.target.value)} + /> +
+ +
+ + handleInputChange('password', e.target.value)} + /> +
+ +
+ + handleInputChange('confirmPassword', e.target.value)} + /> + {data.confirmPassword && data.password !== data.confirmPassword && ( +

비밀번호가 일치하지 않습니다.

+ )} +
+ +
+ + handleInputChange('phone', e.target.value)} + /> +

+ SMS 인증에 사용됩니다. 국제번호 형식으로 입력해주세요. +

+
+
+ +
+ + +
+
+ ); +} + +// Step 3: 업체 등록 (기존 JoinForm 내용) +function VendorStep(props) { + return ; +} + + +// 완전한 업체 등록 폼 컴포넌트 (기존 JoinForm 내용) +function CompleteVendorForm({ + data, onChange, onBack, onComplete, accountData, consentData, + vendorTypes, isLoadingVendorTypes, businessRegistrationFiles, setBusinessRegistrationFiles, + isoCertificationFiles, setIsoCertificationFiles, creditReportFiles, setCreditReportFiles, + bankAccountFiles, setBankAccountFiles, getPhonePlaceholder, getPhoneDescription, + enhancedCountryArray, contactTaskOptions, lng, policyVersions +}) { + const [isSubmitting, setIsSubmitting] = useState(false); + + // 담당자 관리 함수들 + const addContact = () => { + onChange(prev => ({ + ...prev, + contacts: [...prev.contacts, { + contactName: "", + contactPosition: "", + contactDepartment: "", + contactTask: "", + contactEmail: "", + contactPhone: "", + }] + })); + }; + + const removeContact = (index) => { + onChange(prev => ({ + ...prev, + contacts: prev.contacts.filter((_, i) => i !== index) + })); + }; + + const updateContact = (index, field, value) => { + onChange(prev => ({ + ...prev, + contacts: prev.contacts.map((contact, i) => + i === index ? { ...contact, [field]: value } : contact + ) + })); + }; + + // 폼 입력 변경 핸들러 + const handleInputChange = (field, value) => { + onChange(prev => ({ ...prev, [field]: value })); + }; + + // 파일 업로드 핸들러들 + const createFileUploadHandler = (setFiles, currentFiles) => ({ + onDropAccepted: (acceptedFiles) => { + const newFiles = [...currentFiles, ...acceptedFiles]; + setFiles(newFiles); }, - mode: "onChange", - }) + onDropRejected: (fileRejections) => { + fileRejections.forEach((rej) => { + toast({ + variant: "destructive", + title: "File Error", + description: `${rej.file.name}: ${rej.errors[0]?.message || "Upload failed"}`, + }); + }); + }, + removeFile: (index) => { + const updated = [...currentFiles]; + updated.splice(index, 1); + setFiles(updated); + } + }); - // Custom validation for file uploads + const businessRegistrationHandler = createFileUploadHandler(setBusinessRegistrationFiles, businessRegistrationFiles); + const isoCertificationHandler = createFileUploadHandler(setIsoCertificationFiles, isoCertificationFiles); + const creditReportHandler = createFileUploadHandler(setCreditReportFiles, creditReportFiles); + const bankAccountHandler = createFileUploadHandler(setBankAccountFiles, bankAccountFiles); + + // 유효성 검사 const validateRequiredFiles = () => { - const errors = [] + const errors = []; if (businessRegistrationFiles.length === 0) { - errors.push("사업자등록증을 업로드해주세요.") + errors.push("사업자등록증을 업로드해주세요."); } if (isoCertificationFiles.length === 0) { - errors.push("ISO 인증서를 업로드해주세요.") + errors.push("ISO 인증서를 업로드해주세요."); } if (creditReportFiles.length === 0) { - errors.push("신용평가보고서를 업로드해주세요.") + errors.push("신용평가보고서를 업로드해주세요."); } - if (form.watch("country") !== "KR" && bankAccountFiles.length === 0) { - errors.push("대금지급 통장사본을 업로드해주세요.") + if (data.country !== "KR" && bankAccountFiles.length === 0) { + errors.push("대금지급 통장사본을 업로드해주세요."); } - return errors - } - - const isFormValid = form.formState.isValid && validateRequiredFiles().length === 0 - - // Field array for contacts - const { fields: contactFields, append: addContact, remove: removeContact } = - useFieldArray({ - control: form.control, - name: "contacts", - }) - - // File upload handlers - const createFileUploadHandler = ( - setFiles: React.Dispatch>, - currentFiles: File[] - ) => ({ - onDropAccepted: (acceptedFiles: File[]) => { - const newFiles = [...currentFiles, ...acceptedFiles] - setFiles(newFiles) - }, - onDropRejected: (fileRejections: any[]) => { - fileRejections.forEach((rej) => { - toast({ - variant: "destructive", - title: "File Error", - description: `${rej.file.name}: ${rej.errors[0]?.message || "Upload failed"}`, - }) - }) - }, - removeFile: (index: number) => { - const updated = [...currentFiles] - updated.splice(index, 1) - setFiles(updated) - } - }) + return errors; + }; - const businessRegistrationHandler = createFileUploadHandler(setBusinessRegistrationFiles, businessRegistrationFiles) - const isoCertificationHandler = createFileUploadHandler(setIsoCertificationFiles, isoCertificationFiles) - const creditReportHandler = createFileUploadHandler(setCreditReportFiles, creditReportFiles) - const bankAccountHandler = createFileUploadHandler(setBankAccountFiles, bankAccountFiles) + const isFormValid = data.vendorName && data.vendorTypeId && data.items && + data.country && data.phone && data.email && + data.contacts.length > 0 && + data.contacts[0].contactName && + validateRequiredFiles().length === 0; - // Submit - async function onSubmit(values: CreateVendorSchema) { - const fileErrors = validateRequiredFiles() + // 최종 제출 + const handleSubmit = async () => { + const fileErrors = validateRequiredFiles(); if (fileErrors.length > 0) { toast({ variant: "destructive", title: "파일 업로드 필수", description: fileErrors.join("\n"), - }) - return + }); + return; } - setIsSubmitting(true) + setIsSubmitting(true); try { - const formData = new FormData() - - // Add vendor data - const vendorData = { - vendorName: values.vendorName, - vendorTypeId: values.vendorTypeId, - items: values.items, - vendorCode: values.vendorCode, - website: values.website, - taxId: values.taxId, - address: values.address, - email: values.email, - phone: values.phone, - country: values.country, - status: "PENDING_REVIEW" as const, - representativeName: values.representativeName || "", - representativeBirth: values.representativeBirth || "", - representativeEmail: values.representativeEmail || "", - representativePhone: values.representativePhone || "", - corporateRegistrationNumber: values.corporateRegistrationNumber || "", - representativeWorkExpirence: values.representativeWorkExpirence || false - } + const formData = new FormData(); + + // 통합 데이터 준비 + const completeData = { + account: accountData, + vendor: { + ...data, + email: data.email || accountData.email, // 업체 이메일이 없으면 계정 이메일 사용 + }, + consents: { + privacy_policy: { + agreed: consentData.privacy, + version: policyVersions.privacy_policy + }, + terms_of_service: { + agreed: consentData.terms, + version: policyVersions.terms_of_service + }, + marketing: { + agreed: consentData.marketing, + version: policyVersions.privacy_policy + } + } + }; - formData.append('vendorData', JSON.stringify(vendorData)) - formData.append('contacts', JSON.stringify(values.contacts)) + formData.append('completeData', JSON.stringify(completeData)); - // Add files with specific types + // 파일들 추가 businessRegistrationFiles.forEach(file => { - formData.append('businessRegistration', file) - }) + formData.append('businessRegistration', file); + }); isoCertificationFiles.forEach(file => { - formData.append('isoCertification', file) - }) + formData.append('isoCertification', file); + }); creditReportFiles.forEach(file => { - formData.append('creditReport', file) - }) + formData.append('creditReport', file); + }); - if (values.country !== "KR") { + if (data.country !== "KR") { bankAccountFiles.forEach(file => { - formData.append('bankAccount', file) - }) + formData.append('bankAccount', file); + }); } - const response = await fetch('/api/vendors', { + const response = await fetch('/api/auth/signup-with-vendor', { method: 'POST', body: formData, - }) + }); - const result = await response.json() + const result = await response.json(); if (response.ok) { toast({ title: "등록 완료", - description: "회사 등록이 완료되었습니다. (status=PENDING_REVIEW)", - }) - router.push("/ko/partners") + description: "회원가입 및 업체 등록이 완료되었습니다. 관리자 승인 후 서비스를 이용하실 수 있습니다.", + }); + onComplete(); } else { toast({ variant: "destructive", title: "오류", description: result.error || "등록에 실패했습니다.", - }) + }); } - } catch (error: any) { - console.error(error) + } catch (error) { + console.error(error); toast({ variant: "destructive", title: "서버 에러", description: error.message || "에러가 발생했습니다.", - }) + }); } finally { - setIsSubmitting(false) - } - } - - // Get country code for phone number placeholder - const getPhonePlaceholder = (countryCode: string) => { - if (!countryCode || !countryDialCodes[countryCode]) return "+82 010-1234-5678"; - - const dialCode = countryDialCodes[countryCode]; - - switch (countryCode) { - case 'KR': - return `${dialCode} 010-1234-5678`; - case 'US': - case 'CA': - return `${dialCode} 555-123-4567`; - case 'JP': - return `${dialCode} 90-1234-5678`; - case 'CN': - return `${dialCode} 138-0013-8000`; - case 'GB': - return `${dialCode} 20-7946-0958`; - case 'DE': - return `${dialCode} 30-12345678`; - case 'FR': - return `${dialCode} 1-42-86-83-16`; - default: - return `${dialCode} 전화번호`; + setIsSubmitting(false); } }; - const getPhoneDescription = (countryCode: string) => { - if (!countryCode) return "국가를 먼저 선택해주세요."; - - const dialCode = countryDialCodes[countryCode]; - - switch (countryCode) { - case 'KR': - return `${dialCode}로 시작하는 국제번호 또는 010으로 시작하는 국내번호를 입력하세요.`; - case 'US': - case 'CA': - return `${dialCode}로 시작하는 10자리 번호를 입력하세요.`; - case 'JP': - return `${dialCode}로 시작하는 일본 전화번호를 입력하세요.`; - case 'CN': - return `${dialCode}로 시작하는 중국 전화번호를 입력하세요.`; - default: - return `${dialCode}로 시작하는 국제 전화번호를 입력하세요.`; - } - }; + return ( +
+
+

업체 정보 등록

+

+ 업체 정보와 필요한 서류를 등록해주세요. 모든 정보는 관리자 검토 후 승인됩니다. +

+
- // File display component - const FileUploadSection = ({ - title, - description, - files, - onDropAccepted, - onDropRejected, - removeFile, - required = true - }: { - title: string; - description: string; - files: File[]; - onDropAccepted: (files: File[]) => void; - onDropRejected: (rejections: any[]) => void; - removeFile: (index: number) => void; - required?: boolean; - }) => ( + {/* 기본 정보 */} +
+

기본 정보

+
+ {/* 업체 유형 */} +
+ + + + + + + + + + No vendor type found. + + {vendorTypes.map((type) => ( + handleInputChange('vendorTypeId', type.id)} + > + + {lng === "ko" ? type.nameKo : type.nameEn} + + ))} + + + + + +
+ + {/* 업체명 */} +
+ + handleInputChange('vendorName', e.target.value)} + disabled={isSubmitting} + /> +

+ {data.country === "KR" + ? "사업자 등록증에 표기된 정확한 회사명을 입력하세요." + : "해외 업체의 경우 영문 회사명을 입력하세요."} +

+
+ + {/* 공급품목 */} +
+ + handleInputChange('items', e.target.value)} + disabled={isSubmitting} + /> +

+ 공급 가능한 제품/서비스를 입력하세요 +

+
+ + {/* 사업자등록번호 */} +
+ + handleInputChange('taxId', e.target.value)} + disabled={isSubmitting} + placeholder="123-45-67890" + /> +
+ + {/* 주소 */} +
+ + handleInputChange('address', e.target.value)} + disabled={isSubmitting} + /> +
+ + {/* 국가 */} +
+ + + + + + + + + + No country found. + + {enhancedCountryArray.map((country) => ( + handleInputChange('country', country.code)} + > + + {country.label} + + ))} + + + + + +
+ + {/* 대표 전화 */} +
+ + handleInputChange('phone', e.target.value)} + placeholder={getPhonePlaceholder(data.country)} + disabled={isSubmitting} + /> +

+ {getPhoneDescription(data.country)} +

+
+ + {/* 대표 이메일 */} +
+ + handleInputChange('email', e.target.value)} + disabled={isSubmitting} + placeholder={accountData.email} + /> +

+ 비워두면 계정 이메일({accountData.email})을 사용합니다. +

+
+ + {/* 웹사이트 */} +
+ + handleInputChange('website', e.target.value)} + disabled={isSubmitting} + /> +
+
+
+ + {/* 담당자 정보 */} +
+
+

담당자 정보 (최소 1명)

+ +
+ +
+ {data.contacts.map((contact, index) => ( +
+
+
+ + updateContact(index, 'contactName', e.target.value)} + disabled={isSubmitting} + /> +
+ +
+ + updateContact(index, 'contactPosition', e.target.value)} + disabled={isSubmitting} + /> +
+ +
+ + updateContact(index, 'contactDepartment', e.target.value)} + disabled={isSubmitting} + /> +
+ +
+ + +
+ +
+ + updateContact(index, 'contactEmail', e.target.value)} + disabled={isSubmitting} + /> +
+ +
+ + updateContact(index, 'contactPhone', e.target.value)} + placeholder={getPhonePlaceholder(data.country)} + disabled={isSubmitting} + /> +
+
+ + {data.contacts.length > 1 && ( +
+ +
+ )} +
+ ))} +
+
+ + {/* 한국 사업자 정보 */} + {data.country === "KR" && ( +
+

한국 사업자 정보

+
+
+ + handleInputChange('representativeName', e.target.value)} + disabled={isSubmitting} + /> +
+
+ + handleInputChange('representativeBirth', e.target.value)} + disabled={isSubmitting} + /> +
+
+ + handleInputChange('representativeEmail', e.target.value)} + disabled={isSubmitting} + /> +
+
+ + handleInputChange('representativePhone', e.target.value)} + disabled={isSubmitting} + /> +
+
+ + handleInputChange('corporateRegistrationNumber', e.target.value)} + disabled={isSubmitting} + /> +
+
+ handleInputChange('representativeWorkExpirence', e.target.checked)} + disabled={isSubmitting} + className="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" + /> + +
+
+
+ )} + + {/* 필수 첨부 서류 */} +
+

필수 첨부 서류

+ + + + + + + + {data.country !== "KR" && ( + + )} +
+ +
+ + +
+
+ ); +} + +// 파일 업로드 섹션 컴포넌트 +function FileUploadSection({ + title, + description, + files, + onDropAccepted, + onDropRejected, + removeFile, + isSubmitting, + required = true +}) { + return (
@@ -517,654 +1285,5 @@ export function JoinForm() {
)}
- ) - - // Render - return ( -
-
-
-
-

- {defaultTaxId}{" "} - {t("joinForm.title", { - defaultValue: "Vendor Administrator Creation", - })} -

-

- {t("joinForm.description", { - defaultValue: - "Please provide basic company information and attach any required documents (e.g., business registration). We will review and approve as soon as possible.", - })} -

-
- - - -
- - {/* ───────────────────────────────────────── - Basic Info - ───────────────────────────────────────── */} -
-

기본 정보

-
- {/* Vendor Type */} - { - const selectedType = vendorTypes.find(type => type.id === field.value); - const displayName = lng === "ko" ? - (selectedType?.nameKo || "") : - (selectedType?.nameEn || ""); - - return ( - - - 업체유형 - - - - - - - - - - - - No vendor type found. - - {vendorTypes.map((type) => ( - field.onChange(type.id)} - > - - {lng === "ko" ? type.nameKo : type.nameEn} - - ))} - - - - - - - - ); - }} - /> - - {/* vendorName */} - ( - - - 업체명 - - - - - - {form.watch("country") === "KR" - ? "사업자 등록증에 표기된 정확한 회사명을 입력하세요." - : "해외 업체의 경우 영문 회사명을 입력하세요."} - - - - )} - /> - - {/* Items */} - ( - - - 공급품목 - - - - - - 공급 가능한 제품/서비스를 입력하세요 - - - - )} - /> - - {/* Address */} - ( - - 주소 - - - - - - )} - /> - - {/* Country */} - { - const selectedCountry = enhancedCountryArray.find( - (c) => c.code === field.value - ) - return ( - - - 국가 - - - - - - - - - - - - No country found. - - {enhancedCountryArray.map((country) => ( - - field.onChange(country.code) - } - > - - {country.label} - - ))} - - - - - - - - ) - }} - /> - {/* Phone */} - ( - - - 대표 전화 - - - - - - {getPhoneDescription(form.watch("country"))} - - - - )} - /> - - {/* Email */} - ( - - - 대표 이메일 - - - - - - 회사 도메인 이메일을 사용하세요. (naver.com, gmail.com, daum.net 등의 개인 이메일은 지양해주세요) - - - - )} - /> - - {/* Website */} - ( - - 웹사이트 - - - - - - )} - /> -
-
- - {/* ───────────────────────────────────────── - 담당자 정보 (contacts) - ───────────────────────────────────────── */} -
-
-

담당자 정보 (최소 1명)

- -
- -
- {contactFields.map((contact, index) => ( -
-
- {/* contactName */} - ( - - - 담당자명 - - - - - - - )} - /> - - {/* contactPosition */} - ( - - - 직급 - - - - - - - )} - /> - - {/* contactDepartment */} - ( - - - 부서 - - - - - - - )} - /> - - {/* contactTask - Dropdown */} - ( - - - 담당업무 - - - - - )} - /> - - {/* contactEmail */} - ( - - - 이메일 - - - - - - - )} - /> - - {/* contactPhone */} - ( - - - 전화번호 - - - - - - - )} - /> -
- - {/* Remove contact button row */} - {contactFields.length > 1 && ( -
- -
- )} -
- ))} -
-
- - {/* ───────────────────────────────────────── - 한국 사업자 (country === "KR") - ───────────────────────────────────────── */} - {form.watch("country") === "KR" && ( -
-

한국 사업자 정보

- -
- ( - - - 대표자 이름 - - - - - - - )} - /> - ( - - - 대표자 생년월일 - - - - - - - )} - /> - ( - - - 대표자 이메일 - - - - - - - )} - /> - ( - - - 대표자 전화번호 - - - - - - - )} - /> - ( - - - 법인등록번호 - - - - - - - )} - /> - - ( - - - - -
- - 대표자 삼성중공업 근무이력 - - - 대표자가 삼성중공업에서 근무한 경험이 있는 경우 체크해주세요. - -
-
- )} - /> - -
-
- )} - - {/* ───────────────────────────────────────── - Required Document Uploads - ───────────────────────────────────────── */} -
-

필수 첨부 서류

- - {/* Business Registration */} - - - - - {/* ISO Certification */} - - - - - {/* Credit Report */} - - - {/* Bank Account Copy - Only for non-Korean companies */} - {form.watch("country") !== "KR" && ( - <> - - - - )} -
- - {/* ───────────────────────────────────────── - Submit - ───────────────────────────────────────── */} -
- -
-
- -
-
-
- ) -} \ No newline at end of file + ); +} -- cgit v1.2.3