← back to Trademarks Copyright

src/app/drops/page.tsx

262 lines

"use client";

import { Suspense, useEffect, useState } from "react";
import { useSearchParams } from "next/navigation";

export default function DropsLandingPage() {
  return (
    <div>
      <Suspense fallback={<div className="card p-8 mb-6" />}>
        <Hero />
      </Suspense>
      <SampleDrop />
      <Proof />
      <WhoFor />
      <Plans />
      <HowItWorks />
      <Fine />
    </div>
  );
}

function SampleDrop() {
  const [drop, setDrop] = useState<{
    id: number;
    date: string;
    subject: string;
    intro: string | null;
    items: { position: number; headline: string; blurb: string }[];
    hidden_count: number;
  } | null>(null);
  useEffect(() => {
    fetch("/api/drops/latest").then((r) => r.json()).then((j) => setDrop(j.drop));
  }, []);
  if (!drop) return null;
  const date = new Date(drop.date).toLocaleDateString(undefined, { weekday: "long", month: "long", day: "numeric" });
  return (
    <section className="card mb-6 overflow-hidden">
      <div className="bg-ledger text-parchment px-6 py-4">
        <div className="text-xs tracking-widest text-brass">SAMPLE · {date.toUpperCase()}</div>
        <h2 className="mt-1 text-2xl font-semibold">{drop.subject}</h2>
        {drop.intro && <p className="mt-2 text-sm italic text-parchment/75">{drop.intro}</p>}
      </div>
      <div>
        {drop.items.map((it) => (
          <div key={it.position} className="border-t border-ink/10 p-5">
            <div className="text-xs text-ink/50">#{it.position}</div>
            <h3 className="mt-1 text-lg font-semibold">{it.headline}</h3>
            <p className="mt-1 text-sm text-ink/80">{it.blurb}</p>
          </div>
        ))}
        {drop.hidden_count > 0 && (
          <div className="relative border-t border-ink/10 p-6 bg-brass/5 text-center">
            <div className="absolute inset-0 pointer-events-none" style={{
              background: "linear-gradient(180deg, transparent, #f7f4ec 80%)",
            }} />
            <div className="relative">
              <div className="text-xs uppercase tracking-wide text-brass">+ {drop.hidden_count} more in today's drop</div>
              <p className="mt-1 text-sm text-ink/70">
                Subscribers got the rest in their inbox at 9am PT.
              </p>
            </div>
          </div>
        )}
      </div>
    </section>
  );
}

function Hero() {
  const [email, setEmail] = useState("");
  const [name, setName] = useState("");
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState<string | null>(null);
  const sp = useSearchParams();
  const ref = sp.get("ref") || "";

  async function submit(e: React.FormEvent) {
    e.preventDefault();
    setLoading(true); setErr(null);
    try {
      const r = await fetch("/api/drops/signup", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ email, name, ref: ref || undefined }),
      });
      const j = await r.json();
      if (!r.ok) throw new Error(j.error || `HTTP ${r.status}`);
      // Redirect to the private portal instead of showing the token inline.
      window.location.href = `/drops/view/${j.subscriber.token}?welcomed=1`;
    } catch (e) { setErr((e as Error).message); setLoading(false); }
  }

  return (
    <div className="card p-8 mb-6 border-brass/30 bg-gradient-to-br from-parchment to-white">
      <div className="text-xs uppercase tracking-widest text-brass">Daily Drops</div>
      <h1 className="mt-2 text-4xl font-semibold tracking-tight">
        Trademark & domain opportunities in your inbox. <span className="text-brass">Every day at 9am.</span>
      </h1>
      <p className="mt-3 max-w-2xl text-lg text-ink/70">
        Abandoned US trademarks, public-domain works entering their 95th year, expired fashion-brand domains,
        and unregistered names sitting on million-dollar businesses — curated, scored, and delivered fresh.
      </p>

      <form onSubmit={submit} className="mt-6 flex flex-wrap items-center gap-2">
        <input
          required
          type="email"
          placeholder="you@business.com"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          className="w-72"
        />
        <input
          placeholder="your name (optional)"
          value={name}
          onChange={(e) => setName(e.target.value)}
          className="w-52"
        />
        <button type="submit" className="btn" disabled={loading}>
          {loading ? "Starting…" : "Start free trial (3 drops)"}
        </button>
      </form>
      {loading && <div className="mt-3 rounded bg-ink/5 p-3 text-sm text-ink/70">Setting up your account…</div>}
      {err && <div className="mt-3 rounded bg-red-50 p-3 text-sm text-red-700">{err}</div>}
      {ref && <div className="mt-2 text-xs text-brass">✓ referral applied — you're helping a friend too</div>}
      <div className="mt-2 text-xs text-ink/50">No credit card. Unsubscribe anytime from any email.</div>
    </div>
  );
}

