manufacturers/[slug]: executive leadership + phone + street address
Renders bb.manufacturer_executives rows as a card grid (photo, name, title, bio excerpt, LinkedIn link). Adds a Headquarters section with the street-level hq_address + phone (tel: link) + contact email. Bio text is now whitespace-preserving so multi-paragraph copy from the admin editor renders correctly.
This commit is contained in:
@@ -14,21 +14,19 @@ async function loadManufacturer(slug: string) {
|
|||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from("tracked_companies")
|
.from("tracked_companies")
|
||||||
.select(
|
.select(
|
||||||
"id, slug, company_name, bio, bio_source, logo_url, company_website, contact_email, contact_url, hq_city, hq_country, linkedin_url, twitter_handle, youtube_url, press_url, categories, exhibits_nab, exhibits_ibc, mention_count, last_mentioned, featured, ad_status"
|
"id, slug, company_name, bio, bio_source, logo_url, company_website, contact_email, contact_url, phone, hq_address, hq_city, hq_country, linkedin_url, twitter_handle, youtube_url, press_url, categories, exhibits_nab, exhibits_ibc, mention_count, last_mentioned, featured, ad_status"
|
||||||
)
|
)
|
||||||
.eq("slug", slug)
|
.eq("slug", slug)
|
||||||
.eq("directory_visible", true)
|
.eq("directory_visible", true)
|
||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
|
|
||||||
// Per-show appearances
|
|
||||||
const { data: shows } = await supabase
|
const { data: shows } = await supabase
|
||||||
.from("trade_show_exhibitors")
|
.from("trade_show_exhibitors")
|
||||||
.select("show, show_year, booth, description, url, website")
|
.select("show, show_year, booth, description, url, website")
|
||||||
.eq("exhibitor_name", data.company_name)
|
.eq("exhibitor_name", data.company_name)
|
||||||
.order("show_year", { ascending: false });
|
.order("show_year", { ascending: false });
|
||||||
|
|
||||||
// Recent BB stories that mention this company (mention_count guides interest)
|
|
||||||
const { data: pressReleases } = await supabase
|
const { data: pressReleases } = await supabase
|
||||||
.from("press_releases")
|
.from("press_releases")
|
||||||
.select("title, slug, published_at, source_url")
|
.select("title, slug, published_at, source_url")
|
||||||
@@ -36,7 +34,19 @@ async function loadManufacturer(slug: string) {
|
|||||||
.order("published_at", { ascending: false })
|
.order("published_at", { ascending: false })
|
||||||
.limit(10);
|
.limit(10);
|
||||||
|
|
||||||
return { company: data, shows: shows || [], pressReleases: pressReleases || [] };
|
const { data: executives } = await supabase
|
||||||
|
.from("manufacturer_executives")
|
||||||
|
.select("id, name, title, photo_url, linkedin_url, bio")
|
||||||
|
.eq("company_id", data.id)
|
||||||
|
.order("sort_order")
|
||||||
|
.order("name");
|
||||||
|
|
||||||
|
return {
|
||||||
|
company: data,
|
||||||
|
shows: shows || [],
|
||||||
|
pressReleases: pressReleases || [],
|
||||||
|
executives: executives || [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
@@ -76,7 +86,7 @@ export default async function ManufacturerProfile({
|
|||||||
const { slug } = await params;
|
const { slug } = await params;
|
||||||
const result = await loadManufacturer(slug);
|
const result = await loadManufacturer(slug);
|
||||||
if (!result) notFound();
|
if (!result) notFound();
|
||||||
const { company, shows, pressReleases } = result;
|
const { company, shows, pressReleases, executives } = result;
|
||||||
|
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
@@ -149,6 +159,11 @@ export default async function ManufacturerProfile({
|
|||||||
{[company.hq_city, company.hq_country].filter(Boolean).join(", ")}
|
{[company.hq_city, company.hq_country].filter(Boolean).join(", ")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{company.phone && (
|
||||||
|
<a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-ink/60 hover:text-brand-primary">
|
||||||
|
{company.phone}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 flex flex-wrap gap-3 text-sm">
|
<div className="mt-3 flex flex-wrap gap-3 text-sm">
|
||||||
{company.company_website && (
|
{company.company_website && (
|
||||||
@@ -208,7 +223,7 @@ export default async function ManufacturerProfile({
|
|||||||
{company.bio && (
|
{company.bio && (
|
||||||
<section className="mb-10">
|
<section className="mb-10">
|
||||||
<h2 className="text-lg font-display font-bold text-ink mb-2">About</h2>
|
<h2 className="text-lg font-display font-bold text-ink mb-2">About</h2>
|
||||||
<p className="text-ink/80 leading-relaxed">{company.bio}</p>
|
<div className="text-ink/80 leading-relaxed space-y-4 whitespace-pre-line">{company.bio}</div>
|
||||||
{company.bio_source && (
|
{company.bio_source && (
|
||||||
<p className="text-xs text-ink/40 mt-2">
|
<p className="text-xs text-ink/40 mt-2">
|
||||||
Source: {company.bio_source}
|
Source: {company.bio_source}
|
||||||
@@ -217,6 +232,72 @@ export default async function ManufacturerProfile({
|
|||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{executives.length > 0 && (
|
||||||
|
<section className="mb-10">
|
||||||
|
<h2 className="text-lg font-display font-bold text-ink mb-4">Executive leadership</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{executives.map((e: any) => (
|
||||||
|
<div key={e.id} className="bg-surface-soft border border-ink/10 rounded-lg p-4 flex gap-3">
|
||||||
|
{e.photo_url ? (
|
||||||
|
/* eslint-disable-next-line @next/next/no-img-element */
|
||||||
|
<img
|
||||||
|
src={e.photo_url}
|
||||||
|
alt={e.name}
|
||||||
|
className="w-16 h-16 rounded-full object-cover bg-ink/5 flex-shrink-0"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-16 h-16 rounded-full bg-ink/5 flex items-center justify-center text-ink/40 font-bold flex-shrink-0">
|
||||||
|
{(e.name || "?").trim().charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="min-w-0">
|
||||||
|
<div className="font-semibold text-ink truncate">{e.name}</div>
|
||||||
|
{e.title && <div className="text-xs text-ink/60 mb-1">{e.title}</div>}
|
||||||
|
{e.bio && <p className="text-xs text-ink/70 line-clamp-3 mb-1">{e.bio}</p>}
|
||||||
|
{e.linkedin_url && (
|
||||||
|
<a
|
||||||
|
href={e.linkedin_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-[11px] text-brand-primary hover:underline"
|
||||||
|
>
|
||||||
|
LinkedIn ↗
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(company.hq_address || company.phone || company.contact_email) && (
|
||||||
|
<section className="mb-10">
|
||||||
|
<h2 className="text-lg font-display font-bold text-ink mb-2">Headquarters</h2>
|
||||||
|
<address className="not-italic text-ink/80 text-sm leading-relaxed">
|
||||||
|
{company.hq_address && <div>{company.hq_address}</div>}
|
||||||
|
{(company.hq_city || company.hq_country) && (
|
||||||
|
<div>{[company.hq_city, company.hq_country].filter(Boolean).join(", ")}</div>
|
||||||
|
)}
|
||||||
|
{company.phone && (
|
||||||
|
<div className="mt-1">
|
||||||
|
<a href={`tel:${company.phone.replace(/[^\d+]/g, '')}`} className="text-brand-primary hover:underline">
|
||||||
|
{company.phone}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{company.contact_email && (
|
||||||
|
<div>
|
||||||
|
<a href={`mailto:${company.contact_email}`} className="text-brand-primary hover:underline">
|
||||||
|
{company.contact_email}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</address>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
{company.categories && company.categories.length > 0 && (
|
{company.categories && company.categories.length > 0 && (
|
||||||
<section className="mb-10">
|
<section className="mb-10">
|
||||||
<h2 className="text-lg font-display font-bold text-ink mb-2">Categories</h2>
|
<h2 className="text-lg font-display font-bold text-ink mb-2">Categories</h2>
|
||||||
|
|||||||
Reference in New Issue
Block a user