← back to Professional Directory
agents/web-agent/app/page.tsx
91 lines
// Homepage — search hero + ratio ribbon + grid of recently-vouched orgs.
// Hits pd-api on loopback for the live data.
const PD_API = process.env.PD_API_BASE || 'http://127.0.0.1:9874';
async function fetchJson(path: string) {
try {
const r = await fetch(`${PD_API}${path}`, { next: { revalidate: 300 }, headers: { Accept: 'application/json' } });
if (!r.ok) return null;
return await r.json();
} catch { return null; }
}
async function getCounts() {
return fetchJson('/health');
}
async function getRecentOrgs() {
return fetchJson('/organizations?has_email=true&limit=12&sort=score');
}
export default async function HomePage() {
const [health, orgs] = await Promise.all([getCounts(), getRecentOrgs()]);
const c = health?.counts || {};
const orgRows: any[] = orgs?.rows || [];
return (
<>
<section className="hero">
<div className="eyebrow">Patient-vouched LA healthcare</div>
<h1>Find a doctor your community vouches for.</h1>
<p className="lead">
Real reviews from Angelenos. Real ratings on service, price, and quality.
No algorithm games. {Number(c.organizations || 0).toLocaleString()} clinics, hospitals, and practices.
</p>
<form className="search-shell" action="/search" method="GET">
<input type="text" name="q" placeholder="Search doctor, specialty, or city" autoFocus />
<button type="submit">Search</button>
</form>
<div className="stat-ribbon">
<div className="stat"><b>{Number(c.professionals || 0).toLocaleString()}</b>Professionals listed</div>
<div className="stat"><b>{Number(c.organizations || 0).toLocaleString()}</b>Practices & facilities</div>
<div className="stat"><b>{Number(c.specialties || 0).toLocaleString()}</b>Specialties covered</div>
<div className="stat"><b>LA</b>County, every ZIP</div>
</div>
</section>
<section className="section">
<div className="eyebrow">Recently active</div>
<h2>Practices people are talking about</h2>
{orgRows.length === 0 ? (
<p style={{ color: 'var(--ink-soft)' }}>No data yet — pd-api may be still warming up.</p>
) : (
<div className="card-grid">
{orgRows.map((o) => (
<a key={o.id} href={`/orgs/${o.id}`} className="card">
<span className="badge">{titleCase(o.type)}</span>
<span className="name">{titleCase(o.name)}</span>
<span className="meta">
{[titleCase(o.city || ''), o.zip].filter(Boolean).join(' · ')}
{o.has_email ? ' · ✉ on file' : ''}
</span>
<span className="stars">★★★★☆</span>
</a>
))}
</div>
)}
</section>
<section className="section" style={{ borderTop: '1px solid var(--line)' }}>
<div className="eyebrow">For practitioners</div>
<h2>Claim your practice. Respond to patients. Edit your page.</h2>
<p style={{ maxWidth: 580, color: 'var(--ink-soft)' }}>
drvouch automatically builds a draft profile for every licensed practice in LA.
Doctors and office managers can claim theirs in 60 seconds (NPI + practice email)
to respond to reviews, message patients directly, and customize the page content.
</p>
<div style={{ display: 'flex', gap: 12, marginTop: 18, flexWrap: 'wrap' }}>
<a href="/account/claim" className="btn-primary" style={{ background: 'var(--moss)', color: '#fff', padding: '12px 22px', borderRadius: 999, fontWeight: 600, fontSize: 14, letterSpacing: '0.04em', textTransform: 'uppercase' }}>Claim a practice</a>
<a href="/community" style={{ padding: '12px 22px', borderBottom: '1px solid var(--ink)', fontWeight: 600, fontSize: 14, letterSpacing: '0.04em', textTransform: 'uppercase' }}>Community →</a>
</div>
</section>
</>
);
}
function titleCase(s: string) {
return String(s || '').toLowerCase().replace(/\b\w/g, c => c.toUpperCase());
}