From fbb3b7f05737f9571b04b0a8f4f15c0928de8545 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
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/join-form.tsx')

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<VendorType[]>([])
   const [isLoadingVendorTypes, setIsLoadingVendorTypes] = React.useState(true)
 
-  // File states
-  const [selectedFiles, setSelectedFiles] = React.useState<File[]>([])
+  // Individual file states
+  const [businessRegistrationFiles, setBusinessRegistrationFiles] = React.useState<File[]>([])
+  const [isoCertificationFiles, setIsoCertificationFiles] = React.useState<File[]>([])
+  const [creditReportFiles, setCreditReportFiles] = React.useState<File[]>([])
+  const [bankAccountFiles, setBankAccountFiles] = React.useState<File[]>([])
 
   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<React.SetStateAction<File[]>>,
+    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;
+  }) => (
+    <div className="space-y-4">
+      <div>
+        <h5 className="text-sm font-medium">
+          {title}
+          {required && <span className="text-red-500 ml-1">*</span>}
+        </h5>
+        <p className="text-xs text-muted-foreground mt-1">{description}</p>
+      </div>
+      
+      <Dropzone
+        maxSize={MAX_FILE_SIZE}
+        multiple
+        onDropAccepted={onDropAccepted}
+        onDropRejected={onDropRejected}
+        disabled={isSubmitting}
+      >
+        {({ maxSize }) => (
+          <DropzoneZone className="flex justify-center">
+            <DropzoneInput />
+            <div className="flex items-center gap-4">
+              <DropzoneUploadIcon />
+              <div className="grid gap-1">
+                <DropzoneTitle>파일 업로드</DropzoneTitle>
+                <DropzoneDescription>
+                  드래그 또는 클릭
+                  {maxSize ? ` (최대: ${prettyBytes(maxSize)})` : null}
+                </DropzoneDescription>
+              </div>
+            </div>
+          </DropzoneZone>
+        )}
+      </Dropzone>
+      
+      {files.length > 0 && (
+        <div className="mt-2">
+          <ScrollArea className="max-h-32">
+            <FileList className="gap-2">
+              {files.map((file, i) => (
+                <FileListItem key={file.name + i}>
+                  <FileListHeader>
+                    <FileListIcon />
+                    <FileListInfo>
+                      <FileListName>{file.name}</FileListName>
+                      <FileListDescription>
+                        {prettyBytes(file.size)}
+                      </FileListDescription>
+                    </FileListInfo>
+                    <FileListAction onClick={() => removeFile(i)}>
+                      <X className="h-4 w-4" />
+                    </FileListAction>
+                  </FileListHeader>
+                </FileListItem>
+              ))}
+            </FileList>
+          </ScrollArea>
+        </div>
+      )}
+    </div>
+  )
   
   // Render
   return (
@@ -391,7 +533,7 @@ export function JoinForm() {
               <div className="rounded-md border p-4 space-y-4">
                 <h4 className="text-md font-semibold">기본 정보</h4>
                 <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
-                  {/* Vendor Type - New Field */}
+                  {/* Vendor Type */}
                   <FormField
                     control={form.control}
                     name="vendorTypeId"
@@ -481,7 +623,7 @@ export function JoinForm() {
                     )}
                   />
 
-                  {/* Items - New Field */}
+                  {/* Items */}
                   <FormField
                     control={form.control}
                     name="items"
@@ -516,7 +658,7 @@ export function JoinForm() {
                     )}
                   />
 
-                  {/* Country - Updated with enhanced list */}
+                  {/* Country */}
                   <FormField
                     control={form.control}
                     name="country"
@@ -583,8 +725,7 @@ export function JoinForm() {
                       )
                     }}
                   />
-
-                  {/* Phone - Updated with country code hint */}
+                  {/* Phone */}
                   <FormField
                     control={form.control}
                     name="phone"
@@ -611,7 +752,7 @@ export function JoinForm() {
                     )}
                   />
 
-                  {/* Email - Updated with company domain guidance */}
+                  {/* Email */}
                   <FormField
                     control={form.control}
                     name="email"
@@ -679,7 +820,7 @@ export function JoinForm() {
                       className="bg-muted/10 rounded-md p-4 space-y-4"
                     >
                       <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
-                        {/* contactName - All required now */}
+                        {/* contactName */}
                         <FormField
                           control={form.control}
                           name={`contacts.${index}.contactName`}
@@ -696,7 +837,7 @@ export function JoinForm() {
                           )}
                         />
 
-                        {/* contactPosition - Now required */}
+                        {/* contactPosition */}
                         <FormField
                           control={form.control}
                           name={`contacts.${index}.contactPosition`}
@@ -730,7 +871,7 @@ export function JoinForm() {
                           )}
                         />
 
-                        {/* contactPhone - Now required */}
+                        {/* contactPhone */}
                         <FormField
                           control={form.control}
                           name={`contacts.${index}.contactPhone`}
@@ -777,7 +918,6 @@ export function JoinForm() {
                 <div className="rounded-md border p-4 space-y-4">
                   <h4 className="text-md font-semibold">한국 사업자 정보</h4>
 
-                  {/* 대표자 등... all now required for Korean companies */}
                   <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
                     <FormField
                       control={form.control}
@@ -858,78 +998,89 @@ export function JoinForm() {
                         </FormItem>
                       )}
                     />
