Skip to content

Commit

Permalink
feat(blog): add search feature (#61)
Browse files Browse the repository at this point in the history
* feat(blog): add search feature

Signed-off-by: james-a-morris <[email protected]>

* chore: keyword search

Signed-off-by: james-a-morris <[email protected]>

---------

Signed-off-by: james-a-morris <[email protected]>
  • Loading branch information
james-a-morris authored Sep 6, 2024
1 parent 3c96d06 commit e8f474b
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/app/(routes)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ReactNode } from "react";
import { twMerge } from "tailwind-merge";
import Divider from "./_components/divider";
import ArticleContent from "./_components/article-content";
import BackgroundBanner from "../background-banner";
import BackgroundBanner from "../_components/background-banner";
import Breadcrumb from "./_components/breadcrumb";
import { MetaInfo } from "./_components/meta-info";
import BackToTopButton from "../_components/back-to-top-button";
Expand Down
1 change: 0 additions & 1 deletion src/app/(routes)/blog/_components/article-snippet-card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { BlogPostType } from "@/app/_lib/contentful";
import { Text } from "@/app/_components/text";
import { documentToPlainTextString } from "@contentful/rich-text-plain-text-renderer";
import Link from "next/link";
import ContentfulImage from "../[slug]/_components/contentful-image";
import { MetaInfo } from "../[slug]/_components/meta-info";
Expand Down
File renamed without changes.
62 changes: 62 additions & 0 deletions src/app/(routes)/blog/_components/posts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Text } from "@/app/_components/text";
import ArticleSnippetCard from "./article-snippet-card";
import ArticleFullCard from "./article-full-card";
import {
BlogPostWithRelevantEntries,
retrieveContentfulPublishedSlugs,
} from "@/app/_lib/contentful";
import { SearchParams } from "../page";

export async function Posts({
getStartedSnippets,
recentArticleSlugs,
searchParams,
}: {
searchParams: SearchParams;
recentArticleSlugs: string[];
getStartedSnippets: (BlogPostWithRelevantEntries | undefined)[];
}) {
const search = searchParams["search"];
if (!search) {
return (
<>
<div className="flex w-full flex-col gap-4">
<Text variant="body" className="text-grey-400">
Get started with Across
</Text>
<div className="w-full overflow-x-scroll scrollbar-hide">
<div className="grid w-[1024px] grid-cols-3 gap-5 md:w-full">
{getStartedSnippets.slice(0, 3).map((snippet) => (
<div className="w-full" key={snippet?.fields.slug}>
<ArticleSnippetCard article={snippet!} />
</div>
))}
</div>
</div>
</div>
<div className="flex w-full flex-col gap-4">
<Text variant="body" className="text-grey-400">
Most recent articles
</Text>
{recentArticleSlugs.map((slug) => (
<ArticleFullCard key={slug} slug={slug} />
))}
</div>
</>
);
} else {
const searchedSlugs = await retrieveContentfulPublishedSlugs({
query: search,
});
return (
<div className="flex w-full flex-col gap-4">
<Text variant="body" className="text-grey-400">
Search results
</Text>
{searchedSlugs.map((slug) => (
<ArticleFullCard key={slug} slug={slug} />
))}
</div>
);
}
}
59 changes: 30 additions & 29 deletions src/app/(routes)/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { Text } from "@/app/_components/text";
import BackToTopButton from "./_components/back-to-top-button";
import BackgroundBanner from "./background-banner";
import BackgroundBanner from "./_components/background-banner";
import Filter from "./_components/filter";

import { Suspense } from "react";
import {
retrieveContentfulEntry,
retrieveContentfulPublishedSlugs,
retrieveContentfulEntry,
} from "@/app/_lib/contentful";
import ArticleFullCard from "./_components/article-full-card";
import ArticleSnippetCard from "./_components/article-snippet-card";
import { Suspense } from "react";
import { Posts } from "./_components/posts";
import { createCacheKey } from "@/app/_lib/cache";

export default async function BlogHomePage() {
export type SearchParams = Record<string, string | undefined>;

type PageProps = {
searchParams: SearchParams;
};

export default async function BlogHomePage({ searchParams }: PageProps) {
const recentArticleSlugs = await retrieveContentfulPublishedSlugs({
limit: 6,
avoidTags: ["get-started"],
Expand All @@ -23,6 +30,10 @@ export default async function BlogHomePage() {
getStartedSlugs.map((s) => retrieveContentfulEntry(s)),
);

const key = createCacheKey({
searchParams,
});

return (
<>
<BackgroundBanner />
Expand All @@ -33,29 +44,19 @@ export default async function BlogHomePage() {
<Suspense>
<Filter />
</Suspense>
<div className="flex w-full flex-col gap-4">
<Text variant="body" className="text-grey-400">
Get started with Across
</Text>
<div className="w-full overflow-x-scroll scrollbar-hide">
<div className="grid w-[1024px] grid-cols-3 gap-5 md:w-full">
{getStartedSnippets.slice(0, 3).map((snippet) => (
<div className="w-full" key={snippet?.fields.slug}>
<ArticleSnippetCard article={snippet!} />
</div>
))}
</div>
</div>
</div>
<div className="flex w-full flex-col gap-4">
<Text variant="body" className="text-grey-400">
Most recent articles
</Text>
{recentArticleSlugs.map((slug) => (
<ArticleFullCard key={slug} slug={slug} />
))}
</div>
<BackToTopButton />
<Suspense
key={key}
fallback={
<h2 className="text-text-secondary text-2xl my-auto flex-1">Searching...</h2>
}
>
<Posts
getStartedSnippets={getStartedSnippets}
recentArticleSlugs={recentArticleSlugs}
searchParams={searchParams}
/>
<BackToTopButton />
</Suspense>
</main>
</>
);
Expand Down
13 changes: 13 additions & 0 deletions src/app/_lib/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SearchParams } from "../(routes)/blog/page";

export function createCacheKey(options: { searchParams: SearchParams }) {
const { searchParams } = options;
const newParamString = new URLSearchParams();
Object.entries(searchParams).forEach(([key, value]) => {
if (typeof value === "string") {
newParamString.set(key, value);
}
});

return newParamString.toString();
}
6 changes: 5 additions & 1 deletion src/app/_lib/contentful.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export type BlogPostType = Entry<
string
>;

export type BlogPostWithRelevantEntries = BlogPostType & {
relevantEntries: BlogPostType[];
};

function getProductionClient() {
return createClient({
space: CONTENTFUL_SPACE_ID ?? "",
Expand Down Expand Up @@ -72,7 +76,7 @@ export async function retrieveContentfulPublishedSlugs({
export async function retrieveContentfulEntry(
entrySlugId: string,
relevantEntryCount = 4,
) {
): Promise<BlogPostWithRelevantEntries | undefined> {
const client = getProductionClient();
const options = {
content_type: contentType,
Expand Down

0 comments on commit e8f474b

Please sign in to comment.