grain.social is a photo sharing platform built on atproto.
1import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts";
2import { BffContext, RouteHandler } from "@bigmoves/bff";
3import { ActorInfo } from "../components/ActorInfo.tsx";
4import { Breadcrumb } from "../components/Breadcrumb.tsx";
5import { GalleryPreviewLink } from "../components/GalleryPreviewLink.tsx";
6import { Header } from "../components/Header.tsx";
7import { RenderFacetedText } from "../components/RenderFacetedText.tsx";
8import { getGalleriesByHashtag } from "../lib/gallery.ts";
9import { State } from "../state.ts";
10
11export const handler: RouteHandler = (
12 _req,
13 params,
14 ctx: BffContext<State>,
15) => {
16 const tag = params.tag;
17
18 const galleries = getGalleriesByHashtag(tag, ctx);
19
20 ctx.state.meta = [{ title: `Hashtag — Grain` }];
21
22 return ctx.render(
23 <div class="p-4 flex flex-col gap-4">
24 <Breadcrumb
25 class="m-0"
26 items={[{ label: "home", href: "/" }, {
27 label: tag,
28 }]}
29 />
30 <Header>#{tag}</Header>
31
32 {galleries.length > 0
33 ? (
34 <div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
35 {galleries.map((gallery) => (
36 <HashtagGalleryItem gallery={gallery} key={gallery.uri} />
37 ))}
38 </div>
39 )
40 : <div>No galleries found.</div>}
41 </div>,
42 );
43};
44
45function HashtagGalleryItem(
46 { gallery }: Readonly<{
47 gallery: GalleryView;
48 }>,
49) {
50 const title = gallery.title;
51 const description = gallery.description;
52 const facets = gallery.facets || [];
53 return (
54 <div class="flex flex-col gap-2" key={gallery.uri}>
55 <ActorInfo profile={gallery.creator} />
56 <GalleryPreviewLink gallery={gallery} />
57 <div class="font-semibold">
58 {title}
59 </div>
60 {description && (
61 <p class="text-sm text-zinc-600 dark:text-zinc-500">
62 {facets && Array.isArray(facets) &&
63 facets.length > 0
64 ? (
65 <RenderFacetedText
66 text={description}
67 facets={facets}
68 />
69 )
70 : description}
71 </p>
72 )}
73 </div>
74 );
75}