function Plans() {
  const [loading, setLoading] = useState<string | null>(null);
  const [err, setErr] = useState<string | null>(null);
  const [email, setEmail] = useState("");

  async function upgrade(tier: "standard" | "pro") {
    if (!email) { setErr("Enter the email you signed up with, then click upgrade."); return; }
    setLoading(tier); setErr(null);
    try {
      const r = await fetch("/api/drops/checkout", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ email, tier }),
      });
      const j = await r.json();
      if (!r.ok) throw new Error(j.error || `HTTP ${r.status}`);
      if (j.url) window.location.href = j.url;
      else setErr(j.message || "Upgraded (mock).");
    } catch (e) { setErr((e as Error).message); }
    finally { setLoading(null); }
  }

  return (
    <div className="mb-6 card p-6">
      <h2 className="text-2xl font-semibold">Pick a tier</h2>
      <div className="mt-2 mb-4 flex flex-wrap items-center gap-2">
        <label className="text-sm text-ink/70">Upgrade as:</label>
        <input type="email" placeholder="you@business.com" value={email} onChange={(e) => setEmail(e.target.value)} className="w-64" />
      </div>
      <div className="grid grid-cols-1 gap-4 md:grid-cols-3">
        <Plan name="Trial"
              price="Free · 3 drops"
              features={["3 items per drop", "basic edition", "no credit card"]}
              cta={null} />
        <Plan name="Standard"
              price="$29/mo"
              features={["3 items per drop", "curated by editor + Qwen", "full archive access"]}
              cta={<button onClick={() => upgrade("standard")} disabled={loading !== null} className="btn">{loading === "standard" ? "…" : "Upgrade"}</button>} />
        <Plan name="Pro"
              price="$99/mo"
              highlight
              features={["10 items per drop", "Pro-only tactical hints", "SWOT preview per item", "full archive + API access"]}
              cta={<button onClick={() => upgrade("pro")} disabled={loading !== null} className="btn">{loading === "pro" ? "…" : "Upgrade"}</button>} />
      </div>
      {err && <div className="mt-3 rounded bg-red-50 p-3 text-sm text-red-700">{err}</div>}
    </div>
  );
}

function Plan({ name, price, features, cta, highlight }: { name: string; price: string; features: string[]; cta: React.ReactNode; highlight?: boolean }) {
  return (
    <div className={`card p-5 ${highlight ? "border-brass/60 ring-1 ring-brass/30" : ""}`}>
      <div className="flex items-baseline justify-between">
        <h3 className="text-lg font-semibold">{name}</h3>
        <span className="text-sm text-brass">{price}</span>
      </div>
      <ul className="mt-3 space-y-1.5 text-sm text-ink/70">
        {features.map((f, i) => <li key={i}>· {f}</li>)}
      </ul>
      {cta && <div className="mt-4">{cta}</div>}
    </div>
  );
}

function HowItWorks() {
  return (
    <div className="mb-6 card p-6">
      <h2 className="text-2xl font-semibold">How we build each drop</h2>
      <ol className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-4 text-sm">
        <li><b>1. Crawl.</b> USPTO TSDR for cancelled marks. US Copyright Office for 95-year clocks. Fashion/DTC sites for unregistered brands + domain expiries.</li>
        <li><b>2. Score.</b> Distinctiveness, age-fit, domain availability, common-law risk. Top candidates only.</li>
        <li><b>3. Write.</b> Local LLM drafts editorial blurbs; human editor reviews before send.</li>
        <li><b>4. Deliver.</b> Inbox at 9am PT. Full archive on your portal. Unsubscribe anytime.</li>
      </ol>
    </div>
  );
}

function Proof() {
  const stats = [
    { k: "30+", v: "abandoned trademarks in the curated catalog" },
    { k: "1929 cohort", v: "most recent public-domain batch" },
    { k: "4 labels", v: "domain snipe · abandonment watch · licensing · avoid" },
    { k: "9am PT", v: "delivered every single day" },
  ];
  return (
    <div className="mb-6 card p-6">
      <h2 className="text-2xl font-semibold">What you get, in numbers</h2>
      <dl className="mt-4 grid grid-cols-2 gap-4 md:grid-cols-4">
        {stats.map((s, i) => (
          <div key={i}>
            <dt className="text-2xl font-semibold text-brass">{s.k}</dt>
            <dd className="text-sm text-ink/70">{s.v}</dd>
          </div>
        ))}
      </dl>
    </div>
  );
}

function WhoFor() {
  return (
    <div className="mb-6 card p-6">
      <h2 className="text-2xl font-semibold">Who reads Drops</h2>
      <div className="mt-3 grid grid-cols-1 gap-4 md:grid-cols-3 text-sm">
        <div>
          <div className="font-semibold text-brass">Brand builders</div>
          <p className="mt-1 text-ink/80">Looking for a distinctive name with built-in recall? Drops surfaces marks nobody's using. You file; you build.</p>
        </div>
        <div>
          <div className="font-semibold text-brass">Domain investors</div>
          <p className="mt-1 text-ink/80">The domain-snipe label flags expiring domains paired with brand recognition. Wholesale prices at drop, retail on sellback.</p>
        </div>
        <div>
          <div className="font-semibold text-brass">IP attorneys</div>
          <p className="mt-1 text-ink/80">Client lookups, FTO reconnaissance, and the licensing-outreach list as a client-development feed.</p>
        </div>
      </div>
    </div>
  );
}

function Fine() {
  return (
    <div className="text-xs text-ink/60">
      Nothing in Drops is legal advice. Before registering any mark or buying any domain, talk to a USPTO-registered attorney.
      Drops flags <em>opportunities</em>, not guarantees — common-law rights survive trademark abandonment and the ACPA applies to bad-faith domain buys.
      We surface the low-risk side of the ledger and tell you why.
    </div>
  );
}