Skip to content

Commit

Permalink
refactor: move instantsearch providers into layout.tsx
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinstadler committed Jan 9, 2025
1 parent 616f782 commit e469b67
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 106 deletions.
56 changes: 27 additions & 29 deletions app/[locale]/languages/[language]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,40 @@

import { useTranslations } from "next-intl";

import { Results } from "@/components/instantsearch/results";
import { SingleRefinementList } from "@/components/instantsearch/single-refinement-list";
import { InstantSearchView } from "@/components/instantsearch-view";
import { MainContent } from "@/components/main-content";

interface LanguagesPageProps {
params?: {
language: string;
};
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function LanguagesPage(props: LanguagesPageProps) {
export default function LanguagesPage() {
const t = useTranslations("LanguagesPage");
const tl = useTranslations("Languages");
// TODO validate 'language' param?
return (
<MainContent>
<InstantSearchView pageName="languages" pathnameField="language">
<SingleRefinementList
allLabel={t("all languages")}
attribute={"language"}
className="lowercase"
refinementArgs={{
transformItems: (items) => {
return items
.map((item) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
item.label = tl(item.label as any);
return item;
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
},
}}
/>
</InstantSearchView>
<div className="grid h-full grid-cols-[25%_75%] gap-6 px-2">
<div className="relative h-full">
<SingleRefinementList
allLabel={t("all languages")}
attribute={"language"}
className="lowercase"
pathname="languages"
refinementArgs={{
transformItems: (items) => {
return items
.map((item) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
item.label = tl(item.label as any);
return item;
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
},
}}
/>
</div>
<Results />
</div>
</MainContent>
);
}
17 changes: 17 additions & 0 deletions app/[locale]/languages/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { ReactNode } from "react";

import { ThomasBernhardInstantSearchProvider } from "@/components/instantsearch/thomas-bernhard/thomasbernhard-instantsearchprovider";

interface LanguagesLayoutProps {
children: ReactNode;
}

export default function LanguagesLayout(props: LanguagesLayoutProps): ReactNode {
const { children } = props;

return (
<ThomasBernhardInstantSearchProvider pageName="languages" pathnameField="language">
{children}
</ThomasBernhardInstantSearchProvider>
);
}
152 changes: 79 additions & 73 deletions app/[locale]/works/[category]/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
import type { RefinementListItem } from "instantsearch.js/es/connectors/refinement-list/connectRefinementList";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import * as v from "valibot";

import { Results } from "@/components/instantsearch/results";
import { SingleRefinementDropdown } from "@/components/instantsearch/single-refinement-dropdown";
import { SingleRefinementList } from "@/components/instantsearch/single-refinement-list";
import { ThomasBernhardInstantSearchProvider } from "@/components/instantsearch/thomas-bernhard/thomasbernhard-instantsearchprovider";
import { MainContent } from "@/components/main-content";
import { getWorks } from "@/lib/data";
import type { BernhardWork, Category } from "@/lib/model";
import { type BernhardWork, type Category, otherCategories, proseCategories } from "@/lib/model";

interface WorksPageProps {
params: {
Expand All @@ -23,91 +23,97 @@ export default function WorksPage(props: WorksPageProps) {
const ct = useTranslations("BernhardCategories");
const tl = useTranslations("Languages");

// TODO validate category
const category = props.params.category;
const categoryLabel = ct(category as Category);
const category = props.params.category as Category;
if (
!v.is(
v.pipe(
v.string(),
v.check((c) => {
return c in otherCategories || c in proseCategories;
}),
),
category,
)
) {
// TODO return not found
}
const categoryLabel = ct(category);

// get id -> work info dictionary once on pageload
const [works, setWorks] = useState({} as Record<string, BernhardWork>);
useEffect(() => {
const get = async () => {
setWorks(await getWorks(category as Category));
setWorks(await getWorks(category));
};
// eslint-disable-next-line @typescript-eslint/no-floating-promises
get();
}, [category]);

return (
<ThomasBernhardInstantSearchProvider
filters={`contains.work.category:=${category}`}
pageName={category} // hack
pathnameField="contains.work.id"
queryArgsToMenuFields={{ language: "language" }}
>
<MainContent>
<div className="grid h-full grid-cols-[25%_75%] gap-6 px-2">
<div className="relative h-full">
<SingleRefinementList
allLabel={categoryLabel}
attribute={"contains.work.id"}
// format as title (year) instead of showing facet count
refinementArgs={{
// workaround like https://github.com/algolia/instantsearch/issues/2568
transformItems: (items: Array<RefinementListItem>) => {
return (
items
// the refinement may contain out-of-category works which are contained in
// publications which also contain works of this category (and therefore show up
// in the filtered search)
.filter((item) => {
return item.value in works;
})
.sort((a, b) => {
const ya = works[a.value]!.year;
const yb = works[b.value]!.year;
if (!ya) {
return 1;
} else if (!yb) {
return -1;
} else {
return ya - yb;
}
})
.map((item) => {
const work = works[item.value]!;
item.label = work.short_title ?? work.title;
if (work.year) {
item.label += ` (${work.year.toString()})`;
}
return item;
})
);
},
}}
/>
</div>
<Results className="h-full overflow-y-auto">
<SingleRefinementDropdown
allLabel={`${t("all")} ${t("filter_by.language")}`}
attribute={"language"}
className="min-w-40"
refinementArgs={{
transformItems: (items: Array<RefinementListItem>) => {
return items
<MainContent>
<div className="grid h-full grid-cols-[25%_75%] gap-6 px-2">
<div className="relative h-full">
<SingleRefinementList
allLabel={categoryLabel}
attribute={"contains.work.id"}
pathname={`/works/${category}`}
// format as title (year) instead of showing facet count
refinementArgs={{
// workaround like https://github.com/algolia/instantsearch/issues/2568
transformItems: (items: Array<RefinementListItem>) => {
return (
items
// the refinement may contain out-of-category works which are contained in
// publications which also contain works of this category (and therefore show up
// in the filtered search)
.filter((item) => {
return item.value in works;
})
.sort((a, b) => {
const ya = works[a.value]!.year;
const yb = works[b.value]!.year;
if (!ya) {
return 1;
} else if (!yb) {
return -1;
} else {
return ya - yb;
}
})
.map((item) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
item.label = `${tl(item.label as any).toLowerCase()} (${item.count.toString()})`;
const work = works[item.value]!;
item.label = work.short_title ?? work.title;
if (work.year) {
item.label += ` (${work.year.toString()})`;
}
return item;
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
},
}}
/>
</Results>
);
},
}}
/>
</div>
</MainContent>
</ThomasBernhardInstantSearchProvider>
<Results className="h-full overflow-y-auto">
<SingleRefinementDropdown
allLabel={`${t("all")} ${t("filter_by.language")}`}
attribute={"language"}
className="min-w-40"
refinementArgs={{
transformItems: (items: Array<RefinementListItem>) => {
return items
.map((item) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
item.label = `${tl(item.label as any).toLowerCase()} (${item.count.toString()})`;
return item;
})
.sort((a, b) => {
return a.label.localeCompare(b.label);
});
},
}}
/>
</Results>
</div>
</MainContent>
);
}
25 changes: 25 additions & 0 deletions app/[locale]/works/[category]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { ReactNode } from "react";

import { ThomasBernhardInstantSearchProvider } from "@/components/instantsearch/thomas-bernhard/thomasbernhard-instantsearchprovider";

interface LanguagesLayoutProps {
children: ReactNode;
params: {
category: string;
};
}

export default function LanguagesLayout(props: LanguagesLayoutProps): ReactNode {
const { children, params } = props;

return (
<ThomasBernhardInstantSearchProvider
filters={`contains.work.category:=${params.category}`}
pageName={`works/${params.category}`}
pathnameField="contains.work.id"
queryArgsToMenuFields={{ language: "language" }}
>
{children}
</ThomasBernhardInstantSearchProvider>
);
}
1 change: 1 addition & 0 deletions components/instantsearch/single-refinement-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useMenu, type UseMenuProps } from "react-instantsearch";
interface SingleRefinementListProps {
attribute: string;
allLabel?: string;
pathname?: string;
refinementArgs?: Partial<UseMenuProps>;
className?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ export function ThomasBernhardInstantSearchProvider(
props: ThomasBernhardInstantSearchProviderProps,
): ReactNode {
const { children, filters, hitsPerPage = 30 } = props;
const filter = filters
? // '&&' is typesense convention, not instantsearch!
`erstpublikation:true && ${filters}`
: "erstpublikation:true";

// '&&' is typesense convention, not instantsearch!
const filter = `erstpublikation:true ${filters ? `&& ${filters}` : ""}`;
return (
<InstantSearchProvider
collectionName={collectionName}
Expand Down

0 comments on commit e469b67

Please sign in to comment.