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>
This commit is contained in:
54
apps/web/components/inquiries/inquiry-row.tsx
Normal file
54
apps/web/components/inquiries/inquiry-row.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import type { InquiryReadDto } from '@/lib/inquiries-api';
|
||||
|
||||
interface InquiryStatusBadgeProps {
|
||||
isRead: boolean;
|
||||
}
|
||||
|
||||
export function InquiryStatusBadge({ isRead }: InquiryStatusBadgeProps) {
|
||||
if (isRead) {
|
||||
return <Badge variant="secondary">Đã đọc</Badge>;
|
||||
}
|
||||
return <Badge variant="info">Chưa đọc</Badge>;
|
||||
}
|
||||
|
||||
interface InquiryRowProps {
|
||||
inquiry: InquiryReadDto;
|
||||
onSelect: (inquiry: InquiryReadDto) => void;
|
||||
}
|
||||
|
||||
export function InquiryRow({ inquiry, onSelect }: InquiryRowProps) {
|
||||
return (
|
||||
<tr
|
||||
className="border-b last:border-0 cursor-pointer transition-colors hover:bg-accent/50"
|
||||
onClick={() => onSelect(inquiry)}
|
||||
>
|
||||
<td className="p-3">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<span className="font-medium">{inquiry.userName}</span>
|
||||
<span className="text-xs text-muted-foreground">{inquiry.userPhone}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<span className="line-clamp-1 text-sm text-muted-foreground">
|
||||
{inquiry.listingTitle}
|
||||
</span>
|
||||
</td>
|
||||
<td className="hidden p-3 sm:table-cell">
|
||||
<span className="line-clamp-2 text-sm">{inquiry.message}</span>
|
||||
</td>
|
||||
<td className="p-3 text-center">
|
||||
<InquiryStatusBadge isRead={inquiry.isRead} />
|
||||
</td>
|
||||
<td className="p-3 text-right text-xs text-muted-foreground">
|
||||
{new Date(inquiry.createdAt).toLocaleDateString('vi-VN', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user