Files
goodgo-platform/apps/web/components/leads/create-lead-dialog.tsx
Ho Ngoc Hai 1fbe2f4e73 feat: add MFA/TOTP auth, PII encryption, agents/leads/inquiries modules, and comprehensive tests
- Add TOTP-based MFA with setup, verify, disable, backup codes, and challenge flow
- Add PII field encryption middleware with AES-256-GCM and deterministic search hashes
- Add agents, inquiries, and leads domain modules with entities, events, value objects
- Add web dashboard pages for inquiries and leads with detail dialogs
- Add 30+ component tests (valuation, charts, listings, search, providers, UI)
- Add Prisma migrations for encryption hash columns and MFA TOTP support
- Fix all ESLint errors (unused imports, duplicate imports, lint auto-fixes)
- Update dependencies and lock file
- Clean up obsolete exploration/QA docs, add audit documentation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-11 23:43:20 +07:00

154 lines
4.8 KiB
TypeScript

'use client';
import * as React from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select } from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { useCreateLead } from '@/lib/hooks/use-leads';
import { LEAD_SOURCES } from '@/lib/leads-api';
interface CreateLeadDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function CreateLeadDialog({ open, onOpenChange }: CreateLeadDialogProps) {
const createLead = useCreateLead();
const [form, setForm] = React.useState({
name: '',
phone: '',
email: '',
source: 'website',
score: '',
notes: '',
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
createLead.mutate(
{
name: form.name,
phone: form.phone,
email: form.email || undefined,
source: form.source,
score: form.score ? Number(form.score) : undefined,
notes: form.notes ? { text: form.notes } : undefined,
},
{
onSuccess: () => {
setForm({ name: '', phone: '', email: '', source: 'website', score: '', notes: '' });
onOpenChange(false);
},
},
);
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-md sm:max-w-lg">
<DialogHeader>
<DialogTitle>Thêm lead mới</DialogTitle>
<DialogDescription>
Nhập thông tin khách hàng tiềm năng
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4 py-2">
<div className="space-y-2">
<Label htmlFor="lead-name">Tên khách hàng *</Label>
<Input
id="lead-name"
value={form.name}
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
placeholder="Nguyễn Văn A"
required
/>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="lead-phone">Số điện thoại *</Label>
<Input
id="lead-phone"
value={form.phone}
onChange={(e) => setForm((f) => ({ ...f, phone: e.target.value }))}
placeholder="0901234567"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="lead-email">Email</Label>
<Input
id="lead-email"
type="email"
value={form.email}
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
placeholder="email@example.com"
/>
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="lead-source">Nguồn</Label>
<Select
id="lead-source"
value={form.source}
onChange={(e) => setForm((f) => ({ ...f, source: e.target.value }))}
>
{LEAD_SOURCES.map((s) => (
<option key={s.value} value={s.value}>
{s.label}
</option>
))}
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="lead-score">Điểm (0-100)</Label>
<Input
id="lead-score"
type="number"
min={0}
max={100}
value={form.score}
onChange={(e) => setForm((f) => ({ ...f, score: e.target.value }))}
placeholder="75"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="lead-notes">Ghi chú</Label>
<Textarea
id="lead-notes"
value={form.notes}
onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}
placeholder="Thông tin bổ sung về khách hàng..."
rows={3}
/>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
Hủy
</Button>
<Button type="submit" disabled={createLead.isPending}>
{createLead.isPending ? 'Đang tạo...' : 'Tạo lead'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}