Public: Stock Photos page

src/pages/business-assets/stock/StockPhotos.tsx

import React, { useEffect, useMemo, useState } from "react";

type Item = { id: string; url: string; name: string; tags: string[]; category?: string; createdAt: number };

export default function StockPhotos() {
  const [data, setData] = useState<Item[]>([]);
  const [q, setQ] = useState("");

  useEffect(() => { (async () => {
    const res = await fetch("/api/stock/photos");
    setData(await res.json());
  })(); }, []);

  const filtered = useMemo(() => {
    const term = q.toLowerCase().trim();
    if (!term) return data;
    return data.filter(x =>
      x.name.toLowerCase().includes(term) ||
      x.tags.join(" ").toLowerCase().includes(term) ||
      (x.category||"").toLowerCase().includes(term)
    );
  }, [data, q]);

  return (
    <div className="p-6 space-y-4">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold">Stock Photos</h1>
        <input value={q} onChange={(e)=>setQ(e.target.value)} placeholder="Search by tag/name/category"
          className="rounded bg-slate-950/60 border border-white/10 p-2 text-sm w-64" />
      </div>

      <div className="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
        {filtered.map((it) => (
          <a key={it.id} href={it.url} download className="group rounded-lg overflow-hidden border border-white/10 bg-slate-900/60 hover:bg-white/5">
            <div className="aspect-video bg-black/5">
              <img src={it.url} alt={it.name} className="w-full h-full object-cover" />
            </div>
            <div className="p-3">
              <div className="text-sm font-medium">{it.name}</div>
              <div className="text-[11px] text-slate-400">{it.category || "uncategorized"} • {it.tags.join(", ")}</div>
            </div>
          </a>
        ))}
      </div>
    </div>
  );
}

Public: Mockups page

src/pages/business-assets/stock/Mockups.tsx

import React, { useEffect, useMemo, useState } from "react";
type Item = { id: string; url: string; name: string; tags: string[]; category?: string; createdAt: number };

export default function Mockups() {
  const [data, setData] = useState<Item[]>([]);
  const [q, setQ] = useState("");

  useEffect(() => { (async () => {
    const res = await fetch("/api/stock/mockups");
    setData(await res.json());
  })(); }, []);

  const filtered = useMemo(() => {
    const term = q.toLowerCase().trim();
    if (!term) return data;
    return data.filter(x =>
      x.name.toLowerCase().includes(term) ||
      x.tags.join(" ").toLowerCase().includes(term) ||
      (x.category||"").toLowerCase().includes(term)
    );
  }, [data, q]);

  return (
    <div className="p-6 space-y-4">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold">Mockups</h1>
        <input value={q} onChange={(e)=>setQ(e.target.value)} placeholder="Search by tag/name/category"
          className="rounded bg-slate-950/60 border border-white/10 p-2 text-sm w-64" />
      </div>

      <div className="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
        {filtered.map((it) => (
          <a key={it.id} href={it.url} download className="group rounded-lg overflow-hidden border border-white/10 bg-slate-900/60 hover:bg-white/5">
            <div className="aspect-video bg-black/5">
              <img src={it.url} alt={it.name} className="w-full h-full object-cover" />
            </div>
            <div className="p-3">
              <div className="text-sm font-medium">{it.name}</div>
              <div className="text-[11px] text-slate-400">{it.category || "uncategorized"} • {it.tags.join(", ")}</div>
            </div>
          </a>
        ))}
      </div>
    </div>
  );
}