feat(CC-085): add Trust References page (contacts with reference/recomandare tags)

This commit is contained in:
admin-valentin 2026-08-02 17:34:54 +00:00
parent 7d782ddf0e
commit 14416eb20e

View file

@ -0,0 +1,126 @@
'use client';
import { useMemo, useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import Link from 'next/link';
import { apiFetch } from '../../../lib/api';
import { useSession } from '../../../components/session-provider';
interface Contact { id: string; firstName: string; lastName: string | null; role: string | null; organization: string | null; tags: string[]; email: string | null; phone: string | null; linkedinUrl: string | null; createdAt: string; }
const REF_TAGS = ['reference', 'referinta', 'referința', 'testimonial', 'recomandare', 'recommendation'];
export default function TrustReferencesPage() {
const { activeTenant } = useSession();
const tenantId = activeTenant?.tenantId ?? '';
const qc = useQueryClient();
const [showAdd, setShowAdd] = useState(false);
const { data: contacts = [], isLoading } = useQuery({
queryKey: ['refs', tenantId],
queryFn: () => apiFetch<Contact[]>('/v1/contacts?limit=500', { tenantId }),
enabled: Boolean(tenantId), staleTime: 60_000,
});
const references = useMemo(() =>
contacts.filter((c) => c.tags.some((t) => REF_TAGS.includes(t.toLowerCase())))
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()),
[contacts]);
const [form, setForm] = useState({ firstName: '', lastName: '', role: '', organization: '', email: '', linkedinUrl: '', note: '' });
const addMut = useMutation({
mutationFn: () => apiFetch('/v1/contacts', { tenantId, method: 'POST', body: {
firstName: form.firstName, lastName: form.lastName || undefined,
role: form.role || undefined, organization: form.organization || undefined,
email: form.email || undefined, linkedinUrl: form.linkedinUrl || undefined,
tags: ['reference', 'recomandare'],
notes: form.note || undefined,
}}),
onSuccess: () => { qc.invalidateQueries({ queryKey: ['refs', tenantId] }); setShowAdd(false); setForm({ firstName: '', lastName: '', role: '', organization: '', email: '', linkedinUrl: '', note: '' }); },
});
return (
<div className="max-w-4xl space-y-6 p-6">
<div className="flex items-start justify-between flex-wrap gap-3">
<div>
<nav className="text-xs text-ink-faint mb-1">
<Link href="/dashboard/trust" className="hover:underline">Trust Dashboard</Link> / Referințe
</nav>
<h1 className="font-display text-2xl font-semibold text-ink">Referințe & Recomandări</h1>
<p className="text-sm text-ink-faint mt-1">
{isLoading ? 'Se încarcă…' : `${references.length} contacte care pot oferi referințe`}
</p>
</div>
<button onClick={() => setShowAdd(true)}
className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white hover:bg-primary/90">
+ Adaugă referință
</button>
</div>
<div className="card p-3 bg-primary/5 border-primary/20">
<p className="text-[10px] text-ink-faint">
Contactele cu tag-ul <code className="bg-muted px-1 rounded">reference</code> sau <code className="bg-muted px-1 rounded">recomandare</code> apar automat.
Poți adăuga tag-ul și din pagina de <Link href="/dashboard/relationships/contacts" className="text-primary hover:underline">Contacte</Link>.
</p>
</div>
{showAdd && (
<div className="card p-5 space-y-3">
<p className="text-sm font-semibold text-ink">Referință nouă</p>
<div className="grid gap-3 sm:grid-cols-2">
<input placeholder="Prenume *" value={form.firstName} onChange={(e) => setForm((p) => ({ ...p, firstName: e.target.value }))}
className="rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" />
<input placeholder="Nume" value={form.lastName} onChange={(e) => setForm((p) => ({ ...p, lastName: e.target.value }))}
className="rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" />
<input placeholder="Rol / Titlu" value={form.role} onChange={(e) => setForm((p) => ({ ...p, role: e.target.value }))}
className="rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" />
<input placeholder="Companie / Organizație" value={form.organization} onChange={(e) => setForm((p) => ({ ...p, organization: e.target.value }))}
className="rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" />
<input placeholder="Email" type="email" value={form.email} onChange={(e) => setForm((p) => ({ ...p, email: e.target.value }))}
className="rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring" />
<input placeholder="LinkedIn URL" value={form.linkedinUrl} onChange={(e) => setForm((p) => ({ ...p, linkedinUrl: e.target.value }))}
className="rounded-lg border bg-background px-3 py-2 text-sm text-ink font-mono text-xs focus:outline-none focus:ring-2 focus:ring-ring" />
<input placeholder="Notă context (în ce context te recomandă)" value={form.note} onChange={(e) => setForm((p) => ({ ...p, note: e.target.value }))}
className="rounded-lg border bg-background px-3 py-2 text-sm text-ink focus:outline-none focus:ring-2 focus:ring-ring sm:col-span-2" />
</div>
<div className="flex gap-2">
<button onClick={() => addMut.mutate()} disabled={!form.firstName || addMut.isPending}
className="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white disabled:opacity-50">
{addMut.isPending ? 'Se salvează…' : 'Salvează'}
</button>
<button onClick={() => setShowAdd(false)} className="rounded-lg border px-4 py-2 text-sm text-ink">Anulează</button>
</div>
</div>
)}
{isLoading ? (
<div className="text-center text-sm text-ink-faint py-8">Se încarcă</div>
) : references.length === 0 ? (
<div className="card p-8 text-center space-y-2">
<p className="text-2xl">📋</p>
<p className="text-sm text-ink-faint">Nicio referință. Adaugă contacte care pot confirma experiența ta.</p>
</div>
) : (
<div className="grid gap-3 sm:grid-cols-2">
{references.map((ref) => (
<div key={ref.id} className="card p-4 space-y-2">
<div className="flex items-start justify-between">
<div>
<p className="text-sm font-semibold text-ink">{ref.firstName} {ref.lastName ?? ''}</p>
{ref.role && <p className="text-[10px] text-ink-faint">{ref.role}</p>}
{ref.organization && <p className="text-[10px] text-ink-faint">{ref.organization}</p>}
</div>
<span className="rounded-full bg-primary/10 px-2 py-0.5 text-[9px] font-bold text-primary">REF</span>
</div>
<div className="flex flex-wrap gap-2 text-[10px] text-ink-faint">
{ref.email && <a href={`mailto:${ref.email}`} className="text-primary hover:underline">{ref.email}</a>}
{ref.linkedinUrl && <a href={ref.linkedinUrl} target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">LinkedIn </a>}
</div>
</div>
))}
</div>
)}
</div>
);
}