| import React, { useEffect, useState, useRef } from 'react'; |
| import { Loader2 } from 'lucide-react'; |
| import { Button } from '@/components/ui/button'; |
| import { Input } from '@/components/ui/input'; |
| import { cn } from '@/lib/utils'; |
|
|
| |
| function strField(v) { |
| return String(v ?? '').trim(); |
| } |
|
|
| function baselineFromProps(firstName, lastName, email, title) { |
| return { |
| firstName: String(firstName ?? ''), |
| lastName: String(lastName ?? ''), |
| email: String(email ?? ''), |
| title: String(title ?? ''), |
| }; |
| } |
|
|
| function formsEqual(a, b) { |
| return ( |
| strField(a.firstName) === strField(b.firstName) && |
| strField(a.lastName) === strField(b.lastName) && |
| strField(a.email) === strField(b.email) && |
| strField(a.title) === strField(b.title) |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| export default function ContactIdentityEditor({ |
| firstName = '', |
| lastName = '', |
| email = '', |
| title = '', |
| onSave, |
| disabled = false, |
| className, |
| heading = 'Contact details', |
| autoSave = false, |
| }) { |
| const [form, setForm] = useState(() => baselineFromProps(firstName, lastName, email, title)); |
| const [saving, setSaving] = useState(false); |
| const baselineRef = useRef(baselineFromProps(firstName, lastName, email, title)); |
|
|
| useEffect(() => { |
| const b = baselineFromProps(firstName, lastName, email, title); |
| baselineRef.current = b; |
| setForm(b); |
| }, [firstName, lastName, email, title]); |
|
|
| const setField = (key, value) => setForm((f) => ({ ...f, [key]: value })); |
|
|
| const handleSave = async () => { |
| if (!onSave || disabled) return; |
| setSaving(true); |
| try { |
| await onSave({ |
| first_name: form.firstName, |
| last_name: form.lastName, |
| email: form.email, |
| title: form.title, |
| }); |
| } finally { |
| setSaving(false); |
| } |
| }; |
|
|
| useEffect(() => { |
| if (!autoSave || !onSave || disabled) return; |
| const baseline = baselineRef.current; |
| if (formsEqual(form, baseline)) return; |
| const t = setTimeout(async () => { |
| setSaving(true); |
| try { |
| await onSave({ |
| first_name: form.firstName, |
| last_name: form.lastName, |
| email: form.email, |
| title: form.title, |
| }); |
| baselineRef.current = { |
| firstName: form.firstName, |
| lastName: form.lastName, |
| email: form.email, |
| title: form.title, |
| }; |
| } finally { |
| setSaving(false); |
| } |
| }, 650); |
| return () => clearTimeout(t); |
| }, [form, autoSave, disabled, onSave, firstName, lastName, email, title]); |
|
|
| return ( |
| <div className={cn('rounded-xl border border-slate-200 bg-white p-4 mb-6', className)}> |
| <div className="mb-3 flex items-center justify-between gap-2"> |
| <h4 className="text-sm font-semibold text-slate-800">{heading}</h4> |
| {autoSave && saving ? ( |
| <span className="flex items-center gap-1 text-xs text-slate-500"> |
| <Loader2 className="h-3 w-3 animate-spin shrink-0" aria-hidden /> |
| Saving… |
| </span> |
| ) : null} |
| </div> |
| <div className="grid grid-cols-1 gap-3 text-sm sm:grid-cols-2"> |
| <div> |
| <label className="mb-1 block text-xs font-medium text-slate-500">First name</label> |
| <Input |
| value={form.firstName} |
| onChange={(e) => setField('firstName', e.target.value)} |
| disabled={disabled} |
| className="text-sm" |
| /> |
| </div> |
| <div> |
| <label className="mb-1 block text-xs font-medium text-slate-500">Last name</label> |
| <Input |
| value={form.lastName} |
| onChange={(e) => setField('lastName', e.target.value)} |
| disabled={disabled} |
| className="text-sm" |
| /> |
| </div> |
| <div className="sm:col-span-2"> |
| <label className="mb-1 block text-xs font-medium text-slate-500">Email</label> |
| <Input |
| type="email" |
| value={form.email} |
| onChange={(e) => setField('email', e.target.value)} |
| disabled={disabled} |
| className="text-sm" |
| /> |
| </div> |
| <div className="sm:col-span-2"> |
| <label className="mb-1 block text-xs font-medium text-slate-500">Title</label> |
| <Input |
| value={form.title} |
| onChange={(e) => setField('title', e.target.value)} |
| disabled={disabled} |
| className="text-sm" |
| /> |
| </div> |
| </div> |
| {!autoSave ? ( |
| <div className="mt-4"> |
| <Button |
| type="button" |
| className="w-full sm:w-auto" |
| onClick={handleSave} |
| disabled={disabled || saving} |
| > |
| {saving ? ( |
| <> |
| <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden /> |
| Saving… |
| </> |
| ) : ( |
| 'Save' |
| )} |
| </Button> |
| </div> |
| ) : null} |
| </div> |
| ); |
| } |
|
|