← back to Trademarks Copyright

src/app/item/[id]/page.tsx

128 lines

import { notFound } from "next/navigation";
import Link from "next/link";
import { query } from "@/lib/db";
import NewsFeed from "@/components/NewsFeed";
import SwotPanel from "@/components/SwotPanel";
import Disclaimer from "@/components/Disclaimer";
import type { Item } from "@/lib/types";

type Params = { id: string };

export default async function ItemPage({ params }: { params: Promise<Params> }) {
  const { id } = await params;
  const numId = Number(id);
  if (!Number.isFinite(numId)) notFound();

  const { rows } = await query<Item & { years_expired: number; follow_up_date: string }>(
    `SELECT *,
            EXTRACT(YEAR FROM AGE(NOW(), expired_date))::int AS years_expired,
            (CURRENT_DATE + INTERVAL '6 months')::date AS follow_up_date
     FROM items WHERE id = $1`,
    [numId]
  );
  if (!rows.length) notFound();
  const item = rows[0];

  return (
    <div>
      <Disclaimer />
      <Link href="/" className="text-sm text-ink/60 hover:text-brass">← All items</Link>
      <div className="mt-2 flex flex-wrap items-baseline gap-3">
        <h1 className="text-3xl font-semibold tracking-tight">{item.name}</h1>
        <span className="tag">{item.kind}</span>
        <span className="tag">{item.status}</span>
        <span className="text-ink/60">
          Expired {item.expired_date.toString().slice(0, 10)} ({item.years_expired} yrs ago)
          {" · "}
          <span title="Revisit date — 6 months from today">Follow-up {item.follow_up_date.toString().slice(0, 10)}</span>
        </span>
        {item.composite_score !== null && (
          <span className="ml-auto rounded bg-ledger px-2 py-1 font-semibold text-parchment">
            Score {Number(item.composite_score).toFixed(1)}
          </span>
        )}
      </div>

      <div className="mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3">
        <section className="card p-4 lg:col-span-2">
          <h2 className="mb-2 text-lg font-semibold">Details</h2>
          <dl className="grid grid-cols-2 gap-y-2 text-sm">
            <dt className="text-ink/60">Original owner</dt><dd>{item.original_owner ?? "—"}</dd>
            <dt className="text-ink/60">Registration #</dt><dd>{item.registration_number ?? "—"}</dd>
            <dt className="text-ink/60">NICE class</dt><dd>{item.nice_class ?? "—"}</dd>
            <dt className="text-ink/60">Goods / services</dt><dd>{item.goods_services ?? "—"}</dd>
            <dt className="text-ink/60">Filed</dt><dd>{item.filed_date?.toString().slice(0, 10) ?? "—"}</dd>
            <dt className="text-ink/60">Registered</dt><dd>{item.registered_date?.toString().slice(0, 10) ?? "—"}</dd>
            <dt className="text-ink/60">Source</dt>
            <dd>
              {item.source_url ? (
                <a href={item.source_url} target="_blank" rel="noreferrer" className="text-brass hover:underline">{item.source}</a>
              ) : (item.source)}
            </dd>
          </dl>
          {item.notes && (
            <>
              <h3 className="mt-4 text-sm font-semibold">Notes</h3>
              <p className="text-sm text-ink/80">{item.notes}</p>
            </>
          )}
        </section>

        <aside className="card p-4">
          <h2 className="mb-2 text-lg font-semibold">Scoring breakdown</h2>
          <ScoreBar label="Distinctiveness" v={item.distinctiveness_score} w={25} />
          <ScoreBar label="Recognition" v={item.recognition_score} w={15} />
          <ScoreBar label="Monetization breadth" v={item.monetization_breadth} w={10} />
          <div className="mt-2 text-sm">
            <span className="text-ink/60">.com available: </span>
            <b>{item.domain_available === true ? "Yes" : item.domain_available === false ? "No" : "Unknown"}</b>
          </div>
          <div className="text-sm">
            <span className="text-ink/60">Competing active marks: </span>
            <b>{item.competing_active_marks ?? "—"}</b>
          </div>
        </aside>

        <section className="card p-4 lg:col-span-3">
          <h2 className="mb-2 text-lg font-semibold">Monetization ideas</h2>
          {item.monetization_ideas?.length ? (
            <ul className="grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3">
              {item.monetization_ideas.map((idea, i) => (
                <li key={i} className="rounded border border-ink/10 bg-white p-3">
                  <div className="font-semibold">{idea.title}</div>
                  <div className="text-sm text-ink/70">{idea.description}</div>
                </li>
              ))}
            </ul>
          ) : <div className="text-sm text-ink/50">None recorded.</div>}
        </section>

        <section className="card p-4 lg:col-span-2">
          <h2 className="mb-2 text-lg font-semibold">SWOT analysis</h2>
          <SwotPanel itemId={item.id} />
        </section>

        <section className="card p-4">
          <h2 className="mb-2 text-lg font-semibold">News</h2>
          <NewsFeed query={item.name} />
        </section>
      </div>
    </div>
  );
}

function ScoreBar({ label, v, w }: { label: string; v: number | null; w: number }) {
  const pct = v ?? 0;
  return (
    <div className="mb-2">
      <div className="mb-0.5 flex justify-between text-xs">
        <span className="text-ink/70">{label} <span className="text-ink/40">(wt {w}%)</span></span>
        <span className="font-semibold">{v ?? "—"}</span>
      </div>
      <div className="h-2 rounded bg-ink/10">
        <div className="h-2 rounded bg-brass" style={{ width: `${pct}%` }} />
      </div>
    </div>
  );
}