import React, { useEffect, useState } from 'react'; import { KeyRound, Pencil, Trash2 } from 'lucide-react'; import { Alert, Button, Field, Input, Modal, Switch } from '@/components/ds'; import { inputVariants } from '@/components/ui/input'; import { useDeleteOnboardedClient, useUpdateOnboardedClient } from '@/lib/doormileHooks'; import AddressAutocomplete from '@/components/doormile/AddressAutocomplete'; import { addressFromPlace, addressPayload, generateClientPassword, validateAddress } from '@/lib/clientOnboarding'; /** * Edit and Remove for one onboarded client login (a row of "Clients with a * console login"). Both call owner-only endpoints: PUT and DELETE * /admin/clients/:authid. */ const STATUSES = ['Active', 'Pending', 'Inactive']; /** Only the fields that actually changed, so an edit never rewrites the rest. */ export function changedFields(row, form) { const out = {}; const trim = (v) => String(v ?? '').trim(); if (trim(form.companyname) !== trim(row.tenantname)) out.companyname = trim(form.companyname); if (trim(form.contactname) !== trim(row.contactname)) out.contactname = trim(form.contactname); if (trim(form.email).toLowerCase() !== trim(row.loginemail).toLowerCase()) out.email = trim(form.email).toLowerCase(); if (form.phone.replace(/\D/g, '') !== String(row.primarycontact || '').replace(/\D/g, '')) out.phone = form.phone.replace(/\D/g, ''); if (form.status !== row.status) out.status = form.status; if (Boolean(form.requiredeliveryotp) !== Boolean(row.requiredeliveryotp)) out.requiredeliveryotp = Boolean(form.requiredeliveryotp); if (form.password) out.password = form.password; // The address goes as one `location` object, and only when any part of it // changed (a coordinate change alone counts: re-picking the same text moves // the pin). const addr = addressPayload(form); const before = { address: trim(row.address), city: trim(row.city), state: trim(row.state), pincode: String(row.pincode || ''), latitude: Number(row.latitude) || 0, longitude: Number(row.longitude) || 0, }; if ( addr.address !== before.address || addr.pincode !== before.pincode || addr.city !== before.city || addr.state !== before.state || (Number(addr.latitude) || 0) !== before.latitude || (Number(addr.longitude) || 0) !== before.longitude ) { out.location = addr; } return out; } export function validateEdit(form, changes) { const errors = {}; if ('companyname' in changes && changes.companyname.length < 2) errors.companyname = 'Enter the company name'; if ('contactname' in changes && changes.contactname.length < 2) errors.contactname = "Enter the contact person's name"; if ('email' in changes && !/^[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+$/.test(changes.email)) errors.email = 'Enter a valid email address'; if ('phone' in changes && !/^[6-9]\d{9}$/.test(changes.phone)) errors.phone = 'Enter a 10-digit mobile number'; if (form.password && form.password.length < 8) errors.password = 'At least 8 characters'; if (form.password && form.password.length > 72) errors.password = 'At most 72 characters'; if ('location' in changes) Object.assign(errors, validateAddress(form)); return errors; } function formFrom(row) { return { companyname: row?.tenantname || '', contactname: row?.contactname || '', email: row?.loginemail || '', phone: String(row?.primarycontact || '').replace(/\D/g, ''), status: STATUSES.includes(row?.status) ? row.status : 'Active', requiredeliveryotp: Boolean(row?.requiredeliveryotp), password: '', address: row?.address || '', city: row?.city || '', state: row?.state || '', pincode: row?.pincode || '', latitude: row?.latitude || '', longitude: row?.longitude || '', }; } export function EditClientModal({ row, onClose }) { const update = useUpdateOnboardedClient(); const [form, setForm] = useState(formFrom(row)); const [errors, setErrors] = useState({}); const [serverError, setServerError] = useState(''); const [showPassword, setShowPassword] = useState(false); useEffect(() => { setForm(formFrom(row)); setErrors({}); setServerError(''); setShowPassword(false); }, [row]); const set = (key) => (value) => { setForm((f) => ({ ...f, [key]: value })); setErrors((e) => ({ ...e, [key]: undefined })); }; const changes = row ? changedFields(row, form) : {}; const nothingChanged = Object.keys(changes).length === 0; const save = () => { const nextErrors = validateEdit(form, changes); setErrors(nextErrors); setServerError(''); if (Object.keys(nextErrors).length || nothingChanged) return; update.mutate( { authid: row.authid, data: changes }, { onSuccess: (res) => { if (res?.success !== false) onClose(); }, onError: (err) => setServerError(err?.response?.data?.message || 'The change was not saved.'), } ); }; return ( !open && onClose()} title={`Edit ${row?.tenantname || 'client'}`} description="Changes the client and its console login together." icon={Pencil} busy={update.isPending} footer={ <> } > {serverError && ( {serverError} )}
set('companyname')(e.target.value)} /> set('contactname')(e.target.value)} /> set('phone')(e.target.value.replace(/\D/g, '').slice(0, 10))} /> set('email')(e.target.value)} /> set('password')(e.target.value)} />
{form.password && ( )}
set('address')(text)} onPlaceSelected={(place) => { setForm((f) => ({ ...f, ...addressFromPlace(place) })); setErrors((e) => ({ ...e, address: undefined, pincode: undefined })); }} onPlaceCleared={() => setForm((f) => ({ ...f, latitude: '', longitude: '' }))} /> {errors.address ? (

{errors.address}

) : ( !row?.address && (

No address on file yet. Add one so this client has a pickup point and zone.

) )}
set('pincode')(e.target.value.replace(/\D/g, '').slice(0, 6))} /> set('city')(e.target.value)} />
); } export function RemoveClientModal({ row, onClose }) { const remove = useDeleteOnboardedClient(); const [typed, setTyped] = useState(''); const [serverError, setServerError] = useState(''); useEffect(() => { setTyped(''); setServerError(''); }, [row]); const confirmed = row && typed.trim().toLowerCase() === String(row.tenantname).trim().toLowerCase(); const submit = () => { if (!confirmed) return; remove.mutate(row.authid, { onSuccess: (res) => { if (res?.success !== false) onClose(); }, onError: (err) => setServerError(err?.response?.data?.message || 'The login was not removed.'), }); }; return ( !open && onClose()} title={`Remove ${row?.tenantname || 'client'}'s login`} icon={Trash2} busy={remove.isPending} footer={ <> } > {serverError && ( {serverError} )}

{row?.loginemail} will no longer be able to sign in to the console.

The client's orders, customers and pricing are kept. If this is its last login, the client is marked{' '} Inactive. A session already signed in ends when its token expires.

setTyped(e.target.value)} autoComplete="off" />
); }