diff --git a/src/components/setting/ContactForm.tsx b/src/components/setting/ContactForm.tsx new file mode 100644 index 0000000..ba9b964 --- /dev/null +++ b/src/components/setting/ContactForm.tsx @@ -0,0 +1,228 @@ +// 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; + phones: string[]; +} + +interface Props { + onSubmit: (data: ContactData) => void; + loading: boolean; +} + +const ContactForm = ({ onSubmit, loading }: Props) => { + const [formData, setFormData] = useState({ + email: '', + firstName: '', + lastName: '', + title: '', + company: '', + website: '', + phones: [''], + }); + + 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 updatedPhones = [...formData.phones]; + updatedPhones[index] = numericValue; + setFormData((prev) => ({ ...prev, phones: updatedPhones })); + + const errorKey = `phones_${index}`; + if (errors[errorKey]) { + setErrors((prev) => ({ ...prev, [errorKey]: '' })); + } + }; + + + const addPhoneField = () => { + setFormData((prev) => ({ ...prev, phones: [...prev.phones, ''] })); + }; + + const removePhoneField = (index: number) => { + const updatedPhones = formData.phones.filter((_, i) => i !== index); + setFormData((prev) => ({ ...prev, phones: updatedPhones })); + }; + + const validate = () => { + const newErrors: { [key: string]: string } = {}; + + const nameRegex = /^[a-zA-Z\s\-']+$/; + const urlRegex = /^(https?:\/\/)?([\w-]+\.)+[\w-]{2,}(\/.*)?$/; + const phoneRegex = /^\d+$/; + + // 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'; + } else if (!nameRegex.test(formData.lastName)) { + newErrors.lastName = 'Last name contains invalid characters'; + } + + // // 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'; + } + + // Phones + // formData.phones.forEach((phone, index) => { + // if (!phone.trim()) { + // newErrors[`phones_${index}`] = 'Phone number is required'; + // } else if (!phoneRegex.test(phone)) { + // newErrors[`phones_${index}`] = '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: '', + phones: [''], + }); + setErrors({}); + } catch (err) { + console.error('Submit failed:', err); + } + }; + + + return ( +
+

Add Contact

+ +
+ {[ + { 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 }) => ( +
+ + 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" + /> + {errors[field] && {errors[field]}} +
+ ))} +
+ +
+ +
+ {formData.phones.map((phone, index) => ( +
+
+ 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} + /> + {formData.phones.length > 1 && ( + + )} +
+ {errors[`phones_${index}`] && ( + {errors[`phones_${index}`]} + )} +
+ ))} + + +
+
+ +
+ +
+
+ ); +}; + +export default ContactForm; diff --git a/src/components/setting/Setting.tsx b/src/components/setting/Setting.tsx index 623cc53..ff104e2 100644 --- a/src/components/setting/Setting.tsx +++ b/src/components/setting/Setting.tsx @@ -9,6 +9,7 @@ import AsideSection from "./AsideSection"; import Signature from "./Signature/Signature"; import { Bounce, toast } from "react-toastify"; import AccountNav from "../accounts/AccountNav"; +import ContactForm , {ContactData} from "./ContactForm"; // Define the shape of state object interface Account { @@ -57,7 +58,7 @@ const Setting = () => { const [activeTab, setActiveTab] = useState("userProfile"); const [activeSubSection, setActiveSubSection] = useState(""); - const [user, setUser] = useState({ + const [user, setUser] = useState({ id: '', name: '', email: '', @@ -212,6 +213,13 @@ const Setting = () => { accountOptionsLoading={accountOptionsLoading} /> ); + case "contacts": + return ( + + ); default: return
Coming Soon...
; } @@ -236,6 +244,7 @@ const Setting = () => { const tabs = [ { id: "userProfile", label: "User Profile", type: "settings" }, { id: "create and invite", label: "Create and invite", type: "settings" }, + { id: "contacts", label: "Contacts", type: "settings" }, { id: "language", label: "Language", type: "settings" }, { id: "contactAdd", label: "Add Contact", type: "contact" }, { id: "contactRemove", label: "Remove Contact", type: "contact" }, @@ -379,6 +388,28 @@ const Setting = () => { } }; + + const handleContactSubmit = async (data: ContactData) => { + try { + setCreateAndInviteLoader(true); + const res = await api.post(`/api/accounts/${activeAccountId}/contacts`, [data]); // Adjust endpoint if different + if (res.status === 200) { + toast.success('Contact created successfully!', { + position: "top-center", + autoClose: 1500, + transition: Bounce, + }); + } + // return res; + } catch (err) { + console.error("Error creating contact", err); + toast.error("Failed to create contact"); + throw err; + } finally { + setCreateAndInviteLoader(false); + } + }; + return ( <> {/* */}