233 lines
7.1 KiB
TypeScript
233 lines
7.1 KiB
TypeScript
// ContactForm.tsx
|
|
import React, { useState } from "react";
|
|
import { FaSpinner } from "react-icons/fa";
|
|
|
|
export interface ContactData {
|
|
contactId?: number;
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
title: string;
|
|
company: string;
|
|
website: string;
|
|
phone: string;
|
|
}
|
|
|
|
interface Props {
|
|
onSubmit: (data: ContactData) => void;
|
|
loading: boolean;
|
|
}
|
|
|
|
const ContactForm = ({ onSubmit, loading }: Props) => {
|
|
const [formData, setFormData] = useState<ContactData>({
|
|
email: "",
|
|
firstName: "",
|
|
lastName: "",
|
|
title: "",
|
|
company: "",
|
|
website: "",
|
|
phone: "",
|
|
});
|
|
|
|
const [errors, setErrors] = useState<{ [key: string]: string }>({});
|
|
|
|
const handleChange = (field: keyof ContactData, value: string) => {
|
|
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
if (errors[field]) {
|
|
setErrors((prev) => ({ ...prev, [field]: "" }));
|
|
}
|
|
};
|
|
|
|
// const handlePhoneChange = (index: number, value: string) => {
|
|
// const numericValue = value.replace(/\D/g, ""); // Remove non-numeric
|
|
// const updatedPhone = [...formData.phone];
|
|
// updatedPhone[index] = numericValue;
|
|
// setFormData((prev) => ({ ...prev, phone: updatedPhone }));
|
|
|
|
// const errorKey = `phone_${index}`;
|
|
// if (errors[errorKey]) {
|
|
// setErrors((prev) => ({ ...prev, [errorKey]: "" }));
|
|
// }
|
|
// };
|
|
|
|
// const addPhoneField = () => {
|
|
// setFormData((prev) => ({ ...prev, phone: [...prev.phone, ""] }));
|
|
// };
|
|
|
|
// const removePhoneField = (index: number) => {
|
|
// const updatedPhone = formData.phone.filter((_, i) => i !== index);
|
|
// setFormData((prev) => ({ ...prev, phone: updatedPhone }));
|
|
// };
|
|
|
|
const validate = () => {
|
|
const newErrors: { [key: string]: string } = {};
|
|
|
|
// const nameRegex = /^[a-zA-Z\s\-']+$/;
|
|
const urlRegex = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/.*)?$/;
|
|
const phoneRegex =
|
|
/^\+?\d{1,4}?[-.\s]?\(?\d{2,5}\)?[-.\s]?\d{3,5}[-.\s]?\d{3,5}$/;
|
|
|
|
// Email
|
|
if (!formData.email.trim()) {
|
|
newErrors.email = "Email is required";
|
|
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
|
|
newErrors.email = "Invalid email address";
|
|
}
|
|
|
|
// // First Name
|
|
// if (!formData.firstName.trim()) {
|
|
// newErrors.firstName = 'First name is required';
|
|
// } else if (!nameRegex.test(formData.firstName)) {
|
|
// newErrors.firstName = 'First name contains invalid characters';
|
|
// }
|
|
|
|
// Last Name
|
|
if (!formData.lastName.trim()) {
|
|
newErrors.lastName = "Last name is required";
|
|
}
|
|
|
|
// // Title
|
|
// if (!formData.title.trim()) {
|
|
// newErrors.title = 'Title is required';
|
|
// }
|
|
|
|
// Website (optional but validate if present)
|
|
if (formData.website.trim() && !urlRegex.test(formData.website)) {
|
|
newErrors.website = "Invalid website URL";
|
|
}
|
|
|
|
// Phone
|
|
if (formData.phone.trim() && !phoneRegex.test(formData.phone)) {
|
|
newErrors.phone = "Phone number must contain only digits";
|
|
}
|
|
|
|
return newErrors;
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
const validationErrors = validate();
|
|
if (Object.keys(validationErrors).length > 0) {
|
|
setErrors(validationErrors);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await onSubmit(formData);
|
|
|
|
setFormData({
|
|
email: "",
|
|
firstName: "",
|
|
lastName: "",
|
|
title: "",
|
|
company: "",
|
|
website: "",
|
|
phone: "",
|
|
});
|
|
setErrors({});
|
|
} catch (err) {
|
|
console.error("Submit failed:", err);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="px-3 py-3 space-y-2 max-w-[60vw] bg-white rounded-lg">
|
|
<h2 className="text-lg font-medium text-slate-500">Add Contact</h2>
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
{[
|
|
{ label: "First Name", field: "firstName" },
|
|
{ label: "Last Name", field: "lastName" },
|
|
{ label: "Email", field: "email", fieldType: "email" },
|
|
{ label: "Title", field: "title" },
|
|
{ label: "Company", field: "company" },
|
|
{ label: "Website", field: "website", fieldType: "url" },
|
|
{ label: "Phone", field: "phone", fieldType: "tel" },
|
|
].map(({ label, field, fieldType }) => (
|
|
<div key={field} className="flex flex-col">
|
|
<label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">
|
|
{label}
|
|
</label>
|
|
<input
|
|
type={fieldType ? fieldType : "text"}
|
|
value={formData[field as keyof ContactData] as string}
|
|
onChange={(e) =>
|
|
handleChange(field as keyof ContactData, e.target.value)
|
|
}
|
|
required={true}
|
|
className="px-3 py-1.5 border border-gray-400 rounded-md focus:outline-none focus:ring-1 focus:ring-[var(--primary)] text-sm"
|
|
placeholder={`Enter ${label}`}
|
|
/>
|
|
{errors[field] && (
|
|
<span className="text-sm text-red-500 mt-1">{errors[field]}</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* <div>
|
|
<label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">
|
|
Phone Numbers
|
|
</label>
|
|
<div className="space-y-2">
|
|
{formData.phone.map((phone, index) => (
|
|
<div key={index} className="flex flex-col gap-1">
|
|
<div className="flex items-center gap-2">
|
|
<input
|
|
type="text"
|
|
value={phone}
|
|
onChange={(e) => handlePhoneChange(index, e.target.value)}
|
|
className="flex-1 px-3 py-1.5 border border-gray-400 rounded-md focus:outline-none focus:ring-1 focus:ring-[var(--primary)] text-sm"
|
|
maxLength={15}
|
|
placeholder="Enter Phone Numbers"
|
|
/>
|
|
{formData.phone.length > 1 && (
|
|
<button
|
|
type="button"
|
|
onClick={() => removePhoneField(index)}
|
|
className="text-xs text-red-500 hover:underline"
|
|
>
|
|
Remove
|
|
</button>
|
|
)}
|
|
</div>
|
|
{errors[`phone_${index}`] && (
|
|
<span className="text-sm text-red-500">
|
|
{errors[`phone_${index}`]}
|
|
</span>
|
|
)}
|
|
</div>
|
|
))}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={addPhoneField}
|
|
className="text-sm text-blue-500 hover:underline mt-1 cursor-pointer"
|
|
>
|
|
+ Add another phone
|
|
</button>
|
|
</div>
|
|
</div> */}
|
|
|
|
<div className="flex justify-end">
|
|
<button
|
|
type="button"
|
|
onClick={handleSubmit}
|
|
disabled={loading}
|
|
className="bg-gradient-to-br from-[var(--primary)] to-blue-600 px-5 py-2 rounded-md text-white text-sm font-medium hover:bg-opacity-90 transition disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer"
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<FaSpinner className="animate-spin inline-block mr-2" />
|
|
SAVING...
|
|
</>
|
|
) : (
|
|
"SAVE CONTACT"
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ContactForm;
|