"use client"; import React, { useState, useCallback, ChangeEvent, DragEvent } from "react"; import { FaUpload, FaFileAlt, FaTimes } from "react-icons/fa"; import { useForm, SubmitHandler } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import Button from "./ui/Button"; const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const ACCEPTED_FILE_TYPES = [ "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "text/plain", ]; const applicationSchema = z.object({ name: z.string().min(2, "Full name must be at least 2 characters"), email: z.string().email("Invalid email address"), phone: z.string().optional(), linkedin: z.string().url("Invalid URL").optional().or(z.literal("")), coverLetter: z.string().optional(), resume: z .custom((val) => val instanceof File, "Resume/CV is required") .refine( (file) => file && file.size <= MAX_FILE_SIZE, `Max file size is 5MB.` ) .refine( (file) => file && ACCEPTED_FILE_TYPES.includes(file.type), "Unsupported file format. Please upload PDF, DOC, DOCX, or TXT." ), }); type ApplicationFormData = z.infer; interface VacancyApplicationFormProps { vacancyId: string; vacancyTitle: string; onClose: () => void; } export default function VacancyApplicationForm({ vacancyId, vacancyTitle, onClose, }: VacancyApplicationFormProps) { const { register, handleSubmit, setValue, watch, formState: { errors, isSubmitting }, reset, } = useForm({ resolver: zodResolver(applicationSchema), mode: "onChange", defaultValues: { name: "", email: "", phone: "", linkedin: "", coverLetter: "", resume: null, }, }); const resumeFile = watch("resume"); const [isDragging, setIsDragging] = useState(false); const [submitStatus, setSubmitStatus] = useState< "idle" | "success" | "error" >("idle"); const handleFileChange = (e: ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { setValue("resume", e.target.files[0], { shouldValidate: true }); e.target.value = ""; } }; const handleDragEnter = useCallback((e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }, []); const handleDragLeave = useCallback((e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); }, []); const handleDragOver = useCallback( (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); if (!isDragging) setIsDragging(true); }, [isDragging] ); const handleDrop = useCallback( (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { setValue("resume", e.dataTransfer.files[0], { shouldValidate: true }); e.dataTransfer.clearData(); } }, [setValue] ); const removeFile = () => { setValue("resume", null, { shouldValidate: true }); }; const onSubmit: SubmitHandler = async (data) => { setSubmitStatus("idle"); const formData = new FormData(); formData.append("vacancyId", vacancyId); formData.append("vacancyTitle", vacancyTitle); formData.append("name", data.name); formData.append("email", data.email); if (data.phone) formData.append("phone", data.phone); if (data.linkedin) formData.append("linkedin", data.linkedin); if (data.coverLetter) formData.append("coverLetter", data.coverLetter); if (data.resume) formData.append("resume", data.resume, data.resume.name); try { const response = await fetch("/api/apply", { method: "POST", body: formData, }); if (!response.ok) throw new Error(`Submission failed: ${response.statusText}`); setSubmitStatus("success"); reset(); setTimeout(() => { onClose(); setSubmitStatus("idle"); }, 1500); } catch (error) { console.error(error); setSubmitStatus("error"); } }; const inputBaseStyle = "block w-full px-4 py-2 mt-1 text-gray-700 bg-white border border-gray-300 rounded-md focus:border-gold-500 focus:ring focus:ring-gold-500 focus:ring-opacity-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:focus:border-gold-500 font-poppins"; const errorStyle = "border-red-500 focus:border-red-500 focus:ring-red-500"; const errorMessageStyle = "text-red-600 text-xs mt-1"; return (
{/* Row 1: Name & Email */}
{/* Name Field */}
{errors.name && (

{errors.name.message}

)}
{/* Email Field */}
{errors.email && (

{errors.email.message}

)}
{/* Row 2: Phone & LinkedIn */}
{/* Phone Field */}
{errors.phone && (

{errors.phone.message}

)}
{/* LinkedIn Field */}
{errors.linkedin && (

{errors.linkedin.message}

)}
{/* Resume/CV Upload */}
document.getElementById("resume-upload-hidden")?.click() } role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") document.getElementById("resume-upload-hidden")?.click(); }} aria-label="Upload Resume/CV" >

{isDragging ? "Drop file here" : "Drag & drop your file here"}

or click to browse

(PDF, DOC, DOCX, TXT - Max 5MB)

{errors.resume && (

{(errors.resume as unknown as { message: string }).message}

)} {resumeFile && (
{resumeFile.name} ({(resumeFile.size / 1024).toFixed(1)} KB)
)}
{/* Cover Letter Field */}
{errors.coverLetter && (

{errors.coverLetter.message}

)}
{/* Submit Button & Status */}
{submitStatus === "success" && (

Application submitted successfully! Closing form...

)} {submitStatus === "error" && (

Submission failed. Please check your details and try again.

)}
); }