Compare commits

..

No commits in common. "0b039b9dabe075b75c42af444a59265405bb6830" and "d3b4ee61c964a8ca36068c1e5af297beb928a310" have entirely different histories.

3 changed files with 397 additions and 409 deletions

View File

@ -11,7 +11,6 @@ type ContactSchema = [{
firstName: string firstName: string
lastName: string lastName: string
email: string email: string
phone: string
}] }]
const Contacts = () => { const Contacts = () => {

View File

@ -1,214 +1,203 @@
// ContactForm.tsx // ContactForm.tsx
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import { FaSpinner } from "react-icons/fa"; import { FaSpinner } from 'react-icons/fa';
export interface ContactData { export interface ContactData {
contactId?: number; contactId?: number;
email: string; email: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
title: string; title: string;
company: string; company: string;
website: string; website: string;
phone: string; phones: string[];
} }
interface Props { interface Props {
onSubmit: (data: ContactData) => void; onSubmit: (data: ContactData) => void;
loading: boolean; loading: boolean;
setShowModal: () => React.Dispatch<React.SetStateAction<boolean>>; setShowModal: () => React.Dispatch<React.SetStateAction<boolean>>
actionStatus: string; actionStatus: string,
editJsonData: any; editJsonData: any
} }
const ContactFormModal = ({ const ContactFormModal = ({ onSubmit, loading, setShowModal, actionStatus, editJsonData }: Props) => {
onSubmit, const [formData, setFormData] = useState<ContactData>({
loading, email: '',
setShowModal, firstName: '',
actionStatus, lastName: '',
editJsonData, title: '',
}: Props) => { company: '',
const [formData, setFormData] = useState<ContactData>({ website: '',
email: "", phones: [''],
firstName: "", });
lastName: "",
title: "",
company: "",
website: "",
phone: "",
});
const [errors, setErrors] = useState<{ [key: string]: string }>({}); const [errors, setErrors] = useState<{ [key: string]: string }>({});
useEffect(() => { useEffect(() => {
if (actionStatus === "Edit" && editJsonData) { if (actionStatus === 'Edit' && editJsonData) {
setFormData({ setFormData({
email: editJsonData.email, email: editJsonData.email,
firstName: editJsonData.firstName, firstName: editJsonData.firstName,
lastName: editJsonData.lastName, lastName: editJsonData.lastName,
title: editJsonData.title, title: editJsonData.title,
company: editJsonData.company, company: editJsonData.company,
website: editJsonData.website, website: editJsonData.website,
phone: editJsonData.phone, phones: editJsonData.phones,
}); })
} }
}, [actionStatus, editJsonData]); }, [actionStatus, editJsonData])
const handleChange = (field: keyof ContactData, value: string) => {
console.log(field, value);
setFormData((prev) => ({ ...prev, [field]: value }));
if (errors[field]) {
setErrors((prev) => ({ ...prev, [field]: "" }));
}
};
// const handlePhoneChange = (index: number, value: string) => { const handleChange = (field: keyof ContactData, value: string) => {
// const numericValue = value.replace(/\D/g, ""); // Remove non-numeric setFormData((prev) => ({ ...prev, [field]: value }));
// const updatedPhones = [...formData.phone]; if (errors[field]) {
// updatedPhones[index] = numericValue; setErrors((prev) => ({ ...prev, [field]: '' }));
// setFormData((prev) => ({ ...prev, phones: updatedPhones })); }
};
// const errorKey = `phones_${index}`; const handlePhoneChange = (index: number, value: string) => {
// if (errors[errorKey]) { const numericValue = value.replace(/\D/g, ''); // Remove non-numeric
// setErrors((prev) => ({ ...prev, [errorKey]: "" })); const updatedPhones = [...formData.phones];
// } updatedPhones[index] = numericValue;
// }; setFormData((prev) => ({ ...prev, phones: updatedPhones }));
// const addPhoneField = () => { const errorKey = `phones_${index}`;
// setFormData((prev) => ({ ...prev, phone: [...prev.phone, ''] })); if (errors[errorKey]) {
// }; setErrors((prev) => ({ ...prev, [errorKey]: '' }));
}
};
// const removePhoneField = (index: number) => {
// const updatedPhones = formData.phones.filter((_, i) => i !== index);
// setFormData((prev) => ({ ...prev, phones: updatedPhones }));
// };
const validate = () => { const addPhoneField = () => {
const newErrors: { [key: string]: string } = {}; setFormData((prev) => ({ ...prev, phones: [...prev.phones, ''] }));
};
const emailRegex = /\S+@\S+\.\S+/; const removePhoneField = (index: number) => {
// const nameRegex = /^[a-zA-Z\s\-']+$/; const updatedPhones = formData.phones.filter((_, i) => i !== index);
const urlRegex = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/.*)?$/; setFormData((prev) => ({ ...prev, phones: updatedPhones }));
const phoneRegex = };
/^\+?\d{1,4}?[-.\s]?\(?\d{2,5}\)?[-.\s]?\d{3,5}[-.\s]?\d{3,5}$/;
// Email const validate = () => {
if (!formData.email.trim()) { const newErrors: { [key: string]: string } = {};
newErrors.email = "Email is required";
} else if (!emailRegex.test(formData.email)) {
newErrors.email = "Invalid email address";
}
// // First Name const nameRegex = /^[a-zA-Z\s\-']+$/;
// if (!formData.firstName.trim()) { const urlRegex = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/.*)?$/;
// newErrors.firstName = 'First name is required'; const phoneRegex = /^\d+$/;
// } else
// if (formData.firstName.trim() && !nameRegex.test(formData.firstName)) {
// newErrors.firstName = "First name contains invalid characters";
// }
// Last Name // Email
if (!formData.lastName.trim()) { if (!formData.email.trim()) {
newErrors.lastName = "Last name is required"; newErrors.email = 'Email is required';
} } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
// else if (!nameRegex.test(formData.lastName)) { newErrors.email = 'Invalid email address';
// newErrors.lastName = "Last name contains invalid characters"; }
// }
// // Title // // First Name
// if (!formData.title.trim()) { // if (!formData.firstName.trim()) {
// newErrors.title = 'Title is required'; // newErrors.firstName = 'First name is required';
// } // } else if (!nameRegex.test(formData.firstName)) {
// newErrors.firstName = 'First name contains invalid characters';
// }
// Website (optional but validate if present) // Last Name
if (formData.website.trim() && !urlRegex.test(formData.website)) { if (!formData.lastName.trim()) {
newErrors.website = "Invalid website URL"; newErrors.lastName = 'Last name is required';
} } else if (!nameRegex.test(formData.lastName)) {
newErrors.lastName = 'Last name contains invalid characters';
}
// Phone // // Title
if (formData.phone.trim() && !phoneRegex.test(formData.phone)) { // if (!formData.title.trim()) {
newErrors.phone = "Phone number must contain only digits"; // newErrors.title = 'Title is required';
} // }
return newErrors;
};
const handleSubmit = async () => {
const validationErrors = validate();
let type = "Add";
let payload = formData;
console.log(payload);
if (Object.keys(validationErrors).length > 0) { // Website (optional but validate if present)
setErrors(validationErrors); if (formData.website.trim() && !urlRegex.test(formData.website)) {
return; newErrors.website = 'Invalid website URL';
} }
try {
if (actionStatus === "Edit") {
type = "Edit";
payload = {
...formData,
contactId: editJsonData.id,
};
}
await onSubmit(payload, type);
setFormData({ // Phones
email: "", // formData.phones.forEach((phone, index) => {
firstName: "", // if (!phone.trim()) {
lastName: "", // newErrors[`phones_${index}`] = 'Phone number is required';
title: "", // } else if (!phoneRegex.test(phone)) {
company: "", // newErrors[`phones_${index}`] = 'Phone number must contain only digits';
website: "", // }
phone: "", // });
});
setErrors({});
} catch (err) {
console.error("Submit failed:", err);
}
};
return ( return newErrors;
<div className="fixed inset-0 z-50 flex items-center justify-center bg-white/5 backdrop-blur-sm bg-opacity-50"> };
<div className="bg-white rounded-md p-6 max-w-[700px] w-full shadow-lg relative">
<div className="ms-2 space-y-2 max-w-[60vw]">
<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> const handleSubmit = async () => {
<label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">Phone Number</label> const validationErrors = validate();
<div className="space-y-2"> let type = 'Add'
let payload = formData
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
try {
if (actionStatus === 'Edit') {
type = 'Edit'
payload = {
...formData,
contactId: editJsonData.id
}
}
await onSubmit(payload, type);
setFormData({
email: '',
firstName: '',
lastName: '',
title: '',
company: '',
website: '',
phones: [''],
});
setErrors({});
} catch (err) {
console.error('Submit failed:', err);
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-white/5 backdrop-blur-sm bg-opacity-50">
<div className="bg-white rounded-md p-6 max-w-[700px] w-full shadow-lg relative">
<div className="ms-2 space-y-2 max-w-[60vw]">
<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' },
{ label: 'Title', field: 'title' },
{ label: 'Company', field: 'company' },
{ label: 'Website', field: 'website' },
].map(({ label, field }) => (
<div key={field} className="flex flex-col">
<label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">{label}</label>
<input
type={field === 'email' ? 'email' : '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?.phones?.map((phone, index) => ( {formData?.phones?.map((phone, index) => (
<div key={index} className="flex flex-col gap-1"> <div key={index} className="flex flex-col gap-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -244,38 +233,40 @@ const ContactFormModal = ({
+ Add another phone + Add another phone
</button> </button>
</div> </div>
</div> */} </div>
<div className="flex justify-end"> <div className="flex justify-end">
<button <button
type="button" type="button"
onClick={() => setShowModal((prev) => !prev)} onClick={() => setShowModal((prev) => !prev)}
disabled={loading} disabled={loading}
className="bg-gray-300 px-5 py-1.5 rounded-md text-slate-700 text-sm font-medium hover:bg-opacity-90 transition disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer me-3" className="bg-gray-300 px-5 py-1.5 rounded-md text-slate-700 text-sm font-medium hover:bg-opacity-90 transition disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer me-3"
> > Cancel
{" "} </button>
Cancel <button
</button> type="button"
<button onClick={handleSubmit}
type="button" disabled={loading}
onClick={handleSubmit} className="bg-gray-300 px-5 py-1.5 rounded-md text-slate-700 text-sm font-medium hover:bg-opacity-90 transition disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer"
disabled={loading} >
className="bg-gray-300 px-5 py-1.5 rounded-md text-slate-700 text-sm font-medium hover:bg-opacity-90 transition disabled:opacity-60 disabled:cursor-not-allowed cursor-pointer" {loading ? (
> <>
{loading ? ( <FaSpinner className="animate-spin inline-block mr-2" />
<> SAVING...
<FaSpinner className="animate-spin inline-block mr-2" /> </>
SAVING... ) : (
</> `${actionStatus?.toUpperCase() || 'SAVE'} CONTACT`
) : ( )}
`${actionStatus?.toUpperCase() || "SAVE"} CONTACT`
)} </button>
</button>
</div>
</div>
</div>
</div>
</div> </div>
</div> );
</div>
);
}; };
export default ContactFormModal; export default ContactFormModal;

