From fbb3b7f05737f9571b04b0a8f4f15c0928de8545 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 7 Jul 2025 01:43:36 +0000 Subject: (대표님) 변경사항 20250707 10시 43분 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/signup/join-form.tsx | 379 ++++++++++++++++++++++++++++------------ 1 file changed, 265 insertions(+), 114 deletions(-) (limited to 'components/signup') diff --git a/components/signup/join-form.tsx b/components/signup/join-form.tsx index 30449a63..ecaf6bc3 100644 --- a/components/signup/join-form.tsx +++ b/components/signup/join-form.tsx @@ -39,7 +39,7 @@ import { Check, ChevronsUpDown, Loader2, Plus, X } from "lucide-react" import { cn } from "@/lib/utils" import { useTranslation } from "@/i18n/client" -import { createVendor, getVendorTypes } from "@/lib/vendors/service" +import { getVendorTypes } from "@/lib/vendors/service" import { createVendorSchema, CreateVendorSchema } from "@/lib/vendors/validations" import { Select, @@ -70,6 +70,7 @@ import { 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) @@ -161,8 +162,11 @@ export function JoinForm() { const [vendorTypes, setVendorTypes] = React.useState([]) const [isLoadingVendorTypes, setIsLoadingVendorTypes] = React.useState(true) - // File states - const [selectedFiles, setSelectedFiles] = React.useState([]) + // 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) @@ -207,7 +211,7 @@ export function JoinForm() { representativeEmail: "", representativePhone: "", corporateRegistrationNumber: "", - attachedFiles: undefined, + representativeWorkExpirence: false, // contacts (no isPrimary) contacts: [ { @@ -220,11 +224,31 @@ export function JoinForm() { }, mode: "onChange", }) - const isFormValid = form.formState.isValid - console.log("Form errors:", form.formState.errors); - console.log("Form values:", form.getValues()); - console.log("Form valid:", form.formState.isValid); + // Custom validation for file uploads + const validateRequiredFiles = () => { + const errors = [] + + if (businessRegistrationFiles.length === 0) { + errors.push("사업자등록증을 업로드해주세요.") + } + + if (isoCertificationFiles.length === 0) { + errors.push("ISO 인증서를 업로드해주세요.") + } + + if (creditReportFiles.length === 0) { + errors.push("신용평가보고서를 업로드해주세요.") + } + + if (form.watch("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 } = @@ -233,36 +257,53 @@ export function JoinForm() { name: "contacts", }) - // Dropzone handlers - const handleDropAccepted = (acceptedFiles: File[]) => { - const newFiles = [...selectedFiles, ...acceptedFiles] - setSelectedFiles(newFiles) - form.setValue("attachedFiles", newFiles, { shouldValidate: true }) - } - const handleDropRejected = (fileRejections: any[]) => { - fileRejections.forEach((rej) => { - toast({ - variant: "destructive", - title: "File Error", - description: `${rej.file.name}: ${rej.errors[0]?.message || "Upload failed"}`, + // 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"}`, + }) }) - }) - } - const removeFile = (index: number) => { - const updated = [...selectedFiles] - updated.splice(index, 1) - setSelectedFiles(updated) - form.setValue("attachedFiles", updated, { shouldValidate: true }) - } + }, + removeFile: (index: number) => { + const updated = [...currentFiles] + updated.splice(index, 1) + setFiles(updated) + } + }) + + const businessRegistrationHandler = createFileUploadHandler(setBusinessRegistrationFiles, businessRegistrationFiles) + const isoCertificationHandler = createFileUploadHandler(setIsoCertificationFiles, isoCertificationFiles) + const creditReportHandler = createFileUploadHandler(setCreditReportFiles, creditReportFiles) + const bankAccountHandler = createFileUploadHandler(setBankAccountFiles, bankAccountFiles) // Submit async function onSubmit(values: CreateVendorSchema) { + const fileErrors = validateRequiredFiles() + if (fileErrors.length > 0) { + toast({ + variant: "destructive", + title: "파일 업로드 필수", + description: fileErrors.join("\n"), + }) + return + } + setIsSubmitting(true) try { - const mainFiles = values.attachedFiles - ? Array.from(values.attachedFiles as FileList) - : [] + const formData = new FormData() + // Add vendor data const vendorData = { vendorName: values.vendorName, vendorTypeId: values.vendorTypeId, @@ -279,16 +320,40 @@ export function JoinForm() { representativeBirth: values.representativeBirth || "", representativeEmail: values.representativeEmail || "", representativePhone: values.representativePhone || "", - corporateRegistrationNumber: values.corporateRegistrationNumber || "" + corporateRegistrationNumber: values.corporateRegistrationNumber || "", + representativeWorkExpirence: values.representativeWorkExpirence || false + } + + formData.append('vendorData', JSON.stringify(vendorData)) + formData.append('contacts', JSON.stringify(values.contacts)) + + // Add files with specific types + businessRegistrationFiles.forEach(file => { + formData.append('businessRegistration', file) + }) + + isoCertificationFiles.forEach(file => { + formData.append('isoCertification', file) + }) + + creditReportFiles.forEach(file => { + formData.append('creditReport', file) + }) + + if (values.country !== "KR") { + bankAccountFiles.forEach(file => { + formData.append('bankAccount', file) + }) } - const result = await createVendor({ - vendorData, - files: mainFiles, - contacts: values.contacts, + const response = await fetch('/api/vendors', { + method: 'POST', + body: formData, }) - if (!result.error) { + const result = await response.json() + + if (response.ok) { toast({ title: "등록 완료", description: "회사 등록이 완료되었습니다. (status=PENDING_REVIEW)", @@ -340,7 +405,7 @@ export function JoinForm() { } }; - const getPhoneDescription = (countryCode: string) => { + const getPhoneDescription = (countryCode: string) => { if (!countryCode) return "국가를 먼저 선택해주세요."; const dialCode = countryDialCodes[countryCode]; @@ -359,7 +424,84 @@ export function JoinForm() { return `${dialCode}로 시작하는 국제 전화번호를 입력하세요.`; } }; - + + // 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; + }) => ( +
+
+
+ {title} + {required && *} +
+

{description}

+
+ + + {({ maxSize }) => ( + + +
+ +
+ 파일 업로드 + + 드래그 또는 클릭 + {maxSize ? ` (최대: ${prettyBytes(maxSize)})` : null} + +
+
+
+ )} +
+ + {files.length > 0 && ( +
+ + + {files.map((file, i) => ( + + + + + {file.name} + + {prettyBytes(file.size)} + + + removeFile(i)}> + + + + + ))} + + +
+ )} +
+ ) // Render return ( @@ -391,7 +533,7 @@ export function JoinForm() {

