Download frontend/src/components/workspace/AddContactForm.jsx from Seth0330/EMAILOUT: direct link, hf CLI and curl.
- Browser
- Download file 4.65 kB
-
https://huggingface.co/spaces/Seth0330/EMAILOUT/resolve/main/frontend/src/components/workspace/AddContactForm.jsx
- Command line
-
hf download hf://spaces/Seth0330/EMAILOUT/frontend/src/components/workspace/AddContactForm.jsx
-
curl -L -o AddContactForm.jsx https://huggingface.co/spaces/Seth0330/EMAILOUT/resolve/main/frontend/src/components/workspace/AddContactForm.jsx
4.65 kB
| import React, { useState } from 'react'; | |
| import { Loader2 } from 'lucide-react'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Input } from '@/components/ui/input'; | |
| import { apiFetch } from '@/lib/api'; | |
| const EMPTY_DRAFT = { first_name: '', last_name: '', email: '', title: '', phone: '' }; | |
| /** | |
| * "Add a contact" mini-form for the associated-contacts section of a company-scoped detail | |
| * panel (Companies / Leads / Deals). Creates the contact via the generic POST /api/contacts | |
| * route with `company` locked to `companyName` — unlike the Companies page's own | |
| * /api/companies/{id}/contacts route, this doesn't require an existing Company row, so it | |
| * works even when the lead/deal's company hasn't synced into one yet. | |
| */ | |
| export default function AddContactForm({ companyName, onAdded, className }) { | |
| const [draft, setDraft] = useState(EMPTY_DRAFT); | |
| const [saving, setSaving] = useState(false); | |
| const [error, setError] = useState(''); | |
| const setField = (key, value) => setDraft((d) => ({ ...d, [key]: value })); | |
| const save = async () => { | |
| const email = draft.email.trim(); | |
| if (!email) { | |
| setError('Email is required'); | |
| return; | |
| } | |
| if (!email.includes('@')) { | |
| setError('Enter a valid email address'); | |
| return; | |
| } | |
| setSaving(true); | |
| setError(''); | |
| try { | |
| const res = await apiFetch('/api/contacts', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| first_name: draft.first_name.trim(), | |
| last_name: draft.last_name.trim(), | |
| email, | |
| title: draft.title.trim(), | |
| phone: draft.phone.trim(), | |
| company: companyName, | |
| }), | |
| }); | |
| const data = await res.json().catch(() => ({})); | |
| if (!res.ok) { | |
| setError(typeof data.detail === 'string' ? data.detail : 'Could not save contact'); | |
| return; | |
| } | |
| setDraft(EMPTY_DRAFT); | |
| onAdded && onAdded(data); | |
| } catch (e) { | |
| console.error(e); | |
| setError('Network error'); | |
| } finally { | |
| setSaving(false); | |
| } | |
| }; | |
| return ( | |
| <div className={className || 'rounded-xl border border-slate-200 bg-slate-50/50 p-4'}> | |
| <h5 className="mb-3 text-sm font-semibold text-slate-700">Add a contact</h5> | |
| <div className="grid grid-cols-1 gap-2 sm:grid-cols-2"> | |
| <Input | |
| className="bg-white text-sm" | |
| placeholder="First name" | |
| value={draft.first_name} | |
| onChange={(e) => setField('first_name', e.target.value)} | |
| disabled={saving} | |
| /> | |
| <Input | |
| className="bg-white text-sm" | |
| placeholder="Last name" | |
| value={draft.last_name} | |
| onChange={(e) => setField('last_name', e.target.value)} | |
| disabled={saving} | |
| /> | |
| <Input | |
| className="bg-white text-sm sm:col-span-2" | |
| type="email" | |
| placeholder="Email" | |
| value={draft.email} | |
| onChange={(e) => setField('email', e.target.value)} | |
| disabled={saving} | |
| /> | |
| <Input | |
| className="bg-white text-sm" | |
| placeholder="Title" | |
| value={draft.title} | |
| onChange={(e) => setField('title', e.target.value)} | |
| disabled={saving} | |
| /> | |
| <Input | |
| className="bg-white text-sm" | |
| type="tel" | |
| placeholder="Contact number" | |
| value={draft.phone} | |
| onChange={(e) => setField('phone', e.target.value)} | |
| disabled={saving} | |
| /> | |
| </div> | |
| {error ? <p className="mt-2 text-xs text-red-600">{error}</p> : null} | |
| <Button type="button" className="mt-3 w-full" onClick={save} disabled={saving}> | |
| {saving ? ( | |
| <> | |
| <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden /> | |
| Saving… | |
| </> | |
| ) : ( | |
| 'Add contact' | |
| )} | |
| </Button> | |
| </div> | |
| ); | |
| } | |