diff --git a/client/app/create/components/CandidateCard.tsx b/client/app/create/components/CandidateCard.tsx new file mode 100644 index 00000000..015b3761 --- /dev/null +++ b/client/app/create/components/CandidateCard.tsx @@ -0,0 +1,101 @@ +"use client"; +import React from "react"; +import { motion } from "framer-motion"; +import { TrashIcon } from "@heroicons/react/24/solid"; +import { Candidate } from "../../helpers/candidateValidation"; + +interface CandidateCardProps { + candidate: Candidate; + index: number; + isDuplicate: boolean; + emptyFields: Set<"name" | "description"> | undefined; + onRemove: () => void; + onUpdate: (field: "name" | "description", value: string) => void; + onBlur: (field: "name" | "description") => void; +} + +const inputBaseClasses = + "block w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none sm:text-base min-h-[44px]"; +const inputNormalClasses = + "border-gray-300 focus:ring-indigo-500 focus:border-indigo-500"; +const inputErrorClasses = + "border-red-500 bg-red-50 focus:ring-red-500 focus:border-red-500"; + +/** + * Renders an individual candidate entry with validation states. + * Includes name input, description textarea, and delete button. + */ +const CandidateCard: React.FC = ({ + candidate, + index, + isDuplicate, + emptyFields, + onRemove, + onUpdate, + onBlur, +}) => { + const hasNameError = isDuplicate || emptyFields?.has("name"); + const hasDescriptionError = emptyFields?.has("description"); + + return ( + +
+

+ Candidate {index + 1} +

+ + + +
+ +
+
+ onUpdate("name", e.target.value)} + onBlur={() => onBlur("name")} + placeholder="Candidate Name" + className={`${inputBaseClasses} ${ + hasNameError ? inputErrorClasses : inputNormalClasses + }`} + aria-invalid={hasNameError} + /> + {isDuplicate && ( +

+ Duplicate name - must be unique +

+ )} +
+ +