기본 정보

- {/* Vendor Type - New Field */} + {/* Vendor Type */} - {/* Items - New Field */} + {/* Items */} - {/* Country - Updated with enhanced list */} + {/* Country */} - - {/* Phone - Updated with country code hint */} + {/* Phone */} - {/* Email - Updated with company domain guidance */} + {/* Email */}
- {/* contactName - All required now */} + {/* contactName */} - {/* contactPosition - Now required */} + {/* contactPosition */} - {/* contactPhone - Now required */} + {/* contactPhone */}

한국 사업자 정보

- {/* 대표자 등... all now required for Korean companies */}
)} /> + + ( + + + + +
+ + 대표자 삼성중공업 근무이력 + + + 대표자가 삼성중공업에서 근무한 경험이 있는 경우 체크해주세요. + +
+
+ )} + /> +
)} {/* ───────────────────────────────────────── - 첨부파일 (사업자등록증 등) + Required Document Uploads ───────────────────────────────────────── */} -
-

기타 첨부파일

- ( - - - 첨부 파일 - - - 사업자등록증, ISO 9001 인증서, 회사 브로셔, 기본 소개자료 등을 첨부해주세요. - - - {({ maxSize }) => ( - - -
- -
- 파일 업로드 - - 드래그 또는 클릭 - {maxSize - ? ` (최대: ${prettyBytes(maxSize)})` - : null} - -
-
-
- )} -
- {selectedFiles.length > 0 && ( -
- - - {selectedFiles.map((file, i) => ( - - - - - {file.name} - - {prettyBytes(file.size)} - - - removeFile(i)}> - - - - - ))} - - -
- )} -
- )} +
+

필수 첨부 서류

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