add stable media support and sync skillhub UI
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import Link from "next/link";
|
||||
import {
|
||||
PopiartApiError,
|
||||
getSkillsCatalog,
|
||||
getViewerSession,
|
||||
} from "@/lib/popiart-api";
|
||||
import { getLiveCopy } from "@/lib/live-copy";
|
||||
import { type Locale } from "@/lib/site-content";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
function formatError(error: unknown) {
|
||||
if (error instanceof PopiartApiError) {
|
||||
return error.message;
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
||||
export default async function SkillsPage({
|
||||
params,
|
||||
searchParams,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
searchParams: Promise<{ search?: string | string[] }>;
|
||||
}) {
|
||||
const { locale } = await params;
|
||||
const rawSearch = (await searchParams).search;
|
||||
const search = Array.isArray(rawSearch) ? rawSearch[0] : rawSearch;
|
||||
const typedLocale = locale as Locale;
|
||||
const liveCopy = getLiveCopy(typedLocale);
|
||||
const session = await getViewerSession();
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<div className="page-stack">
|
||||
<section className="section-panel hero-tight">
|
||||
<div className="section-heading">
|
||||
<div className="eyebrow">{liveCopy.skills.tag}</div>
|
||||
<h1>{liveCopy.skills.title}</h1>
|
||||
<p>{liveCopy.skills.subtitle}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section-panel empty-state-card">
|
||||
<div className="section-heading compact">
|
||||
<h2>{liveCopy.skills.unauthenticatedTitle}</h2>
|
||||
<p>{liveCopy.skills.unauthenticatedBody}</p>
|
||||
</div>
|
||||
<div className="hero-actions">
|
||||
<Link className="button button-dark" href={`/${locale}/login`}>
|
||||
{liveCopy.skills.loginCta}
|
||||
</Link>
|
||||
<Link className="button button-light" href={`/${locale}/docs`}>
|
||||
{liveCopy.skills.docsCta}
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let catalog = null;
|
||||
let loadError: string | null = null;
|
||||
|
||||
try {
|
||||
catalog = await getSkillsCatalog(search?.trim());
|
||||
} catch (error) {
|
||||
loadError = formatError(error);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="page-stack">
|
||||
<section className="section-panel hero-tight">
|
||||
<div className="section-heading">
|
||||
<div className="eyebrow">{liveCopy.skills.tag}</div>
|
||||
<h1>{liveCopy.skills.title}</h1>
|
||||
<p>{liveCopy.skills.subtitle}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section-panel">
|
||||
<form className="search-form" method="get">
|
||||
<label className="field-label" htmlFor="skill-search">
|
||||
{liveCopy.skills.searchLabel}
|
||||
</label>
|
||||
<div className="search-row">
|
||||
<input
|
||||
className="text-input"
|
||||
defaultValue={search ?? ""}
|
||||
id="skill-search"
|
||||
name="search"
|
||||
placeholder={liveCopy.skills.searchPlaceholder}
|
||||
type="search"
|
||||
/>
|
||||
<button className="button button-dark" type="submit">
|
||||
{liveCopy.skills.searchAction}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{loadError ? (
|
||||
<div className="status-banner status-banner-error">
|
||||
<strong>{liveCopy.skills.loadErrorPrefix}</strong>
|
||||
<span>{loadError}</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="pill-row">
|
||||
<span className="pill">
|
||||
{liveCopy.skills.resultsPrefix}: {catalog?.total ?? 0}
|
||||
</span>
|
||||
<span className="pill">{session.user.name || session.user.email || session.user.id}</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="catalog-grid">
|
||||
{catalog && catalog.items.length > 0 ? (
|
||||
catalog.items.map((skill) => (
|
||||
<article className="feature-card skill-card" key={skill.id}>
|
||||
<span className="card-kicker">{skill.version}</span>
|
||||
<h3>{skill.name}</h3>
|
||||
<p>{skill.description}</p>
|
||||
<dl className="meta-grid">
|
||||
<div>
|
||||
<dt>{liveCopy.skills.routeKey}</dt>
|
||||
<dd>{skill.route_key || skill.id}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{liveCopy.skills.modelType}</dt>
|
||||
<dd>{skill.model_type}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{liveCopy.skills.latency}</dt>
|
||||
<dd>{skill.estimated_duration_s}s</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>{liveCopy.skills.tags}</dt>
|
||||
<dd>{skill.tags.join(", ") || "-"}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</article>
|
||||
))
|
||||
) : (
|
||||
<article className="section-panel empty-state-card">
|
||||
<div className="section-heading compact">
|
||||
<h2>{liveCopy.skills.noResults}</h2>
|
||||
<p>{search ? `"${search}"` : liveCopy.skills.subtitle}</p>
|
||||
</div>
|
||||
</article>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user