View File

@ -1,232 +1,230 @@
// ContactForm.tsx // ContactForm.tsx
import React, { useState } from "react"; import React, { useState } from 'react';
import { FaSpinner } from "react-icons/fa"; import { FaSpinner } from 'react-icons/fa';
export interface ContactData { export interface ContactData {
contactId?: number; contactId?: number;
email: string; email: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
title: string; title: string;
company: string; company: string;
website: string; website: string;
phone: string; phones: string[];
} }
interface Props { interface Props {
onSubmit: (data: ContactData) => void; onSubmit: (data: ContactData) => void;
loading: boolean; loading: boolean;
} }
const ContactForm = ({ onSubmit, loading }: Props) => { const ContactForm = ({ onSubmit, loading }: Props) => {
const [formData, setFormData] = useState<ContactData>({ const [formData, setFormData] = useState<ContactData>({
email: "", email: '',
firstName: "", firstName: '',
lastName: "", lastName: '',
title: "", title: '',
company: "", company: '',
website: "", website: '',
phone: "", phones: [''],
}); });
const [errors, setErrors] = useState<{ [key: string]: string }>({}); const [errors, setErrors] = useState<{ [key: string]: string }>({});
const handleChange = (field: keyof ContactData, value: string) => { const handleChange = (field: keyof ContactData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value })); setFormData((prev) => ({ ...prev, [field]: value }));
if (errors[field]) { if (errors[field]) {
setErrors((prev) => ({ ...prev, [field]: "" })); setErrors((prev) => ({ ...prev, [field]: '' }));
} }
}; };
// const handlePhoneChange = (index: number, value: string) => { const handlePhoneChange = (index: number, value: string) => {
// const numericValue = value.replace(/\D/g, ""); // Remove non-numeric const numericValue = value.replace(/\D/g, ''); // Remove non-numeric
// const updatedPhone = [...formData.phone]; const updatedPhones = [...formData.phones];
// updatedPhone[index] = numericValue; updatedPhones[index] = numericValue;
// setFormData((prev) => ({ ...prev, phone: updatedPhone })); setFormData((prev) => ({ ...prev, phones: updatedPhones }));
// const errorKey = `phone_${index}`; const errorKey = `phones_${index}`;
// if (errors[errorKey]) { if (errors[errorKey]) {
// setErrors((prev) => ({ ...prev, [errorKey]: "" })); setErrors((prev) => ({ ...prev, [errorKey]: '' }));
// } }
// }; };
// const addPhoneField = () => {
// setFormData((prev) => ({ ...prev, phone: [...prev.phone, ""] }));
// };
// const removePhoneField = (index: number) => { const addPhoneField = () => {
// const updatedPhone = formData.phone.filter((_, i) => i !== index); setFormData((prev) => ({ ...prev, phones: [...prev.phones, ''] }));
// setFormData((prev) => ({ ...prev, phone: updatedPhone })); };
// };
const validate = () => { const removePhoneField = (index: number) => {
const newErrors: { [key: string]: string } = {}; const updatedPhones = formData.phones.filter((_, i) => i !== index);
setFormData((prev) => ({ ...prev, phones: updatedPhones }));
};
// const nameRegex = /^[a-zA-Z\s\-']+$/; const validate = () => {
const urlRegex = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/.*)?$/; const newErrors: { [key: string]: string } = {};
const phoneRegex =
/^\+?\d{1,4}?[-.\s]?\(?\d{2,5}\)?[-.\s]?\d{3,5}[-.\s]?\d{3,5}$/;
// Email const nameRegex = /^[a-zA-Z\s\-']+$/;
if (!formData.email.trim()) { const urlRegex = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/.*)?$/;
newErrors.email = "Email is required"; const phoneRegex = /^\d+$/;
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = "Invalid email address";
}
// // First Name // Email
// if (!formData.firstName.trim()) { if (!formData.email.trim()) {
// newErrors.firstName = 'First name is required'; newErrors.email = 'Email is required';
// } else if (!nameRegex.test(formData.firstName)) { } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
// newErrors.firstName = 'First name contains invalid characters'; newErrors.email = 'Invalid email address';
// } }
// Last Name // // First Name
if (!formData.lastName.trim()) { // if (!formData.firstName.trim()) {
newErrors.lastName = "Last name is required"; // newErrors.firstName = 'First name is required';
} // } else if (!nameRegex.test(formData.firstName)) {
// newErrors.firstName = 'First name contains invalid characters';
// }
// // Title // Last Name
// if (!formData.title.trim()) { if (!formData.lastName.trim()) {
// newErrors.title = 'Title is required'; newErrors.lastName = 'Last name is required';
// } } else if (!nameRegex.test(formData.lastName)) {
newErrors.lastName = 'Last name contains invalid characters';
}
// Website (optional but validate if present) // // Title
if (formData.website.trim() && !urlRegex.test(formData.website)) { // if (!formData.title.trim()) {
newErrors.website = "Invalid website URL"; // newErrors.title = 'Title is required';
} // }
// Phone
if (formData.phone.trim() && !phoneRegex.test(formData.phone)) {
newErrors.phone = "Phone number must contain only digits";
}
return newErrors; // Website (optional but validate if present)
}; if (formData.website.trim() && !urlRegex.test(formData.website)) {
newErrors.website = 'Invalid website URL';
}
const handleSubmit = async () => { // Phones
const validationErrors = validate(); // formData.phones.forEach((phone, index) => {
if (Object.keys(validationErrors).length > 0) { // if (!phone.trim()) {
setErrors(validationErrors); // newErrors[`phones_${index}`] = 'Phone number is required';
return; // } else if (!phoneRegex.test(phone)) {
} // newErrors[`phones_${index}`] = 'Phone number must contain only digits';
// }
// });
try { return newErrors;
await onSubmit(formData); };
setFormData({
email: "",
firstName: "",
lastName: "",
title: "",
company: "",
website: "",
phone: "",
});
setErrors({});
} catch (err) {
console.error("Submit failed:", err);
}
};
return ( const handleSubmit = async () => {
<div className="ms-2 space-y-2 max-w-[60vw]"> const validationErrors = validate();
<h2 className="text-lg font-medium text-slate-500">Add Contact</h2> if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
<div className="grid grid-cols-2 gap-3"> try {
{[ await onSubmit(formData);
{ 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> setFormData({
<label className="text-sm font-medium text-[var(--greyTextCol)] mb-1"> email: '',
Phone Numbers firstName: '',
</label> lastName: '',
<div className="space-y-2"> title: '',
{formData.phone.map((phone, index) => ( company: '',
<div key={index} className="flex flex-col gap-1"> website: '',
<div className="flex items-center gap-2"> phones: [''],
<input });
type="text" setErrors({});
value={phone} } catch (err) {
onChange={(e) => handlePhoneChange(index, e.target.value)} console.error('Submit failed:', err);
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 && ( return (
<button <div className="ms-2 space-y-2 max-w-[60vw]">
type="button" <h2 className="text-lg font-medium text-slate-500">Add Contact</h2>
onClick={() => removePhoneField(index)}
className="text-xs text-red-500 hover:underline" <div className="grid grid-cols-2 gap-3">
> {[
Remove { label: 'First Name', field: 'firstName' },
</button> { label: 'Last Name', field: 'lastName' },
)} { label: 'Email', field: 'email' },
</div> { label: 'Title', field: 'title' },
{errors[`phone_${index}`] && ( { label: 'Company', field: 'company' },
<span className="text-sm text-red-500"> { label: 'Website', field: 'website' },
{errors[`phone_${index}`]} ].map(({ label, field }) => (
</span> <div key={field} className="flex flex-col">
)} <label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">{label}</label>
<input
type={field === 'email' ? 'email' : '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>
))}
<button <div>
type="button" <label className="text-sm font-medium text-[var(--greyTextCol)] mb-1">Phone Numbers</label>
onClick={addPhoneField} <div className="space-y-2">
className="text-sm text-blue-500 hover:underline mt-1 cursor-pointer" {formData.phones.map((phone, index) => (
> <div key={index} className="flex flex-col gap-1">
+ Add another phone <div className="flex items-center gap-2">
</button> <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.phones.length > 1 && (
<button
type="button"
onClick={() => removePhoneField(index)}
className="text-xs text-red-500 hover:underline"
>
Remove
</button>
)}
</div>
{errors[`phones_${index}`] && (
<span className="text-sm text-red-500">{errors[`phones_${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-gray-300 px-5 py-2 rounded-md text-slate-700 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> </div>
</div> */} );
<div className="flex justify-end">
<button
type="button"
onClick={handleSubmit}
disabled={loading}
className="bg-gray-300 px-5 py-2 rounded-md text-slate-700 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; export default ContactForm;