diff --git a/apps/codeforafrica/payload.config.ts b/apps/codeforafrica/payload.config.ts index 83c3e6173..02820279d 100644 --- a/apps/codeforafrica/payload.config.ts +++ b/apps/codeforafrica/payload.config.ts @@ -17,8 +17,9 @@ import Members from "./src/payload/collections/Members"; import Pages from "./src/payload/collections/Pages"; import Partners from "./src/payload/collections/Partners"; import Posts from "./src/payload/collections/Posts"; +import Publication from "./src/payload/globals/Publication"; import Projects from "./src/payload/collections/Projects"; -import Settings from "./src/payload/globals/Settings"; +import Site from "./src/payload/globals/Site"; import Tags from "./src/payload/collections/Tags"; import Teams from "./src/payload/collections/Teams"; import Users from "./src/payload/collections/Users"; @@ -67,7 +68,7 @@ export default buildConfig({ Teams, Users, ] as CollectionConfig[], - globals: [Settings] as GlobalConfig[], + globals: [Publication, Site] as GlobalConfig[], ...(locales?.length ? { localization: { diff --git a/apps/codeforafrica/src/components/Articles/Articles.js b/apps/codeforafrica/src/components/Articles/Articles.js index 8576ae156..f2950a146 100644 --- a/apps/codeforafrica/src/components/Articles/Articles.js +++ b/apps/codeforafrica/src/components/Articles/Articles.js @@ -35,7 +35,7 @@ const Articles = React.forwardRef(function Articles(props, ref) { const [q, setQ] = useState(); const [filtering, setFiltering] = useState(false); const [tag, setTag] = useState(allTag); - const queryParams = useFilterQuery({ page, q, tag: tag.slug }); + const queryParams = useFilterQuery({ page, q, tag }); const router = useRouter(); @@ -59,7 +59,13 @@ const Articles = React.forwardRef(function Articles(props, ref) { setFiltering(isFiltering); }, [page, q, tag]); - const { data } = useArticles({ page, q, tag: tag.slug }, primaryTag); + const { data } = useArticles( + { page, q, tag }, + { + primaryTag, + featured: !filtering && featuredArticle ? featuredArticle.slug : null, + }, + ); useEffect(() => { if (data) { const { posts: results, pagination } = data; diff --git a/apps/codeforafrica/src/components/Articles/useArticles.js b/apps/codeforafrica/src/components/Articles/useArticles.js index 0db0cbf37..3fa395ce8 100644 --- a/apps/codeforafrica/src/components/Articles/useArticles.js +++ b/apps/codeforafrica/src/components/Articles/useArticles.js @@ -4,11 +4,13 @@ import useFilterQuery from "@/codeforafrica/components/useFilterQuery"; const fetcher = (url) => fetch(url).then((res) => res.json()); -function useArticles(params, primaryTag) { +function useArticles(params, { primaryTag, featured }) { const queryParams = useFilterQuery(params); - const query = queryParams - ? `${queryParams}&primaryTag=${primaryTag}` - : `?primaryTag=${primaryTag}`; + const separator = queryParams ? "" : "?"; + const filterQuery = queryParams ? `${queryParams}&` : ""; + const primaryTagQuery = `primaryTag=${primaryTag}`; + const featuredQuery = featured ? `&featured=${featured}` : ""; + const query = `${separator}${filterQuery}${primaryTagQuery}${featuredQuery}`; const { data, error } = useSWR(`/api/v1/posts${query}`, fetcher); return { diff --git a/apps/codeforafrica/src/components/Opportunities/Opportunities.js b/apps/codeforafrica/src/components/Opportunities/Opportunities.js index 93b5d8e9a..d98f9585e 100644 --- a/apps/codeforafrica/src/components/Opportunities/Opportunities.js +++ b/apps/codeforafrica/src/components/Opportunities/Opportunities.js @@ -36,7 +36,7 @@ const Opportunities = React.forwardRef(function Opportunities( const [opportunities, setOpportunities] = useState(opportunitiesList); const [q, setQ] = useState(); const [tag, setTag] = useState(allTag); - const queryParams = useFilterQuery({ page, q, tag: tag.slug }); + const queryParams = useFilterQuery({ page, q, tag }); const router = useRouter(); const handleChangePage = (_, value) => { @@ -54,7 +54,7 @@ const Opportunities = React.forwardRef(function Opportunities( setPage(1); }; - const { data } = useOpportunities({ page, q, tag: tag.slug }, primaryTag); + const { data } = useOpportunities({ page, q, tag }, primaryTag); useEffect(() => { if (data) { const { posts: results, pagination } = data; diff --git a/apps/codeforafrica/src/components/RelatedStories/RelatedStories.js b/apps/codeforafrica/src/components/RelatedStories/RelatedStories.js index 52a2e731d..928d22570 100644 --- a/apps/codeforafrica/src/components/RelatedStories/RelatedStories.js +++ b/apps/codeforafrica/src/components/RelatedStories/RelatedStories.js @@ -5,9 +5,9 @@ import React from "react"; import ArticleCardList from "@/codeforafrica/components/ArticleCardList"; const RelatedStories = React.forwardRef(function RelatedStories(props, ref) { - const { articles, sx, title } = props; + const { posts, sx, title } = props; - if (!articles?.length) { + if (!posts?.length) { return null; } return ( @@ -25,10 +25,7 @@ const RelatedStories = React.forwardRef(function RelatedStories(props, ref) { > {title} - + ); }); diff --git a/apps/codeforafrica/src/components/RelatedStories/RelatedStories.snap.js b/apps/codeforafrica/src/components/RelatedStories/RelatedStories.snap.js index db5aa7a87..94b062097 100644 --- a/apps/codeforafrica/src/components/RelatedStories/RelatedStories.snap.js +++ b/apps/codeforafrica/src/components/RelatedStories/RelatedStories.snap.js @@ -1,3 +1,54 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` renders unchanged 1`] = `
`; +exports[` renders unchanged 1`] = ` + +`; diff --git a/apps/codeforafrica/src/components/RelatedStories/RelatedStories.test.js b/apps/codeforafrica/src/components/RelatedStories/RelatedStories.test.js index 5b45158fc..4742aa706 100644 --- a/apps/codeforafrica/src/components/RelatedStories/RelatedStories.test.js +++ b/apps/codeforafrica/src/components/RelatedStories/RelatedStories.test.js @@ -10,6 +10,20 @@ const render = createRender({ theme }); const defaultProps = { title: "Related Stories", + posts: [ + { + title: "Battle for gender equality in African media continues", + excerpt: + "Lorem ipsum dolor sit amet consectetur adipiscing elit mattis, vestibulum potenti rhoncus eget lacus fermentum taciti quam, quis curae accumsan viverra semper dapibus sed.", + publishedOn: "Jan 6, 2022", + image: { + src: "https://res.cloudinary.com/code-for-africa/image/upload/v1650885664/codeforafrica/unsplash_L6hr1BptcNc_of23p3.png", + alt: "Featured Article Image", + }, + readMoreLabel: "Read Story", + href: "/stories/article-1", + }, + ], }; describe("", () => { diff --git a/apps/codeforafrica/src/lib/data/blockify/posts.js b/apps/codeforafrica/src/lib/data/blockify/posts.js index 11463d6da..bfbbf0253 100644 --- a/apps/codeforafrica/src/lib/data/blockify/posts.js +++ b/apps/codeforafrica/src/lib/data/blockify/posts.js @@ -11,6 +11,13 @@ async function posts(block, api, context) { const featured = featuredStory ? formatPost(featuredStory, primaryTag) : null; const options = { ...query, + ...(featured && { + where: { + slug: { + not_equals: featured.slug, + }, + }, + }), }; // rename post to fix eslint no-shadow const { posts: list, pagination } = await getPosts(api, options, primaryTag); diff --git a/apps/codeforafrica/src/lib/data/common/index.js b/apps/codeforafrica/src/lib/data/common/index.js index 8858787a9..92394bef0 100644 --- a/apps/codeforafrica/src/lib/data/common/index.js +++ b/apps/codeforafrica/src/lib/data/common/index.js @@ -172,11 +172,11 @@ export async function getPageProps(api, context) { } } const blocks = await blockify(page?.blocks, api, context); - const settings = await api.findGlobal("settings"); - const navbar = getNavBar(settings); - const footer = getFooter(settings); + const siteSettings = await api.findGlobal("settings-site"); + const navbar = getNavBar(siteSettings); + const footer = getFooter(siteSettings); - const seo = getPageSeoFromMeta(page, settings); + const seo = getPageSeoFromMeta(page, siteSettings); return { blocks, footer, diff --git a/apps/codeforafrica/src/lib/data/utils/posts.js b/apps/codeforafrica/src/lib/data/utils/posts.js index d83b458b9..c0d6b900a 100644 --- a/apps/codeforafrica/src/lib/data/utils/posts.js +++ b/apps/codeforafrica/src/lib/data/utils/posts.js @@ -31,7 +31,7 @@ export function formatPost(post, primaryTag) { } export async function getPosts(api, params, primaryTag) { - const { page: queryPage = 1, tag, q, ...other } = params; + const { page: queryPage = 1, tag, q, where, ...other } = params; const options = { limit: 9, page: queryPage, @@ -68,6 +68,7 @@ export async function getPosts(api, params, primaryTag) { }, ], }), + ...where, }, ...other, }; @@ -117,29 +118,54 @@ export async function getPost(api, slug, primaryTag) { image: coverImage, ...meta, }; - return { - title, - blocks: [ + const blocks = [ + { + authors: authors.map(({ fullName, bio }) => { + return { + name: fullName, + bio, + }; + }), + title, + coverImage, + excerpt, + tags, + publishedOn: formatDate(publishedOn, { + includeTime: false, + month: "short", + }), + primaryTag, + blockType: "article", + ...other, + }, + ]; + + const publicationSettings = await api.findGlobal("settings-publication"); + const primaryTagPostSettings = publicationSettings?.[primaryTag] ?? {}; + const { showRecent, title: recentTitle } = primaryTagPostSettings; + if (showRecent) { + const { posts } = await getPosts( + api, { - authors: authors.map(({ fullName, bio }) => { - return { - name: fullName, - bio, - }; - }), - title, - coverImage, - excerpt, - tags, - publishedOn: formatDate(publishedOn, { - includeTime: false, - month: "short", - }), - primaryTag, - blockType: "article", - ...other, + limit: 3, + where: { + slug: { + not_equals: slug, + }, + }, }, - ], + primaryTag, + ); + blocks.push({ + title: recentTitle, + posts, + blockType: "recent-posts", + }); + } + + return { + title, + blocks, meta: postMeta, }; } diff --git a/apps/codeforafrica/src/pages/[...slugs].page.js b/apps/codeforafrica/src/pages/[...slugs].page.js index 73bd5493a..d04a47ca2 100644 --- a/apps/codeforafrica/src/pages/[...slugs].page.js +++ b/apps/codeforafrica/src/pages/[...slugs].page.js @@ -22,6 +22,7 @@ import OurTeam from "@/codeforafrica/components/OurTeam"; import PageHeader from "@/codeforafrica/components/PageHeader"; import Project from "@/codeforafrica/components/Project"; import Projects from "@/codeforafrica/components/Projects"; +import RelatedStories from "@/codeforafrica/components/RelatedStories"; import { getPageServerSideProps } from "@/codeforafrica/lib/data"; const componentsBySlugs = { @@ -45,6 +46,7 @@ const componentsBySlugs = { "our-work": Projects, "page-header": PageHeader, project: Project, + "recent-posts": RelatedStories, stories: Articles, }; diff --git a/apps/codeforafrica/src/pages/api/v1/posts.page.js b/apps/codeforafrica/src/pages/api/v1/posts.page.js index 30ebe9296..040d71a8e 100644 --- a/apps/codeforafrica/src/pages/api/v1/posts.page.js +++ b/apps/codeforafrica/src/pages/api/v1/posts.page.js @@ -2,12 +2,25 @@ import { getPosts } from "@/codeforafrica/lib/data/utils/posts"; import api from "@/codeforafrica/lib/payload"; export default async function handler(req, res) { - const { primaryTag, ...other } = req.query; + const { primaryTag, featured, ...other } = req.query; if (!primaryTag) { return res.status(400).json({ error: "Primary Tag is required" }); } try { - const data = await getPosts(api, other, primaryTag); + const data = await getPosts( + api, + { + ...other, + ...(featured && { + where: { + slug: { + not_equals: featured, + }, + }, + }), + }, + primaryTag, + ); return res.json(data); } catch (error) { return res.status(500).json(error); diff --git a/apps/codeforafrica/src/payload/collections/Posts.js b/apps/codeforafrica/src/payload/collections/Posts.js index 154b7803d..04330cd14 100644 --- a/apps/codeforafrica/src/payload/collections/Posts.js +++ b/apps/codeforafrica/src/payload/collections/Posts.js @@ -23,6 +23,7 @@ const Posts = { description: "Stories and Opportunities", group: "Publication", useAsTitle: "title", + listSearchableFields: ["content", "excerpt"], }, fields: [ { diff --git a/apps/codeforafrica/src/payload/collections/Users.js b/apps/codeforafrica/src/payload/collections/Users.js index 814d37b80..daeeef760 100644 --- a/apps/codeforafrica/src/payload/collections/Users.js +++ b/apps/codeforafrica/src/payload/collections/Users.js @@ -16,7 +16,7 @@ const Users = { admin: { defaultColumns: ["firstName", "lastName", "email", "updatedAt"], enableRichTextLink: false, - group: "Website", + group: "Settings", useAsTitle: "email", }, auth: { diff --git a/apps/codeforafrica/src/payload/globals/Publication/PostTab.js b/apps/codeforafrica/src/payload/globals/Publication/PostTab.js new file mode 100644 index 000000000..e57aabf69 --- /dev/null +++ b/apps/codeforafrica/src/payload/globals/Publication/PostTab.js @@ -0,0 +1,75 @@ +const PostTab = { + label: "Post", + fields: [ + { + type: "collapsible", + label: "Story", + fields: [ + { + name: "stories", + label: "Recent stories", + type: "group", + localized: true, + fields: [ + { + name: "showRecent", + label: "Show recent stories", + type: "checkbox", + required: true, + defaultValue: false, + }, + { + name: "title", + label: "Title", + type: "text", + required: true, + defaultValue: "Recent Stories", + admin: { + condition: (_, data) => data.showRecent, + }, + }, + ], + admin: { + className: "group-field-nested", + }, + }, + ], + }, + { + type: "collapsible", + label: "Opportunity", + fields: [ + { + name: "opportunities", + label: "Recent opportunities", + type: "group", + localized: true, + fields: [ + { + name: "showRecent", + label: "Show recent opportunities", + type: "checkbox", + required: true, + defaultValue: false, + }, + { + name: "title", + label: "Title", + type: "text", + required: true, + defaultValue: "Recent Opportunities", + admin: { + condition: (_, data) => data.showRecent, + }, + }, + ], + admin: { + className: "group-field-nested", + }, + }, + ], + }, + ], +}; + +export default PostTab; diff --git a/apps/codeforafrica/src/payload/globals/Publication/index.js b/apps/codeforafrica/src/payload/globals/Publication/index.js new file mode 100644 index 000000000..3f08a2dab --- /dev/null +++ b/apps/codeforafrica/src/payload/globals/Publication/index.js @@ -0,0 +1,20 @@ +import PostTab from "./PostTab"; + +const Publication = { + slug: "settings-publication", + label: "Publication", + access: { + read: () => true, + }, + admin: { + group: "Settings", + }, + fields: [ + { + type: "tabs", + tabs: [PostTab], + }, + ], +}; + +export default Publication; diff --git a/apps/codeforafrica/src/payload/globals/Settings/EngagementTab.js b/apps/codeforafrica/src/payload/globals/Site/EngagementTab.js similarity index 100% rename from apps/codeforafrica/src/payload/globals/Settings/EngagementTab.js rename to apps/codeforafrica/src/payload/globals/Site/EngagementTab.js diff --git a/apps/codeforafrica/src/payload/globals/Settings/GeneralTab.js b/apps/codeforafrica/src/payload/globals/Site/GeneralTab.js similarity index 100% rename from apps/codeforafrica/src/payload/globals/Settings/GeneralTab.js rename to apps/codeforafrica/src/payload/globals/Site/GeneralTab.js diff --git a/apps/codeforafrica/src/payload/globals/Settings/NavigationTab.js b/apps/codeforafrica/src/payload/globals/Site/NavigationTab.js similarity index 100% rename from apps/codeforafrica/src/payload/globals/Settings/NavigationTab.js rename to apps/codeforafrica/src/payload/globals/Site/NavigationTab.js diff --git a/apps/codeforafrica/src/payload/globals/Settings/index.js b/apps/codeforafrica/src/payload/globals/Site/index.js similarity index 74% rename from apps/codeforafrica/src/payload/globals/Settings/index.js rename to apps/codeforafrica/src/payload/globals/Site/index.js index 1680b1186..207b83587 100644 --- a/apps/codeforafrica/src/payload/globals/Settings/index.js +++ b/apps/codeforafrica/src/payload/globals/Site/index.js @@ -2,13 +2,14 @@ import EngagementTab from "./EngagementTab"; import GeneralTab from "./GeneralTab"; import NavigationTab from "./NavigationTab"; -const Settings = { - slug: "settings", +const Site = { + slug: "settings-site", + label: "Site", access: { read: () => true, }, admin: { - group: "Website", + group: "Settings", }, fields: [ { @@ -18,4 +19,4 @@ const Settings = { ], }; -export default Settings; +export default Site;