Hard-filter /portfolio/:slug when it matches a category
Category chips (e.g. /portfolio/photography) now show only that category — empty if none. Audience views like /portfolio/game-dev still use prioritization.
This commit is contained in:
@@ -105,12 +105,16 @@ export interface ListMeta {
|
||||
perPage: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
/** all | category (hard filter) | view (audience prioritization) */
|
||||
mode?: "all" | "category" | "view";
|
||||
category?: Category;
|
||||
view?: {
|
||||
slug: string;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
categories?: Category[];
|
||||
};
|
||||
search?: boolean;
|
||||
}
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
|
||||
@@ -53,13 +53,21 @@ export function PortfolioPage() {
|
||||
setSearchParams(next);
|
||||
}
|
||||
|
||||
const title = meta?.view?.name || (category ? categories.find((c) => c.slug === category)?.name : null) || "Portfolio";
|
||||
const mode = meta?.mode;
|
||||
const title =
|
||||
meta?.view?.name ||
|
||||
meta?.category?.name ||
|
||||
(category ? categories.find((c) => c.slug === category)?.name : null) ||
|
||||
"Portfolio";
|
||||
const description =
|
||||
meta?.view?.description ||
|
||||
"Browse projects by category, tag, or search. Share tailored views with clients and collaborators.";
|
||||
meta?.category?.description ||
|
||||
(mode === "category"
|
||||
? `Only projects tagged ${title}.`
|
||||
: "Browse projects by category, tag, or search. Share tailored views with clients and collaborators.");
|
||||
|
||||
const tabTitle = viewSlug
|
||||
? meta?.view?.name || viewSlug
|
||||
? meta?.view?.name || meta?.category?.name || viewSlug
|
||||
: category
|
||||
? categories.find((c) => c.slug === category)?.name || category
|
||||
: q
|
||||
@@ -67,6 +75,10 @@ export function PortfolioPage() {
|
||||
: "Portfolio";
|
||||
usePageTitle(tabTitle);
|
||||
|
||||
const activeSlug = viewSlug || category;
|
||||
const isCategoryMode = mode === "category" || (!!viewSlug && categories.some((c) => c.slug === viewSlug));
|
||||
const isViewMode = mode === "view";
|
||||
|
||||
return (
|
||||
<div className="portfolio page-enter container">
|
||||
<header className="portfolio__header">
|
||||
@@ -74,11 +86,19 @@ export function PortfolioPage() {
|
||||
<p className="portfolio__eyebrow">Work</p>
|
||||
<h1>{title}</h1>
|
||||
<p className="portfolio__desc">{description}</p>
|
||||
{viewSlug && (
|
||||
{activeSlug && (
|
||||
<p className="portfolio__view-note">
|
||||
Viewing curated list <code>/portfolio/{viewSlug}</code>
|
||||
{isCategoryMode && !isViewMode ? (
|
||||
<>
|
||||
Filtered by category <code>/portfolio/{activeSlug}</code>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Viewing curated list <code>/portfolio/{activeSlug}</code>
|
||||
</>
|
||||
)}
|
||||
{" · "}
|
||||
<Link to="/portfolio">clear view</Link>
|
||||
<Link to="/portfolio">show all</Link>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -96,10 +116,19 @@ export function PortfolioPage() {
|
||||
aria-label="Search projects"
|
||||
/>
|
||||
<select
|
||||
value={category}
|
||||
onChange={(e) => updateParam("category", e.target.value)}
|
||||
value={viewSlug && isCategoryMode ? viewSlug : category}
|
||||
onChange={(e) => {
|
||||
const slug = e.target.value;
|
||||
if (!slug) {
|
||||
// clear path filter → all portfolio
|
||||
window.location.href = "/portfolio";
|
||||
return;
|
||||
}
|
||||
// Category hard-filter via path
|
||||
window.location.href = `/portfolio/${slug}`;
|
||||
}}
|
||||
aria-label="Filter by category"
|
||||
disabled={!!viewSlug}
|
||||
disabled={isViewMode}
|
||||
>
|
||||
<option value="">All categories</option>
|
||||
{categories.map((c) => (
|
||||
@@ -124,9 +153,10 @@ export function PortfolioPage() {
|
||||
<Link
|
||||
key={c.id}
|
||||
to={`/portfolio/${c.slug}`}
|
||||
className={viewSlug === c.slug ? "is-active" : ""}
|
||||
className={viewSlug === c.slug || category === c.slug ? "is-active" : ""}
|
||||
>
|
||||
{c.name}
|
||||
{typeof c.projectCount === "number" ? ` (${c.projectCount})` : ""}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
@@ -135,7 +165,11 @@ export function PortfolioPage() {
|
||||
{error && <p className="portfolio__status portfolio__status--err">{error}</p>}
|
||||
|
||||
{!loading && !error && projects.length === 0 && (
|
||||
<p className="portfolio__status">No published projects match these filters.</p>
|
||||
<p className="portfolio__status">
|
||||
{isCategoryMode
|
||||
? `No published projects in ${title} yet.`
|
||||
: "No published projects match these filters."}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="project-grid">
|
||||
|
||||
Reference in New Issue
Block a user