From 0361ae7c046984c71a3be23e4078de0b324505e7 Mon Sep 17 00:00:00 2001 From: JaiPandey425 Date: Tue, 29 Jul 2025 13:33:03 +0530 Subject: [PATCH 1/2] contact-add-placeholders --- src/components/setting/ContactForm.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/setting/ContactForm.tsx b/src/components/setting/ContactForm.tsx index ba9b964..920b650 100644 --- a/src/components/setting/ContactForm.tsx +++ b/src/components/setting/ContactForm.tsx @@ -159,6 +159,7 @@ const ContactForm = ({ onSubmit, loading }: Props) => { 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] && {errors[field]}} @@ -177,6 +178,7 @@ const ContactForm = ({ onSubmit, loading }: Props) => { 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 && ( + + +
+ + + + + + + + + + + {loading ? ( + + + + ) : contacts.length === 0 ? ( + + + + ) : ( + contacts.map((contact) => ( + + + + + + + )) + )} + +
First NameLast NameEmailActions
Loading...
No contacts found.
{contact.firstName}{contact.lastName}{contact.email} +
+ + + {openMenuId === contact.id && ( +
+ + +
+ )} +
+
+ {showModal && + + } +
+ + ); +}; + +export default Contacts; diff --git a/src/components/contacts/ContactsFormModal.tsx b/src/components/contacts/ContactsFormModal.tsx new file mode 100644 index 0000000..ea6416a --- /dev/null +++ b/src/components/contacts/ContactsFormModal.tsx @@ -0,0 +1,245 @@ +// 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; + setShowModal: () => React.Dispatch> +} + +const ContactFormModal = ({ onSubmit, loading, setShowModal }: 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" + placeholder={`Enter ${label}`} + /> + {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} + placeholder='Enter Phone Numbers' + /> + {formData.phones.length > 1 && ( + + )} +
+ {errors[`phones_${index}`] && ( + {errors[`phones_${index}`]} + )} +
+ ))} + + +
+
+ +
+ + + + + +
+
+
+
+ ); +}; + +export default ContactFormModal; diff --git a/src/components/dashboard/SideBar.tsx b/src/components/dashboard/SideBar.tsx index 8c7b7a6..398e3bd 100644 --- a/src/components/dashboard/SideBar.tsx +++ b/src/components/dashboard/SideBar.tsx @@ -10,7 +10,7 @@ import { IoSettingsOutline } from "react-icons/io5"; import axios from 'axios'; import { MdManageAccounts } from 'react-icons/md'; -import { GrTransaction } from 'react-icons/gr'; +import { GrContactInfo, GrTransaction } from 'react-icons/gr'; type SideBarProps = { showSideBar: boolean; @@ -66,6 +66,7 @@ const SideBar: React.FC = ({ showSideBar, setShowSideBar }) => { } label="Templates" showSideBar={showSideBar} menuPathMap={menuPathMap} /> {/* } label="Accounts" showSideBar={showSideBar} menuPathMap={menuPathMap} /> */} } label="Transactions" showSideBar={showSideBar} menuPathMap={menuPathMap} /> + } label="Contacts" showSideBar={showSideBar} menuPathMap={menuPathMap} /> diff --git a/src/components/setting/Setting.tsx b/src/components/setting/Setting.tsx index ff104e2..d53aded 100644 --- a/src/components/setting/Setting.tsx +++ b/src/components/setting/Setting.tsx @@ -392,7 +392,7 @@ const Setting = () => { const handleContactSubmit = async (data: ContactData) => { try { setCreateAndInviteLoader(true); - const res = await api.post(`/api/accounts/${activeAccountId}/contacts`, [data]); // Adjust endpoint if different + const res = await api.post(`/api/accounts/${activeAccountId}/contacts`, [data]); if (res.status === 200) { toast.success('Contact created successfully!', { position: "top-center",