manufacturer hover popup + smart back link
Content Partners (and any other) company-hover preview previously used text-ink / bg-ink / brand-primary utilities that aren't defined in our Tailwind config, so the popup rendered as white background with no text color (inheriting near-white text from the dark theme parent). Replaced every undefined utility with concrete dark-theme colors matching the rest of the site: #111827 panel, #2a3a50 borders, #e8e8e8 body text, #60a5fa links, real semi-transparent chip badges. Manufacturer profile back-link now uses BackLink, a client component that calls router.back() when we have same-origin history — so clicking the breadcrumb from the homepage Content Partners hover returns the user to the homepage instead of dumping them at /manufacturers. Falls through to the directory link for new-tab opens, search-engine entries, and no-JS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
36
src/app/manufacturers/[slug]/BackLink.tsx
Normal file
36
src/app/manufacturers/[slug]/BackLink.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Smart back link for the manufacturer profile page.
|
||||
*
|
||||
* Renders as a server-rendered <a> linking to the directory so search
|
||||
* engines and no-JS users still get a sensible href. Client-side it
|
||||
* intercepts the click and calls `router.back()` — if the user arrived
|
||||
* here from anywhere on the BB site (homepage Content Partners hover,
|
||||
* an article body link, the directory itself) we'll return them there.
|
||||
* Falls through to the directory only when there is no in-app history.
|
||||
*/
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import type { MouseEvent } from "react";
|
||||
|
||||
export default function BackLink() {
|
||||
const router = useRouter();
|
||||
function onClick(e: MouseEvent<HTMLAnchorElement>) {
|
||||
if (typeof window === "undefined") return;
|
||||
// Same-origin Referer means we have somewhere to go back to. The
|
||||
// browser history length > 1 check catches the case where the user
|
||||
// opened the profile in a new tab — in that case we leave the default
|
||||
// navigation (to /manufacturers) alone.
|
||||
const sameOrigin = !!document.referrer && new URL(document.referrer).origin === window.location.origin;
|
||||
if (sameOrigin && window.history.length > 1) {
|
||||
e.preventDefault();
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Link href="/manufacturers" onClick={onClick} className="hover:text-[#3b82f6]">
|
||||
← Back
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import BackLink from "./BackLink";
|
||||
|
||||
export const revalidate = 1800;
|
||||
|
||||
@@ -166,9 +167,7 @@ export default async function ManufacturerProfile({
|
||||
/>
|
||||
<article className="max-w-4xl mx-auto px-4 py-10">
|
||||
<nav className="text-xs text-[#888] mb-6">
|
||||
<Link href="/manufacturers" className="hover:text-[#3b82f6]">
|
||||
← Production Industry Manufacturers
|
||||
</Link>
|
||||
<BackLink />
|
||||
</nav>
|
||||
|
||||
<header className="flex flex-col sm:flex-row gap-6 mb-8 pb-8 border-b border-[#252525]">
|
||||
|
||||
@@ -99,68 +99,68 @@ export default function CompanyMentionsHover() {
|
||||
role="dialog"
|
||||
aria-label={preview?.name ? `${preview.name} preview` : "Company preview"}
|
||||
style={{ position: "absolute", top: pos.top, left: pos.left, width: 360, zIndex: 999 }}
|
||||
className="bg-white text-ink border border-ink/10 rounded-xl shadow-2xl p-4 text-sm"
|
||||
className="bg-[#111827] text-[#e8e8e8] border border-[#2a3a50] rounded-xl shadow-2xl p-4 text-sm backdrop-blur-sm"
|
||||
onMouseEnter={() => { if (hideTimer.current) { window.clearTimeout(hideTimer.current); hideTimer.current = null; } }}
|
||||
onMouseLeave={() => { hideTimer.current = window.setTimeout(() => { setAnchor(null); setPreview(null); setPos(null); }, 180); }}
|
||||
>
|
||||
{loading || !preview ? (
|
||||
<div className="text-ink/50 text-xs py-2">Loading…</div>
|
||||
<div className="text-[#888] text-xs py-2">Loading…</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start gap-3 mb-2">
|
||||
{preview.logoUrl ? (
|
||||
/* eslint-disable-next-line @next/next/no-img-element */
|
||||
<img src={preview.logoUrl} alt="" className="w-12 h-12 rounded bg-ink/5 object-contain flex-shrink-0" loading="lazy" />
|
||||
<img src={preview.logoUrl} alt="" className="w-12 h-12 rounded bg-white p-1 object-contain flex-shrink-0" loading="lazy" />
|
||||
) : (
|
||||
<div className="w-12 h-12 rounded bg-ink/5 flex items-center justify-center text-ink/40 font-bold flex-shrink-0">
|
||||
<div className="w-12 h-12 rounded bg-[#1a2535] flex items-center justify-center text-[#3b82f6] font-bold flex-shrink-0">
|
||||
{(preview.name || "?").trim().charAt(0).toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<Link href={`/manufacturers/${preview.slug}`} className="font-semibold text-ink hover:text-brand-primary block truncate">
|
||||
<Link href={`/manufacturers/${preview.slug}`} className="font-semibold text-white hover:text-[#3b82f6] block truncate">
|
||||
{preview.name}
|
||||
</Link>
|
||||
<div className="flex items-center gap-1.5 mt-0.5 flex-wrap text-[10px]">
|
||||
<div className="flex items-center gap-1.5 mt-1 flex-wrap text-[10px]">
|
||||
{preview.isActiveAdvertiser && (
|
||||
<span className="bg-emerald-50 text-emerald-700 px-1.5 py-0.5 rounded font-semibold uppercase tracking-wider">Sponsor</span>
|
||||
<span className="bg-emerald-500/15 text-emerald-300 border border-emerald-500/30 px-1.5 py-0.5 rounded font-semibold uppercase tracking-wider">Sponsor</span>
|
||||
)}
|
||||
{preview.exhibitsNab && (
|
||||
<span className="bg-amber-50 text-amber-700 px-1.5 py-0.5 rounded font-semibold uppercase tracking-wider">2026 NAB Show</span>
|
||||
<span className="bg-amber-500/15 text-amber-300 border border-amber-500/30 px-1.5 py-0.5 rounded font-semibold uppercase tracking-wider">2026 NAB Show</span>
|
||||
)}
|
||||
{preview.exhibitsIbc && (
|
||||
<span className="bg-blue-50 text-blue-700 px-1.5 py-0.5 rounded font-semibold uppercase tracking-wider">IBC 2026</span>
|
||||
<span className="bg-blue-500/15 text-blue-300 border border-blue-500/30 px-1.5 py-0.5 rounded font-semibold uppercase tracking-wider">IBC 2026</span>
|
||||
)}
|
||||
{preview.hq && <span className="text-ink/60">{preview.hq}</span>}
|
||||
{preview.hq && <span className="text-[#888]">{preview.hq}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{preview.bio && (
|
||||
<p className="text-ink/70 text-xs leading-snug mb-3 line-clamp-4">{preview.bio}</p>
|
||||
<p className="text-[#cbd5e1] text-xs leading-snug mb-3 line-clamp-4">{preview.bio}</p>
|
||||
)}
|
||||
|
||||
{preview.stories.length > 0 && (
|
||||
<div className="border-t border-ink/10 pt-2">
|
||||
<div className="text-[10px] uppercase tracking-widest text-ink/50 font-bold mb-1.5">Recent coverage</div>
|
||||
<div className="border-t border-[#2a3a50] pt-2">
|
||||
<div className="text-[10px] uppercase tracking-widest text-[#888] font-bold mb-1.5">Recent coverage</div>
|
||||
<ul className="space-y-1">
|
||||
{preview.stories.slice(0, 3).map((s) => (
|
||||
<li key={s.slug}>
|
||||
<Link href={`/news/${s.slug}`} className="text-xs text-brand-primary hover:underline line-clamp-2">
|
||||
<Link href={`/news/${s.slug}`} className="text-xs text-[#60a5fa] hover:text-[#93c5fd] hover:underline line-clamp-2">
|
||||
{s.title}
|
||||
</Link>
|
||||
{s.date && <span className="text-[10px] text-ink/40 ml-1">· {new Date(s.date).toISOString().slice(0, 10)}</span>}
|
||||
{s.date && <span className="text-[10px] text-[#666] ml-1">· {new Date(s.date).toISOString().slice(0, 10)}</span>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t border-ink/10 mt-3 pt-2 flex items-center justify-between">
|
||||
<Link href={`/manufacturers/${preview.slug}`} className="text-xs font-semibold text-brand-primary hover:underline">
|
||||
<div className="border-t border-[#2a3a50] mt-3 pt-2 flex items-center justify-between">
|
||||
<Link href={`/manufacturers/${preview.slug}`} className="text-xs font-semibold text-[#60a5fa] hover:text-[#93c5fd] hover:underline">
|
||||
Full profile →
|
||||
</Link>
|
||||
{preview.website && (
|
||||
<a href={preview.website} target="_blank" rel="noopener noreferrer" className="text-[10px] text-ink/50 hover:text-brand-primary">
|
||||
<a href={preview.website} target="_blank" rel="noopener noreferrer" className="text-[10px] text-[#888] hover:text-[#3b82f6]">
|
||||
{preview.website.replace(/^https?:\/\//, "").replace(/\/$/, "")} ↗
|
||||
</a>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user