Files
goodgo-platform/apps/web/lib/leads-api.ts
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

101 lines
3.2 KiB
TypeScript

import { apiClient } from './api-client';
// ─── Types ──────────────────────────────────────────────
export type LeadStatus = 'NEW' | 'CONTACTED' | 'QUALIFIED' | 'NEGOTIATING' | 'CONVERTED' | 'LOST';
export interface LeadReadDto {
id: string;
agentId: string;
name: string;
phone: string;
email: string | null;
source: string;
score: number | null;
notes: Record<string, unknown> | null;
status: LeadStatus;
createdAt: string;
updatedAt: string;
}
export interface LeadStatsData {
totalLeads: number;
byStatus: Record<string, number>;
conversionRate: number;
avgScore: number | null;
}
export interface PaginatedResult<T> {
data: T[];
total: number;
page: number;
limit: number;
totalPages: number;
}
export interface ListLeadsParams {
status?: LeadStatus;
page?: number;
limit?: number;
}
export interface CreateLeadPayload {
name: string;
phone: string;
email?: string;
source: string;
score?: number;
notes?: Record<string, unknown>;
}
// ─── Constants ──────────────────────────────────────────
export const LEAD_STATUSES: Record<LeadStatus, { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'warning' | 'info' }> = {
NEW: { label: 'Mới', variant: 'info' },
CONTACTED: { label: 'Đã liên hệ', variant: 'secondary' },
QUALIFIED: { label: 'Đủ điều kiện', variant: 'warning' },
NEGOTIATING: { label: 'Đang thương lượng', variant: 'default' },
CONVERTED: { label: 'Chuyển đổi', variant: 'success' },
LOST: { label: 'Mất', variant: 'destructive' },
};
export const LEAD_SOURCES = [
{ value: 'website', label: 'Website' },
{ value: 'referral', label: 'Giới thiệu' },
{ value: 'social', label: 'Mạng xã hội' },
{ value: 'phone', label: 'Điện thoại' },
{ value: 'walk_in', label: 'Đến trực tiếp' },
{ value: 'other', label: 'Khác' },
] as const;
// ─── API Functions ──────────────────────────────────────
export const leadsApi = {
/** Create a new lead */
create: (data: CreateLeadPayload) =>
apiClient.post<{ leadId: string }>('/leads', data),
/** List leads for current agent */
getLeads: (params: ListLeadsParams = {}) => {
const query = new URLSearchParams();
if (params.status) query.append('status', params.status);
if (params.page) query.append('page', String(params.page));
if (params.limit) query.append('limit', String(params.limit));
const qs = query.toString();
return apiClient.get<PaginatedResult<LeadReadDto>>(
`/leads${qs ? `?${qs}` : ''}`,
);
},
/** Get lead statistics */
getStats: () => apiClient.get<LeadStatsData>('/leads/stats'),
/** Update lead status */
updateStatus: (id: string, status: LeadStatus) =>
apiClient.patch<{ updated: boolean }>(`/leads/${id}/status`, { status }),
/** Delete a lead */
delete: (id: string) =>
apiClient.delete<{ deleted: boolean }>(`/leads/${id}`),
};