From 90e9ddb86a290720903739e3d9631597564e9358 Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 11:16:10 +0100 Subject: [PATCH 01/11] refactor(contentFetchers): expand fetching to categories collection Changes fetching structure to include type and target, expanding fetching to categories to facilitate pulling content to populate "content by category" pages. Adds a slug field to categories collection to enable fetching category by slug from its respective page. Updates function calling in ./authors/[slug]/page.tsx --- src/app/(pages)/authors/[slug]/page.tsx | 8 ++++++-- src/app/_utilities/contentFetchers.ts | 17 ++++++++++++----- src/collections/Categories.ts | 2 ++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/app/(pages)/authors/[slug]/page.tsx b/src/app/(pages)/authors/[slug]/page.tsx index 3a48386..d466a13 100644 --- a/src/app/(pages)/authors/[slug]/page.tsx +++ b/src/app/(pages)/authors/[slug]/page.tsx @@ -1,4 +1,4 @@ -import { fetchContentFromAuthor } from '@/app/_utilities/contentFetchers' +import { fetchContentFromAuthorOrCategory } from '@/app/_utilities/contentFetchers' import AuthorContentGrid from '../../../_blocks/AuthorContentGrid' import { Subscribe } from '../../../_blocks/Subscribe' import AuthorSummary from '../../../_components/AuthorSummary' @@ -22,7 +22,11 @@ const headerStyle = { export default async function ContributorPage({ params: paramsPromise }) { const { slug } = await paramsPromise const author = await fetchContentBySlug({ slug: slug, type: 'authors' }) - const contentFromAuthor = await fetchContentFromAuthor(author) + + // Given payload typing structure an error is expected as we're passing multiple possible types + // to a function that only accepts 2 (Authors | Categories). + // @ts-expect-error + const contentFromAuthor = await fetchContentFromAuthorOrCategory({ type: 'authors', target: author }) return (
diff --git a/src/app/_utilities/contentFetchers.ts b/src/app/_utilities/contentFetchers.ts index 6edfaa4..48f520d 100644 --- a/src/app/_utilities/contentFetchers.ts +++ b/src/app/_utilities/contentFetchers.ts @@ -1,6 +1,6 @@ import { getPayloadHMR } from "@payloadcms/next/utilities"; import configPromise from "@payload-config"; -import type { Author, Blogpost, CaseStudy, Config, Media, Podcast, TalksAndRoundtable } from "@/payload-types"; +import type { Author, Blogpost, CaseStudy, Category, Config, Media, Podcast, TalksAndRoundtable } from "@/payload-types"; import { notFound } from "next/navigation"; import { CollectionSlug, getPayload } from "payload"; import { draftMode } from "next/headers"; @@ -28,13 +28,13 @@ async function fetcher({ collection, limit = 10, depth = 1, draft = false, overr }); } -export async function fetchContentBySlug({ slug, type, depth }: { slug: string, type: CollectionSlug, depth?: number }) { +export async function fetchContentBySlug({ slug, type, depth }: { slug: string, type: CollectionSlug, depth?: number }) { if (!slug || !type) { throw new Error("Must input slug and/or type."); } - const { isEnabled: draft } = await draftMode() + const { isEnabled: draft } = await draftMode(); const query = { slug: { equals: slug } }; @@ -54,9 +54,16 @@ export async function fetchContentBySlug({ slug, type, depth }: { slug: string, } -export async function fetchContentFromAuthor(author) { +export async function fetchContentFromAuthorOrCategory({ type, target }: { type: string, target: Author | Category }) { + let query = {} - const query = { authors: { in: author.id } }; + if (type === "author") { + query = { authors: { in: target.id } }; + } + + if (type === "category") { + query = { categories: { in: target.id } }; + } const blogposts = await fetcher({ collection: "blogposts", diff --git a/src/collections/Categories.ts b/src/collections/Categories.ts index 9ba5864..09f4e05 100644 --- a/src/collections/Categories.ts +++ b/src/collections/Categories.ts @@ -2,6 +2,7 @@ import type { CollectionConfig } from 'payload' import { anyone } from '../access/anyone' import { authenticated } from '../access/authenticated' +import { slugField } from "@/fields/slug"; const Categories: CollectionConfig = { slug: 'categories', @@ -20,6 +21,7 @@ const Categories: CollectionConfig = { type: 'text', required: true, }, + ...slugField(), ], } From d3bf946203925bd61aa32edd2080680ff70c3ffa Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 11:31:03 +0100 Subject: [PATCH 02/11] refactor: rename AuthorContentGrid to ContentGrid Renames the AuthorContentGrid component to ContentGrid as it will be used in "contents by category" page. --- .../_blocks/{AuthorContentGrid => ContentGrid}/index.tsx | 8 +------- .../{AuthorContentGrid => ContentGrid}/styles.module.css | 0 2 files changed, 1 insertion(+), 7 deletions(-) rename src/app/_blocks/{AuthorContentGrid => ContentGrid}/index.tsx (80%) rename src/app/_blocks/{AuthorContentGrid => ContentGrid}/styles.module.css (100%) diff --git a/src/app/_blocks/AuthorContentGrid/index.tsx b/src/app/_blocks/ContentGrid/index.tsx similarity index 80% rename from src/app/_blocks/AuthorContentGrid/index.tsx rename to src/app/_blocks/ContentGrid/index.tsx index 374c4ea..3529c65 100644 --- a/src/app/_blocks/AuthorContentGrid/index.tsx +++ b/src/app/_blocks/ContentGrid/index.tsx @@ -1,14 +1,8 @@ -import { - Blogpost, - CaseStudy, - Podcast, - TalksAndRoundtable, -} from "@/payload-types"; import ContentCard from "../../_components/ContentCard"; import { calculateTotalArticles } from "../../_utilities/calculateTotalArticles"; import styles from "./styles.module.css"; -export default function AuthorContentGrid({ content }) { +export default function ContentGrid({ content }) { return (
diff --git a/src/app/_blocks/AuthorContentGrid/styles.module.css b/src/app/_blocks/ContentGrid/styles.module.css similarity index 100% rename from src/app/_blocks/AuthorContentGrid/styles.module.css rename to src/app/_blocks/ContentGrid/styles.module.css From 72f289fc37e2d5d5a5b7193929361f49fbd8f69a Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 11:49:01 +0100 Subject: [PATCH 03/11] fix: total article calculation logic issue Simplifies and fixes total article calculation logic to loop through the different content types and increment its lengts. Closes #33 --- src/app/_utilities/calculateTotalArticles.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/_utilities/calculateTotalArticles.ts b/src/app/_utilities/calculateTotalArticles.ts index e5c1e72..07a0133 100644 --- a/src/app/_utilities/calculateTotalArticles.ts +++ b/src/app/_utilities/calculateTotalArticles.ts @@ -11,7 +11,11 @@ export function calculateTotalArticles(content: { CaseStudies: CaseStudy[] TalksAndRoundtables: TalksAndRoundtable[] }): number { - return Object.values(content).filter( - innerArray => Array.isArray(innerArray) && innerArray.length > 0, - ).length + let contentCount = 0 + for (const type in content) { + if (content[type].length > 0) { + contentCount += content[type].length + } + } + return contentCount } From de512a7a1ddf13cb4a08a27654ef1085bab3a050 Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 11:59:02 +0100 Subject: [PATCH 04/11] fix: typo in content fetching Also adds extra typing to fetchContentFromAuthorOrCategory 'type' parameter --- src/app/(pages)/authors/[slug]/page.tsx | 6 +++--- src/app/_utilities/contentFetchers.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/(pages)/authors/[slug]/page.tsx b/src/app/(pages)/authors/[slug]/page.tsx index d466a13..3a608b9 100644 --- a/src/app/(pages)/authors/[slug]/page.tsx +++ b/src/app/(pages)/authors/[slug]/page.tsx @@ -1,5 +1,5 @@ import { fetchContentFromAuthorOrCategory } from '@/app/_utilities/contentFetchers' -import AuthorContentGrid from '../../../_blocks/AuthorContentGrid' +import ContentGrid from '../../../_blocks/ContentGrid' import { Subscribe } from '../../../_blocks/Subscribe' import AuthorSummary from '../../../_components/AuthorSummary' import BackButton from '../../../_components/BackButton' @@ -26,7 +26,7 @@ export default async function ContributorPage({ params: paramsPromise }) { // Given payload typing structure an error is expected as we're passing multiple possible types // to a function that only accepts 2 (Authors | Categories). // @ts-expect-error - const contentFromAuthor = await fetchContentFromAuthorOrCategory({ type: 'authors', target: author }) + const contentFromAuthor = await fetchContentFromAuthorOrCategory({ type: 'author', target: author }) return (
@@ -35,7 +35,7 @@ export default async function ContributorPage({ params: paramsPromise }) {
- +
diff --git a/src/app/_utilities/contentFetchers.ts b/src/app/_utilities/contentFetchers.ts index 48f520d..bd97218 100644 --- a/src/app/_utilities/contentFetchers.ts +++ b/src/app/_utilities/contentFetchers.ts @@ -54,7 +54,7 @@ export async function fetchContentBySlug({ slug, type, depth }: { slug: string, } -export async function fetchContentFromAuthorOrCategory({ type, target }: { type: string, target: Author | Category }) { +export async function fetchContentFromAuthorOrCategory({ type, target }: { type: 'author' | 'category', target: Author | Category }) { let query = {} if (type === "author") { From ae2374e7b4860d764e2409c7229bfa5d510ced2c Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 12:06:55 +0100 Subject: [PATCH 05/11] feat(category page): add category content page Adds Category pages that fetches content per category, based on slug, and displays in grid as well as pointing to other existing categories. Currently displays all categories but can be changed to related / parent / child categories in the feature --- src/app/(pages)/categories/[slug]/page.tsx | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/app/(pages)/categories/[slug]/page.tsx diff --git a/src/app/(pages)/categories/[slug]/page.tsx b/src/app/(pages)/categories/[slug]/page.tsx new file mode 100644 index 0000000..9b4c7b1 --- /dev/null +++ b/src/app/(pages)/categories/[slug]/page.tsx @@ -0,0 +1,51 @@ +import { fetchAllContentByType, fetchContentBySlug, fetchContentFromAuthorOrCategory } from "@/app/_utilities/contentFetchers"; +import ContentGrid from "src/app/_blocks/ContentGrid"; +import { Header } from "@/app/_components/Header"; +import BackButton from "@/app/_components/BackButton"; +import { Category } from "@/payload-types"; +import { Subscribe } from "@/app/_blocks/Subscribe"; +import CategoryPill from "@/app/_components/CategoryPill"; + +const headerStyle = { + '--dynamic-background': 'var(--sub-blue-100)', + '--dynamic-color': 'var(--dark-rock-800)', + '--dynamic-width': 'calc(100% - 40px)', +} + +export default async function CategoryPage({params: paramsPromise}) { + const { slug } = await paramsPromise + + const category = await fetchContentBySlug({ + slug: slug, + type: 'categories' + }) as Category + + const content = await fetchContentFromAuthorOrCategory({type: 'category', target: category}) + + const otherCategories = await fetchAllContentByType('categories').then((res: Category[]) => res.map((item: Category) => item.title)) + + return ( +
+
+ + {/* Head block*/} +
+ +

{category.title}

+
+ + {/* Content Grid */} +
+ +
+

Recommended categories

+ {otherCategories.map(category => ( + + ))} +
+
+ + +
+ ) +} From fea3f9570706f5fb3aae32ff703192819429f130 Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 12:26:20 +0100 Subject: [PATCH 06/11] refactor(content grid): make article count exhibition optional --- src/app/_blocks/ContentGrid/index.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app/_blocks/ContentGrid/index.tsx b/src/app/_blocks/ContentGrid/index.tsx index 3529c65..868941d 100644 --- a/src/app/_blocks/ContentGrid/index.tsx +++ b/src/app/_blocks/ContentGrid/index.tsx @@ -2,17 +2,20 @@ import ContentCard from "../../_components/ContentCard"; import { calculateTotalArticles } from "../../_utilities/calculateTotalArticles"; import styles from "./styles.module.css"; -export default function ContentGrid({ content }) { +export default function ContentGrid({ content, showCount = true }) { return (
-
- {calculateTotalArticles(content)} Articles -
+ + {showCount && ( +
+ {calculateTotalArticles(content)} Articles +
+ )}
{Object.keys(content).map(key => content[key].map((contentPiece, i) => ( - + )), )}
From 5f065524854217fdf65064ea504e28a64817b4f6 Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 12:28:47 +0100 Subject: [PATCH 07/11] style: implement category page styling --- src/app/(pages)/categories/[slug]/page.tsx | 73 ++++++++++--------- .../categories/[slug]/styles.module.css | 25 +++++++ 2 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 src/app/(pages)/categories/[slug]/styles.module.css diff --git a/src/app/(pages)/categories/[slug]/page.tsx b/src/app/(pages)/categories/[slug]/page.tsx index 9b4c7b1..25b49e2 100644 --- a/src/app/(pages)/categories/[slug]/page.tsx +++ b/src/app/(pages)/categories/[slug]/page.tsx @@ -1,51 +1,54 @@ -import { fetchAllContentByType, fetchContentBySlug, fetchContentFromAuthorOrCategory } from "@/app/_utilities/contentFetchers"; -import ContentGrid from "src/app/_blocks/ContentGrid"; -import { Header } from "@/app/_components/Header"; -import BackButton from "@/app/_components/BackButton"; -import { Category } from "@/payload-types"; -import { Subscribe } from "@/app/_blocks/Subscribe"; -import CategoryPill from "@/app/_components/CategoryPill"; +import { fetchAllContentByType, fetchContentBySlug, fetchContentFromAuthorOrCategory } from '@/app/_utilities/contentFetchers'; +import ContentGrid from '@/app/_blocks/ContentGrid'; +import { Header } from '@/app/_components/Header'; +import BackButton from '@/app/_components/BackButton'; +import { Category } from '@/payload-types'; +import { Subscribe } from '@/app/_blocks/Subscribe'; +import CategoryPill from '@/app/_components/CategoryPill'; +import styles from './styles.module.css'; const headerStyle = { '--dynamic-background': 'var(--sub-blue-100)', '--dynamic-color': 'var(--dark-rock-800)', '--dynamic-width': 'calc(100% - 40px)', -} +}; -export default async function CategoryPage({params: paramsPromise}) { - const { slug } = await paramsPromise +export default async function CategoryPage({ params: paramsPromise }) { + const { slug } = await paramsPromise; const category = await fetchContentBySlug({ slug: slug, - type: 'categories' - }) as Category + type: 'categories', + }) as Category; - const content = await fetchContentFromAuthorOrCategory({type: 'category', target: category}) + const content = await fetchContentFromAuthorOrCategory({ type: 'category', target: category }); - const otherCategories = await fetchAllContentByType('categories').then((res: Category[]) => res.map((item: Category) => item.title)) + const otherCategories = await fetchAllContentByType('categories').then((res: Category[]) => res.map((item: Category) => item.title)); return ( -
-
- - {/* Head block*/} -
- -

{category.title}

-
- - {/* Content Grid */} -
- -
-

Recommended categories

- {otherCategories.map(category => ( - - ))} -
+
+
+ + {/* Head block*/} +
+ +

{category.title}

+
+ + {/* Content Grid */} +
+ +
+

Recommended categories

+
+ {otherCategories.map(category => ( + + ))}
- -
- ) +
+ + +
+ ); } diff --git a/src/app/(pages)/categories/[slug]/styles.module.css b/src/app/(pages)/categories/[slug]/styles.module.css new file mode 100644 index 0000000..3be2817 --- /dev/null +++ b/src/app/(pages)/categories/[slug]/styles.module.css @@ -0,0 +1,25 @@ +@media (min-width: 1024px) { + .headBlock { + background: var(--sub-blue-100); + width: calc(100% - 39px); + display: flex; + flex-direction: column; + gap: 30px; + margin: auto; + padding: 40px 100px; + border-radius: 0 0 100px 100px; + } + + .contentContainer { + display: grid; + grid-template-columns: 0.7fr 0.3fr; + padding: 60px; + } + + .otherCategories { + display: flex; + flex-wrap: wrap; + margin-top: 20px; + gap: 20px; + } +} From 3dbe6527282c07dfced5cd1f70d193300aa56745 Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 12:29:15 +0100 Subject: [PATCH 08/11] chore(payload-types.ts): regenerate payload types --- src/payload-types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/payload-types.ts b/src/payload-types.ts index a759f69..efdeab3 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -97,6 +97,8 @@ export interface Media { export interface Category { id: string; title: string; + slug?: string | null; + slugLock?: boolean | null; parent?: (string | null) | Category; breadcrumbs?: | { From 65d4f0f577697c8b9520ec7683659820b2c65d15 Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 12:46:37 +0100 Subject: [PATCH 09/11] refactor(category pill): enable linking to category page Enables category pill to link directly to the category content page. Updates SearchBar to disable link function in order to retain filtering behaviour. --- src/app/_components/CategoryPill/index.tsx | 62 +++++++++++++++------- src/app/_components/SearchBar/index.tsx | 1 + 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/app/_components/CategoryPill/index.tsx b/src/app/_components/CategoryPill/index.tsx index 7362e7f..8517aa8 100644 --- a/src/app/_components/CategoryPill/index.tsx +++ b/src/app/_components/CategoryPill/index.tsx @@ -2,39 +2,65 @@ import styles from "./styles.module.css"; import { CloseIcon } from "@/app/_icons/icons"; import React, { useState } from "react"; +import Link from "next/link"; +import { toKebabCase } from "@/app/_utilities/toKebabCase"; interface CategoryPillProps { title: string, id?: string, selected?: boolean, + enableLink?: boolean, setActiveFilter?: React.Dispatch> setActiveCategory?: React.Dispatch> } -export default function CategoryPill({ title, id, selected = false, setActiveFilter, setActiveCategory }: CategoryPillProps) { +export default function CategoryPill({ title, id, selected = false, setActiveFilter, setActiveCategory, enableLink = true }: CategoryPillProps) { const dynamicStyle = { "--dynamic-color": selected ? "var(--sub-blue-300)" : "var(--sub-blue-100)", } as React.CSSProperties; return ( -
- {title} - {selected && ( - - )} -
+
+ {title} + {selected && ( + + )} +
+ ) : ( +
+ {title} + {selected && ( + + )} +
+ ) ); } diff --git a/src/app/_components/SearchBar/index.tsx b/src/app/_components/SearchBar/index.tsx index 09f2f1b..2345a08 100644 --- a/src/app/_components/SearchBar/index.tsx +++ b/src/app/_components/SearchBar/index.tsx @@ -172,6 +172,7 @@ export default function SearchBar({ currentContent, highlights, categories }) { selected={filtering && activeCategory === category} setActiveFilter={setFiltering} setActiveCategory={setActiveCategory} + enableLink={false} /> ))} From ea8c0a7ecc6567548d7a392e71bdb93b8d26c233 Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 14:26:00 +0100 Subject: [PATCH 10/11] style(category page): add mobile version styling --- .../categories/[slug]/styles.module.css | 33 +++++++++++++++++-- src/app/_blocks/ContentGrid/styles.module.css | 6 ++-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/app/(pages)/categories/[slug]/styles.module.css b/src/app/(pages)/categories/[slug]/styles.module.css index 3be2817..2aaed14 100644 --- a/src/app/(pages)/categories/[slug]/styles.module.css +++ b/src/app/(pages)/categories/[slug]/styles.module.css @@ -1,13 +1,40 @@ +.headBlock { + background: var(--sub-blue-100); + width: 100%; + display: flex; + flex-direction: column; + gap: 30px; + padding: 30px 16px; + border-radius: 0 0 16px 16px; +} + +.contentContainer { + display: flex; + flex-direction: column; + gap: 20px; +} + +.otherCategoriesContainer { + padding: 16px; + font-weight: bold; + +} + +.otherCategories { + display: flex; + flex-wrap: wrap; + gap: 20px; + padding-top: 20px; +} + @media (min-width: 1024px) { .headBlock { background: var(--sub-blue-100); width: calc(100% - 39px); - display: flex; - flex-direction: column; gap: 30px; margin: auto; padding: 40px 100px; - border-radius: 0 0 100px 100px; + border-radius: 0 0 45px 45px; } .contentContainer { diff --git a/src/app/_blocks/ContentGrid/styles.module.css b/src/app/_blocks/ContentGrid/styles.module.css index dda8956..cb9904c 100644 --- a/src/app/_blocks/ContentGrid/styles.module.css +++ b/src/app/_blocks/ContentGrid/styles.module.css @@ -14,9 +14,9 @@ .contentGrid { display: grid; grid-template-columns: repeat(auto-fill, 1fr); - margin: 0 auto; - padding-left: 40px; - padding-right: 40px; + margin: auto; + padding: 16px; + gap: 16px; } .contentCard { From 8053c6c258a06ea836bca37e366e1e038fe8b5ed Mon Sep 17 00:00:00 2001 From: Rui Sousa Date: Fri, 25 Oct 2024 15:14:47 +0100 Subject: [PATCH 11/11] fix: restructure links --- src/app/_components/CategoryPill/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/_components/CategoryPill/index.tsx b/src/app/_components/CategoryPill/index.tsx index 8517aa8..1b96019 100644 --- a/src/app/_components/CategoryPill/index.tsx +++ b/src/app/_components/CategoryPill/index.tsx @@ -22,7 +22,7 @@ export default function CategoryPill({ title, id, selected = false, setActiveFil return ( enableLink ? ( - +
{title}