← back to Homesonspec

apps/web/src/app/builders/page.tsx

107 lines

import Link from "next/link";
import { prisma } from "@homesonspec/database";

export const dynamic = "force-dynamic";

export const metadata = {
  title: "Home Builders Directory | HomesOnSpec",
  description:
    "Home builders indexed on HomesOnSpec, with each builder's coverage area, website, and live verified inventory count. New builders added continuously.",
};

export default async function BuildersDirectoryPage() {
  const builders = await prisma.builder.findMany({
    where: { isDemo: false },
    include: {
      _count: {
        select: {
          communities: true,
          homes: { where: { status: "PUBLISHED", isDemo: false } },
        },
      },
    },
  });

  const rows = builders
    .map((b) => ({
      slug: b.slug,
      name: b.name,
      website: b.websiteUrl,
      coverage: b.coverageArea,
      communities: b._count.communities,
      homeCount: b._count.homes,
    }))
    .sort((a, b) => b.homeCount - a.homeCount || a.name.localeCompare(b.name));

  const totalHomes = rows.reduce((s, r) => s + r.homeCount, 0);
  const withInventory = rows.filter((r) => r.homeCount > 0).length;

  return (
    <div className="mx-auto max-w-6xl px-4 py-8">
      <h1 className="text-3xl font-bold">Home Builders Directory</h1>
      <p className="mt-2 text-neutral-600">
        Every builder on HomesOnSpec — national, regional, and local. {rows.length} builders ·{" "}
        {withInventory} with live inventory · {totalHomes.toLocaleString()} verified homes.
      </p>
      <p className="mt-1 text-xs text-neutral-400">
        Listing does not imply sponsorship or endorsement by the builder. Inventory is verified from each
        builder&apos;s own website.
      </p>

      <div className="mt-6 overflow-x-auto rounded-xl border border-neutral-200">
        <table className="w-full text-left text-sm">
          <thead className="border-b border-neutral-200 bg-neutral-50 text-xs uppercase tracking-wide text-neutral-500">
            <tr>
              <th className="px-4 py-3">Builder</th>
              <th className="px-4 py-3">Coverage area</th>
              <th className="px-4 py-3">Website</th>
              <th className="px-4 py-3 text-right">Homes</th>
            </tr>
          </thead>
          <tbody>
            {rows.map((r) => (
              <tr key={r.slug} className="border-b border-neutral-100 hover:bg-neutral-50">
                <td className="px-4 py-3">
                  <Link href={`/builders/${r.slug}`} className="font-medium text-teal-800 hover:underline">
                    {r.name}
                  </Link>
                  {r.communities > 0 && (
                    <span className="ml-2 text-xs text-neutral-400">{r.communities} communities</span>
                  )}
                </td>
                <td className="px-4 py-3 text-neutral-600">{r.coverage ?? "—"}</td>
                <td className="px-4 py-3">
                  {r.website ? (
                    <a
                      href={r.website}
                      target="_blank"
                      rel="nofollow noopener noreferrer"
                      className="text-teal-700 hover:underline"
                    >
                      {r.website.replace(/^https?:\/\/(www\.)?/, "").replace(/\/$/, "")}
                    </a>
                  ) : (
                    <span className="text-neutral-400">—</span>
                  )}
                </td>
                <td className="px-4 py-3 text-right">
                  {r.homeCount > 0 ? (
                    <Link
                      href={`/search?builder=${r.slug}`}
                      className="rounded-full bg-teal-50 px-3 py-1 text-xs font-semibold text-teal-800 hover:bg-teal-100"
                    >
                      {r.homeCount.toLocaleString()} homes →
                    </Link>
                  ) : (
                    <span className="text-xs text-neutral-400">coming soon</span>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}