+
+<FormField
+                      control={form.control}
+                      name="representativeWorkExpirence"
+                      render={({ field }) => (
+                        <FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
+                          <FormControl>
+                            <Checkbox
+                              checked={field.value}
+                              onCheckedChange={field.onChange}
+                              disabled={isSubmitting}
+                            />
+                          </FormControl>
+                          <div className="space-y-1 leading-none">
+                            <FormLabel>
+                              대표자 삼성중공업 근무이력
+                            </FormLabel>
+                            <FormDescription>
+                              대표자가 삼성중공업에서 근무한 경험이 있는 경우 체크해주세요.
+                            </FormDescription>
+                          </div>
+                        </FormItem>
+                      )}
+                    />
+                    
                   </div>
                 </div>
               )}
 
               {/* ─────────────────────────────────────────
-                  첨부파일 (사업자등록증 등)
+                  Required Document Uploads
               ───────────────────────────────────────── */}
-              <div className="rounded-md border p-4 space-y-4">
-                <h4 className="text-md font-semibold">기타 첨부파일</h4>
-                <FormField
-                  control={form.control}
-                  name="attachedFiles"
-                  render={() => (
-                    <FormItem>
-                      <FormLabel className="after:content-['*'] after:ml-0.5 after:text-red-500">
-                        첨부 파일
-                      </FormLabel>
-                      <FormDescription>
-                        사업자등록증, ISO 9001 인증서, 회사 브로셔, 기본 소개자료 등을 첨부해주세요.
-                      </FormDescription>
-                      <Dropzone
-                        maxSize={MAX_FILE_SIZE}
-                        multiple
-                        onDropAccepted={handleDropAccepted}
-                        onDropRejected={handleDropRejected}
-                        disabled={isSubmitting}
-                      >
-                        {({ maxSize }) => (
-                          <DropzoneZone className="flex justify-center">
-                            <DropzoneInput />
-                            <div className="flex items-center gap-4">
-                              <DropzoneUploadIcon />
-                              <div className="grid gap-1">
-                                <DropzoneTitle>파일 업로드</DropzoneTitle>
-                                <DropzoneDescription>
-                                  드래그 또는 클릭
-                                  {maxSize
-                                    ? ` (최대: ${prettyBytes(maxSize)})`
-                                    : null}
-                                </DropzoneDescription>
-                              </div>
-                            </div>
-                          </DropzoneZone>
-                        )}
-                      </Dropzone>
-                      {selectedFiles.length > 0 && (
-                        <div className="mt-2">
-                          <ScrollArea className="max-h-32">
-                            <FileList className="gap-2">
-                              {selectedFiles.map((file, i) => (
-                                <FileListItem key={file.name + i}>
-                                  <FileListHeader>
-                                    <FileListIcon />
-                                    <FileListInfo>
-                                      <FileListName>{file.name}</FileListName>
-                                      <FileListDescription>
-                                        {prettyBytes(file.size)}
-                                      </FileListDescription>
-                                    </FileListInfo>
-                                    <FileListAction onClick={() => removeFile(i)}>
-                                      <X className="h-4 w-4" />
-                                    </FileListAction>
-                                  </FileListHeader>
-                                </FileListItem>
-                              ))}
-                            </FileList>
-                          </ScrollArea>
-                        </div>
-                      )}
-                    </FormItem>
-                  )}
+              <div className="rounded-md border p-4 space-y-6">
+                <h4 className="text-md font-semibold">필수 첨부 서류</h4>
+                
+                {/* Business Registration */}
+                <FileUploadSection
+                  title="사업자등록증"
+                  description="사업자등록증 스캔본 또는 사진을 업로드해주세요. 모든 내용이 선명하게 보여야 합니다."
+                  files={businessRegistrationFiles}
+                  onDropAccepted={businessRegistrationHandler.onDropAccepted}
+                  onDropRejected={businessRegistrationHandler.onDropRejected}
+                  removeFile={businessRegistrationHandler.removeFile}
                 />
+
+                <Separator />
+
+                {/* ISO Certification */}
+                <FileUploadSection
+                  title="ISO 인증서"
+                  description="ISO 9001, ISO 14001 등 품질/환경 관리 인증서를 업로드해주세요. 유효기간이 확인 가능해야 합니다."
+                  files={isoCertificationFiles}
+                  onDropAccepted={isoCertificationHandler.onDropAccepted}
+                  onDropRejected={isoCertificationHandler.onDropRejected}
+                  removeFile={isoCertificationHandler.removeFile}
+                />
+
+                <Separator />
+
+                {/* Credit Report */}
+                <FileUploadSection
+                  title="신용평가보고서"
+                  description="신용평가기관(KIS, NICE 등)에서 발급한 발행 1년 이내의 신용평가보고서를 업로드해주세요. 전년도 재무제표 필수표시. 신규업체, 영세업체로 재무제표 및 신용평가 결과가 없을 경우는 국세, 지방세 납입 증명으로 신용평가를 갈음할 수 있음"
+                  files={creditReportFiles}
+                  onDropAccepted={creditReportHandler.onDropAccepted}
+                  onDropRejected={creditReportHandler.onDropRejected}
+                  removeFile={creditReportHandler.removeFile}
+                />
+
+                {/* Bank Account Copy - Only for non-Korean companies */}
+                {form.watch("country") !== "KR" && (
+                  <>
+                    <Separator />
+                    <FileUploadSection
+                      title="대금지급 통장사본"
+                      description="대금 지급용 은행 계좌의 통장 사본 또는 계좌증명서를 업로드해주세요. 계좌번호와 예금주명이 명확히 보여야 합니다."
+                      files={bankAccountFiles}
+                      onDropAccepted={bankAccountHandler.onDropAccepted}
+                      onDropRejected={bankAccountHandler.onDropRejected}
+                      removeFile={bankAccountHandler.removeFile}
+                    />
+                  </>
+                )}
               </div>
 
               {/* ─────────────────────────────────────────
-- 
cgit v1.2.3

