From 4f09a2b0ca75b6286c14aef96dcde026012e8251 Mon Sep 17 00:00:00 2001 From: Kipruto <43873157+kelvinkipruto@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:48:39 +0300 Subject: [PATCH 1/9] Initialise site settings --- apps/climatemappedafrica/next.config.js | 14 -- apps/climatemappedafrica/payload.config.ts | 4 +- .../src/lib/data/blockify/index.js | 23 +++ .../src/lib/data/common/index.js | 29 ++++ .../climatemappedafrica/src/lib/data/index.js | 3 + .../src/lib/data/local/index.js | 15 ++ apps/climatemappedafrica/src/lib/index.js | 3 + .../src/lib/payload/index.js | 64 ++++++++ .../src/pages/[[...slug]].js | 14 ++ .../src/payload/fields/image.js | 16 ++ .../src/payload/fields/links/link.js | 149 ++++++++++++++++++ .../src/payload/fields/links/linkArray.js | 27 ++++ .../src/payload/fields/links/linkGroup.js | 20 +++ .../src/payload/fields/richText.js | 60 +++++++ .../src/payload/fields/socialLinks.js | 81 ++++++++++ .../src/payload/fields/url.js | 25 +++ .../src/payload/globals/site/EngagementTab.js | 82 ++++++++++ .../src/payload/globals/site/GeneralTab.js | 53 +++++++ .../src/payload/globals/site/InitiativeTab.js | 57 +++++++ .../src/payload/globals/site/NavigationTab.js | 80 ++++++++++ .../src/payload/globals/site/index.js | 23 +++ .../src/payload/utils/mapLinkTypeToHref.js | 18 +++ 22 files changed, 845 insertions(+), 15 deletions(-) create mode 100644 apps/climatemappedafrica/src/lib/data/blockify/index.js create mode 100644 apps/climatemappedafrica/src/lib/data/common/index.js create mode 100644 apps/climatemappedafrica/src/lib/data/index.js create mode 100644 apps/climatemappedafrica/src/lib/data/local/index.js create mode 100644 apps/climatemappedafrica/src/lib/index.js create mode 100644 apps/climatemappedafrica/src/lib/payload/index.js create mode 100644 apps/climatemappedafrica/src/pages/[[...slug]].js create mode 100644 apps/climatemappedafrica/src/payload/fields/image.js create mode 100644 apps/climatemappedafrica/src/payload/fields/links/link.js create mode 100644 apps/climatemappedafrica/src/payload/fields/links/linkArray.js create mode 100644 apps/climatemappedafrica/src/payload/fields/links/linkGroup.js create mode 100644 apps/climatemappedafrica/src/payload/fields/richText.js create mode 100644 apps/climatemappedafrica/src/payload/fields/socialLinks.js create mode 100644 apps/climatemappedafrica/src/payload/fields/url.js create mode 100644 apps/climatemappedafrica/src/payload/globals/site/EngagementTab.js create mode 100644 apps/climatemappedafrica/src/payload/globals/site/GeneralTab.js create mode 100644 apps/climatemappedafrica/src/payload/globals/site/InitiativeTab.js create mode 100644 apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js create mode 100644 apps/climatemappedafrica/src/payload/globals/site/index.js create mode 100644 apps/climatemappedafrica/src/payload/utils/mapLinkTypeToHref.js diff --git a/apps/climatemappedafrica/next.config.js b/apps/climatemappedafrica/next.config.js index 2c50a972d..c282b8971 100644 --- a/apps/climatemappedafrica/next.config.js +++ b/apps/climatemappedafrica/next.config.js @@ -51,18 +51,4 @@ module.exports = { }; return config; }, - async redirects() { - return [ - { - source: "/", - destination: "/explore/af", - permanent: true, - }, - { - source: "/explore", - destination: "/explore/af", - permanent: true, - }, - ]; - }, }; diff --git a/apps/climatemappedafrica/payload.config.ts b/apps/climatemappedafrica/payload.config.ts index be284f509..91c734b5a 100644 --- a/apps/climatemappedafrica/payload.config.ts +++ b/apps/climatemappedafrica/payload.config.ts @@ -15,6 +15,8 @@ import Media from "./src/payload/collections/Media"; import Pages from "./src/payload/collections/Pages"; import Users from "./src/payload/collections/Users"; +import Site from "./src/payload/globals/Site"; + const projectDir = process.cwd(); loadEnvConfig(projectDir); @@ -52,7 +54,7 @@ export default buildConfig({ migrationDir: process.env.MIGRATIONS_DIR, }), collections: [Media, Pages, Users] as CollectionConfig[], - globals: [] as GlobalConfig[], + globals: [Site] as GlobalConfig[], ...(locales?.length ? { localization: { diff --git a/apps/climatemappedafrica/src/lib/data/blockify/index.js b/apps/climatemappedafrica/src/lib/data/blockify/index.js new file mode 100644 index 000000000..3d7836feb --- /dev/null +++ b/apps/climatemappedafrica/src/lib/data/blockify/index.js @@ -0,0 +1,23 @@ +/* eslint-disable import/prefer-default-export */ + +const propsifyBlockBySlug = {}; + +export const blockify = async (blocks, api) => { + const promises = blocks?.map(async (block) => { + const slug = block.blockType; + const propsifyBlock = propsifyBlockBySlug[slug]; + + if (propsifyBlock) { + return propsifyBlock(block, api); + } + return { + ...block, + slug, + }; + }); + + if (promises) { + return Promise.all(promises); + } + return blocks ?? null; +}; diff --git a/apps/climatemappedafrica/src/lib/data/common/index.js b/apps/climatemappedafrica/src/lib/data/common/index.js new file mode 100644 index 000000000..4025643d6 --- /dev/null +++ b/apps/climatemappedafrica/src/lib/data/common/index.js @@ -0,0 +1,29 @@ +/* eslint-disable import/prefer-default-export */ + +import { blockify } from "@/climatemappedafrica/lib/data/blockify"; + +export async function getPageProps(api, context) { + // For now, ClimatemappedAfrica only supports single paths i.e. /, /about, etc., + // so params.slug[0] is good enough + const slugs = context.params?.slug || undefined; + const [slug] = slugs || ["index"]; + const { draftMode = false } = context; + const options = { draft: draftMode }; + + const { + docs: [page], + } = await api.findPage(slug, options); + + if (!page) { + return null; + } + + const blocks = await blockify(page.blocks, api); + + const siteSettings = await api.findGlobal("settings-site"); + + return { + blocks, + siteSettings, + }; +} diff --git a/apps/climatemappedafrica/src/lib/data/index.js b/apps/climatemappedafrica/src/lib/data/index.js new file mode 100644 index 000000000..c2ebe4abf --- /dev/null +++ b/apps/climatemappedafrica/src/lib/data/index.js @@ -0,0 +1,3 @@ +/* eslint-disable import/prefer-default-export */ + +export { getPageServerSideProps } from "./local"; diff --git a/apps/climatemappedafrica/src/lib/data/local/index.js b/apps/climatemappedafrica/src/lib/data/local/index.js new file mode 100644 index 000000000..9b1693471 --- /dev/null +++ b/apps/climatemappedafrica/src/lib/data/local/index.js @@ -0,0 +1,15 @@ +import { payload } from "@/climatemappedafrica/lib"; +import { getPageProps } from "@/climatemappedafrica/lib/data/common"; + +export const api = payload; + +export async function getPageServerSideProps(context) { + const props = await getPageProps(api, context); + + if (!props) { + return { notFound: true }; + } + return { + props, + }; +} diff --git a/apps/climatemappedafrica/src/lib/index.js b/apps/climatemappedafrica/src/lib/index.js new file mode 100644 index 000000000..7f94a740a --- /dev/null +++ b/apps/climatemappedafrica/src/lib/index.js @@ -0,0 +1,3 @@ +/* eslint-disable import/prefer-default-export */ + +export { default as payload } from "./payload"; diff --git a/apps/climatemappedafrica/src/lib/payload/index.js b/apps/climatemappedafrica/src/lib/payload/index.js new file mode 100644 index 000000000..8bb041faf --- /dev/null +++ b/apps/climatemappedafrica/src/lib/payload/index.js @@ -0,0 +1,64 @@ +import payload from "payload"; + +async function findPage(slug, options) { + return payload.find({ + ...options, + collection: "pages", + where: { + ...options?.where, + slug: { + equals: slug, + }, + }, + }); +} + +async function getCollection(collection, options) { + return payload.find({ + limit: 0, + ...options, + collection, + }); +} + +async function findGlobal(slug, options) { + return payload.findGlobal({ + ...options, + slug, + }); +} + +async function createCollection(collection, data, options) { + return payload.create({ + collection, + data, + ...options, + }); +} + +async function deleteCollection(collection, options) { + return payload.delete({ + ...options, + collection, + }); +} + +async function updateCollection(collection, id, data, options) { + const args = { + ...options, + collection, + id, + data, + }; + return payload.update(args); +} +const api = { + createCollection, + deleteCollection, + findGlobal, + findPage, + getCollection, + updateCollection, +}; + +export default api; diff --git a/apps/climatemappedafrica/src/pages/[[...slug]].js b/apps/climatemappedafrica/src/pages/[[...slug]].js new file mode 100644 index 000000000..8b2d4afdc --- /dev/null +++ b/apps/climatemappedafrica/src/pages/[[...slug]].js @@ -0,0 +1,14 @@ +import Page from "@/climatemappedafrica/components/Page"; +import { getPageServerSideProps } from "@/climatemappedafrica/lib/data"; + +export default function Index(props) { + return ( + +

Pahed

+
+ ); +} + +export async function getServerSideProps(context) { + return getPageServerSideProps(context); +} diff --git a/apps/climatemappedafrica/src/payload/fields/image.js b/apps/climatemappedafrica/src/payload/fields/image.js new file mode 100644 index 000000000..8bf1e9c5b --- /dev/null +++ b/apps/climatemappedafrica/src/payload/fields/image.js @@ -0,0 +1,16 @@ +import { deepmerge } from "@mui/utils"; + +function image({ overrides = undefined }) { + const imageResult = { + name: "image", + type: "upload", + relationTo: "media", + filterOptions: { + mimeType: { contains: "image" }, + }, + }; + + return deepmerge(imageResult, overrides); +} + +export default image; diff --git a/apps/climatemappedafrica/src/payload/fields/links/link.js b/apps/climatemappedafrica/src/payload/fields/links/link.js new file mode 100644 index 000000000..88d80d0a7 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/fields/links/link.js @@ -0,0 +1,149 @@ +import { deepmerge } from "@mui/utils"; + +import mapLinkTypeToHref from "../../utils/mapLinkTypeToHref"; + +export async function mapLinkToHrefBeforeValidate({ + siblingData, + req: { payload }, +}) { + // Don't modify original doc. + const doc = { ...siblingData.doc }; + if (typeof doc.value === "string") { + const { relationTo: collection, value: id } = doc; + doc.value = await payload.findByID({ + collection, + id, + // We only need slug from the collection don't expand the whole + // relationship. We may end up getting stuck on infinite recursion if + // collection contain other links. + depth: 0, + }); + } + const href = mapLinkTypeToHref({ ...siblingData, doc }); + + return href; +} + +const link = ({ + defaultValue = "internal", + disableLabel = false, + disableLinkTypeSelection = false, + disableOpenInNewTab = false, + overrides = {}, + required = true, +} = {}) => { + const linkResult = { + type: "row", + fields: [ + { + name: "linkType", + type: "radio", + options: [ + { + label: { + en: "Custom URL", + }, + value: "custom", + }, + { + label: { + en: "Internal link", + }, + value: "internal", + }, + ], + defaultValue, + admin: { + hidden: disableLinkTypeSelection, + }, + }, + ], + }; + + const linkTypes = [ + { + type: "row", + fields: [ + { + name: "doc", + label: { + en: "Document to link to", + fr: "Document pour lien vers", + pt: "Documento para link para", + }, + type: "relationship", + relationTo: ["pages"], + required, + maxDepth: 1, + admin: { + condition: (_, siblingData) => siblingData?.linkType === "internal", + }, + }, + { + name: "url", + label: { + en: "Custom URL", + fr: "URL personnalisée", + pt: "URL personalizado", + }, + type: "text", + required, + admin: { + condition: (_, siblingData) => siblingData?.linkType === "custom", + }, + }, + { + name: "href", + type: "text", + required, + admin: { + hidden: true, + }, + hooks: { + beforeValidate: [mapLinkToHrefBeforeValidate], + }, + }, + ], + }, + ]; + let labelFields = []; + if (!disableLabel) { + labelFields = [ + { + type: "row", + fields: [ + { + name: "label", + label: { + en: "Label", + pt: "Rótulo", + }, + type: "text", + required, + }, + ], + }, + ]; + } + linkResult.fields = [...labelFields, ...linkResult.fields, ...linkTypes]; + if (!disableOpenInNewTab) { + linkResult.fields.push({ + type: "row", + fields: [ + { + name: "newTab", + label: { + en: "Open in new tab", + fr: "Ouvrir dans un nouvel onglet", + pt: "Abrir num novo separador", + }, + type: "checkbox", + }, + ], + }); + } + + return deepmerge(linkResult, overrides); +}; + +export default link; diff --git a/apps/climatemappedafrica/src/payload/fields/links/linkArray.js b/apps/climatemappedafrica/src/payload/fields/links/linkArray.js new file mode 100644 index 000000000..d36f2c2f3 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/fields/links/linkArray.js @@ -0,0 +1,27 @@ +import { deepmerge } from "@mui/utils"; + +import link from "./link"; + +/** + * array field consisting of link fields . + */ +function linkArray(args) { + const { linkConfig, overrides = {} } = args ?? {}; + const generatedLinkArray = { + name: "links", + type: "array", + fields: [link(linkConfig)], + admin: { + initCollapsed: true, + components: { + RowLabel: ({ data }) => { + return data?.label || data?.reference?.title || data?.url || data?.id; + }, + }, + }, + }; + + return deepmerge(generatedLinkArray, overrides); +} + +export default linkArray; diff --git a/apps/climatemappedafrica/src/payload/fields/links/linkGroup.js b/apps/climatemappedafrica/src/payload/fields/links/linkGroup.js new file mode 100644 index 000000000..f76381d28 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/fields/links/linkGroup.js @@ -0,0 +1,20 @@ +import { deepmerge } from "@mui/utils"; + +import link from "./link"; + +/** + * group field consisting of a link field. + */ +function linkGroup(args) { + const { linkConfig, overrides = {} } = args ?? {}; + const generatedLinkGroup = { + name: "link", + type: "group", + required: true, + fields: [link(linkConfig)], + }; + + return deepmerge(generatedLinkGroup, overrides); +} + +export default linkGroup; diff --git a/apps/climatemappedafrica/src/payload/fields/richText.js b/apps/climatemappedafrica/src/payload/fields/richText.js new file mode 100644 index 000000000..e7a4eea85 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/fields/richText.js @@ -0,0 +1,60 @@ +import { deepmerge } from "@mui/utils"; + +import mapLinkTypeToHref from "../utils/mapLinkTypeToHref"; + +async function insertHref(nodes, payload) { + if (!nodes?.length) { + // Front-end needs `null` for serialization + return null; + } + return Promise.all( + nodes.map(async (node) => { + let newNode = node; + // The most important thing is not to change the doc structure + // since the admin UI expects it to be in certain why. But of course, + // we can add href prop for front-end. + if (node.type === "link") { + let { doc } = node; + if (typeof doc?.value === "string") { + const { relationTo: collection, value: id } = doc; + if (payload.findByID) { + // @ts-ignore + const value = await payload.findByID({ + collection, + id, + // We only need slug from the collection don't expand the whole + // relationship. We may end up getting stuck on infinite recursion if + // collection contain other links. + depth: 0, + }); + doc = { ...doc, value }; + } + } + const href = mapLinkTypeToHref({ ...node, doc }); + newNode = { ...node, href }; + } + newNode.children = await insertHref(node.children, payload); + return newNode; + }), + ); +} + +async function mapLinkToHrefAfterRead({ req: { payload }, value }) { + if (!value?.length) { + return value; + } + return insertHref(value, payload); +} + +function richText(overrides) { + const richTextResult = { + type: "richText", + hooks: { + afterRead: [mapLinkToHrefAfterRead], + }, + }; + + return deepmerge(richTextResult, overrides); +} + +export default richText; diff --git a/apps/climatemappedafrica/src/payload/fields/socialLinks.js b/apps/climatemappedafrica/src/payload/fields/socialLinks.js new file mode 100644 index 000000000..be10a90c4 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/fields/socialLinks.js @@ -0,0 +1,81 @@ +import { deepmerge } from "@mui/utils"; +import { select } from "payload/dist/fields/validations"; + +import url from "./url"; + +export const socialMediaOptions = [ + "Facebook", + "Twitter", + "Instagram", + "Linkedin", + "Github", + "Slack", +]; + +function socialLinks(overrides) { + const defaults = { + name: "links", + type: "array", + labels: { + singular: { + en: "Link", + }, + plural: { + en: "Links", + }, + }, + minRows: 1, + admin: { + className: "array-field-nested", + components: { + RowLabel: ({ data, index }) => { + let label = ""; + if (data.platform) { + label = data.platform; + } + if (data.url) { + label = label ? `${label} (${data.url})` : data.url; + } + if (!label) { + label = `Link ${String(index).padStart(2, "0")}`; + } + return label; + }, + }, + initCollapsed: true, + }, + fields: [ + { + name: "platform", + type: "select", + label: "Platform", + options: socialMediaOptions, + required: true, + validate: (val, args) => { + const { data, t } = args || {}; + const { name: linksName = "links" } = overrides || {}; + if ( + data?.[linksName]?.filter((l) => l.platform === val)?.length > 1 + ) { + return t("codeforafrica.validation:uniquePlatforms"); + } + + const { + hasMany, + options = socialMediaOptions, + required = true, + } = args; + return select(val, { hasMany, options, required, t }); + }, + }, + url({ + overrides: { + required: true, + }, + }), + ], + }; + return deepmerge(defaults, overrides); +} + +export default socialLinks; diff --git a/apps/climatemappedafrica/src/payload/fields/url.js b/apps/climatemappedafrica/src/payload/fields/url.js new file mode 100644 index 000000000..d4ecac720 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/fields/url.js @@ -0,0 +1,25 @@ +import { deepmerge } from "@mui/utils"; +import { text } from "payload/dist/fields/validations"; + +function url({ overrides = undefined }) { + const urlResult = { + name: "url", + type: "text", + label: "URL", + validate: (val, options) => { + try { + // eslint-disable-next-line no-new + new URL(val); + } catch (e) { + if (e instanceof TypeError) { + return "Please enter valid URL"; + } + } + return text(val, options); + }, + }; + + return deepmerge(urlResult, overrides); +} + +export default url; diff --git a/apps/climatemappedafrica/src/payload/globals/site/EngagementTab.js b/apps/climatemappedafrica/src/payload/globals/site/EngagementTab.js new file mode 100644 index 000000000..ca7c01f84 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/globals/site/EngagementTab.js @@ -0,0 +1,82 @@ +import socialLinks from "../../fields/socialLinks"; + +const EngagementTab = { + label: "Engagement", + fields: [ + { + name: "connect", + type: "group", + label: "Social Accounts", + localized: true, + fields: [ + { + type: "collapsible", + label: "Title & Links", + fields: [ + { + name: "title", + type: "text", + admin: { + description: + "Text that appears on contact links e.g Stay in Touch", + }, + required: true, + }, + socialLinks(), + ], + }, + ], + }, + { + name: "newsletter", + type: "group", + label: "Email Newsletter", + localized: true, + fields: [ + { + type: "collapsible", + label: "Title & Embed Code", + fields: [ + { + name: "title", + type: "text", + required: true, + }, + { + name: "embedCode", + type: "code", + required: true, + admin: { + language: "html", + }, + }, + ], + }, + ], + }, + { + name: "analytics", + type: "group", + label: "Site Analytics", + localized: true, + fields: [ + { + type: "collapsible", + label: "Google Analytics", + fields: [ + { + name: "analyticsId", + type: "text", + }, + ], + admin: { + description: + "Measurement ID: https://support.google.com/analytics/answer/12270356", + }, + }, + ], + }, + ], +}; + +export default EngagementTab; diff --git a/apps/climatemappedafrica/src/payload/globals/site/GeneralTab.js b/apps/climatemappedafrica/src/payload/globals/site/GeneralTab.js new file mode 100644 index 000000000..a1fa6fb64 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/globals/site/GeneralTab.js @@ -0,0 +1,53 @@ +import image from "../../fields/image"; +import richText from "../../fields/richText"; + +const GeneralTab = { + label: "General", + fields: [ + { + type: "collapsible", + label: "Title & Description", + fields: [ + { + name: "title", + type: "text", + required: true, + localized: true, + }, + richText({ + name: "description", + required: true, + localized: true, + }), + ], + }, + { + type: "collapsible", + label: "Logo", + fields: [ + image({ + overrides: { + name: "primaryLogo", + required: true, + localized: true, + admin: { + description: "Shown on main navigation bar.", + }, + }, + }), + image({ + overrides: { + name: "secondaryLogo", + localized: true, + admin: { + description: + "Shown on main footer. If not provided, primary logo will be reused.", + }, + }, + }), + ], + }, + ], +}; + +export default GeneralTab; diff --git a/apps/climatemappedafrica/src/payload/globals/site/InitiativeTab.js b/apps/climatemappedafrica/src/payload/globals/site/InitiativeTab.js new file mode 100644 index 000000000..9ad7647f8 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/globals/site/InitiativeTab.js @@ -0,0 +1,57 @@ +import image from "../../fields/image"; +import link from "../../fields/links/link"; +import richText from "../../fields/richText"; + +const PartnersTab = { + label: "Initiative", + fields: [ + { + name: "initiative", + type: "group", + fields: [ + { + name: "title", + type: "text", + required: true, + }, + richText({ + name: "description", + required: true, + }), + { + name: "partners", + label: "Partners", + type: "array", + fields: [ + { + name: "name", + type: "text", + required: true, + }, + image({ + overrides: { + label: "Logo", + name: "logo", + required: true, + }, + }), + link({ + defaultValue: "custom", + disableLinkTypeSelection: true, + disableOpenInNewTab: true, + }), + ], + admin: { + components: { + RowLabel: ({ data, index = 0 }) => { + return data?.name || `Partner ${index + 1}`; + }, + }, + }, + }, + ], + }, + ], +}; + +export default PartnersTab; diff --git a/apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js b/apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js new file mode 100644 index 000000000..6c2a3cf8b --- /dev/null +++ b/apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js @@ -0,0 +1,80 @@ +import link from "../../fields/links/link"; +import linkArray from "../../fields/links/linkArray"; +import { socialMediaOptions } from "../../fields/socialLinks"; + +const linkField = link({ + disableOpenInNewTab: true, +}); + +const NavigationTab = { + label: "Navigation", + fields: [ + { + name: "primaryNavigation", + type: "group", + localized: true, + fields: [ + { + type: "collapsible", + label: "Title & Links", + fields: [ + linkArray({ + overrides: { + name: "menus", + labels: { + singular: { + en: "Menu", + }, + plural: { + en: "Menus", + }, + }, + fields: [linkField], + admin: { + className: "array-field-nested", + }, + }, + }), + { + name: "connect", + type: "select", + options: socialMediaOptions, + }, + ], + }, + ], + }, + { + name: "secondaryNavigation", + type: "group", + localized: true, + fields: [ + { + type: "collapsible", + label: "Title & Links", + fields: [ + linkArray({ + overrides: { + name: "menus", + labels: { + singular: { + en: "Menu", + }, + plural: { + en: "Menus", + }, + }, + fields: [linkField], + admin: { + className: "array-field-nested", + }, + }, + }), + ], + }, + ], + }, + ], +}; + +export default NavigationTab; diff --git a/apps/climatemappedafrica/src/payload/globals/site/index.js b/apps/climatemappedafrica/src/payload/globals/site/index.js new file mode 100644 index 000000000..1e56939e8 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/globals/site/index.js @@ -0,0 +1,23 @@ +import EngagementTab from "./EngagementTab"; +import GeneralTab from "./GeneralTab"; +import InitiativeTab from "./InitiativeTab"; +import NavigationTab from "./NavigationTab"; + +const Site = { + slug: "settings-site", + label: "Site", + access: { + read: () => true, + }, + admin: { + group: "Settings", + }, + fields: [ + { + type: "tabs", + tabs: [GeneralTab, NavigationTab, EngagementTab, InitiativeTab], + }, + ], +}; + +export default Site; diff --git a/apps/climatemappedafrica/src/payload/utils/mapLinkTypeToHref.js b/apps/climatemappedafrica/src/payload/utils/mapLinkTypeToHref.js new file mode 100644 index 000000000..ab5c938c9 --- /dev/null +++ b/apps/climatemappedafrica/src/payload/utils/mapLinkTypeToHref.js @@ -0,0 +1,18 @@ +import formatPagePath from "./formatPagePath"; + +const mapLinkTypeToHref = ({ doc: linkDoc, linkType, url }) => { + // default to `null` for serialization. + let href = null; + if (linkType === "internal") { + const { relationTo: collection, value: doc } = linkDoc; + if (doc?.slug) { + href = formatPagePath(collection, doc); + } + } else { + // custom link + href = url; + } + return href; +}; + +export default mapLinkTypeToHref; From 689a69539f01f1407dca5cfcc238e067d9831028 Mon Sep 17 00:00:00 2001 From: Kipruto <43873157+kelvinkipruto@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:58:42 +0300 Subject: [PATCH 2/9] Reusable richtext component --- packages/commons-ui-next/package.json | 3 +- .../commons-ui-next/src/RichText/RichText.js | 108 ++++++++++++++++++ .../src/RichText/RichText.snap.js | 31 +++++ .../src/RichText/RichText.test.js | 59 ++++++++++ .../commons-ui-next/src/RichText/index.js | 3 + packages/commons-ui-next/src/index.js | 1 + pnpm-lock.yaml | 3 + 7 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 packages/commons-ui-next/src/RichText/RichText.js create mode 100644 packages/commons-ui-next/src/RichText/RichText.snap.js create mode 100644 packages/commons-ui-next/src/RichText/RichText.test.js create mode 100644 packages/commons-ui-next/src/RichText/index.js diff --git a/packages/commons-ui-next/package.json b/packages/commons-ui-next/package.json index f5cdf7922..51eed1925 100644 --- a/packages/commons-ui-next/package.json +++ b/packages/commons-ui-next/package.json @@ -56,7 +56,8 @@ "next": "catalog:", "prop-types": "catalog:", "react": "catalog:", - "react-dom": "catalog:" + "react-dom": "catalog:", + "slate": "catalog:" }, "dependencies": { "clsx": "catalog:" diff --git a/packages/commons-ui-next/src/RichText/RichText.js b/packages/commons-ui-next/src/RichText/RichText.js new file mode 100644 index 000000000..6f1ea3ae0 --- /dev/null +++ b/packages/commons-ui-next/src/RichText/RichText.js @@ -0,0 +1,108 @@ +/* eslint-disable react/no-array-index-key */ +import { Box } from "@mui/material"; +import React, { Fragment } from "react"; +import { Text } from "slate"; + +import Link from "@/commons-ui/next/Link"; +import RichTypography from "@/commons-ui/next/RichTypography"; + +const DEFAULT_PROPS = { + html: false, +}; + +const serialize = (children, props) => + children?.map((node, i) => { + if (Text.isText(node)) { + let { text } = node; + if (node.bold) { + text = {text}; + } + if (node.code) { + text = {text}; + } + if (node.italic) { + text = {text}; + } + + // Handle other leaf types here... + + return {text}; + } + + if (!node) { + return null; + } + // TODO(kilemensi): handle node.type === indent + switch (node.type) { + case "h1": + return ( + + {serialize(node.children)} + + ); + case "h2": + return ( + + {serialize(node.children)} + + ); + case "h3": + return ( + + {serialize(node.children)} + + ); + case "h4": + return ( + + {serialize(node.children)} + + ); + case "h5": + return ( + + {serialize(node.children)} + + ); + case "h6": + return ( + + {serialize(node.children)} + + ); + case "quote": + return
{serialize(node.children)}
; + case "link": + return ( + + {serialize(node.children)} + + ); + default: + return ( + + {serialize(node.children, props)} + + ); + } + }); + +const RichText = React.forwardRef(function RichText(props, ref) { + const { elements, variant, typographyProps, ...other } = props; + + if (!elements?.length) { + return null; + } + return ( + + {serialize(elements, typographyProps)} + + ); +}); + +export default RichText; diff --git a/packages/commons-ui-next/src/RichText/RichText.snap.js b/packages/commons-ui-next/src/RichText/RichText.snap.js new file mode 100644 index 000000000..b0a821528 --- /dev/null +++ b/packages/commons-ui-next/src/RichText/RichText.snap.js @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders unchanged 1`] = ` +
+
+

+ The Charter Project is a pan-African initiative by a coalition of watchdog organisations that use civic technologies to strengthen democracy. +

+

+ We do this by helping digital activists and democracy changemakers leverage the African Union’s Charter on Democracy, Elections and Governance (ACDEG). +

+

+ The project currently supports initiatives in 11 countries. Find out more + + here + +

+
+
+`; diff --git a/packages/commons-ui-next/src/RichText/RichText.test.js b/packages/commons-ui-next/src/RichText/RichText.test.js new file mode 100644 index 000000000..bcbed0d5d --- /dev/null +++ b/packages/commons-ui-next/src/RichText/RichText.test.js @@ -0,0 +1,59 @@ +import { render } from "@commons-ui/testing-library"; +import React from "react"; + +import RichText from "./RichText"; + +const defaultProps = { + elements: [ + { + children: [ + { + text: "The Charter Project is a pan-African initiative by a coalition of watchdog organisations that use civic technologies to strengthen democracy.", + children: null, + }, + ], + }, + { + children: [ + { + text: "We do this by helping digital activists and democracy changemakers leverage the African Union’s Charter on Democracy, Elections and Governance (ACDEG).", + children: null, + }, + ], + }, + { + children: [ + { + text: "The project currently supports initiatives in 11 countries. Find out more ", + children: null, + }, + { + type: "link", + linkType: "internal", + doc: { + value: "63887cf05bc566facccee049", + relationTo: "pages", + }, + children: [ + { + text: "here", + children: null, + }, + ], + href: "/", + }, + { + text: "", + children: null, + }, + ], + }, + ], +}; + +describe("", () => { + it("renders unchanged", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/commons-ui-next/src/RichText/index.js b/packages/commons-ui-next/src/RichText/index.js new file mode 100644 index 000000000..49b1d7e02 --- /dev/null +++ b/packages/commons-ui-next/src/RichText/index.js @@ -0,0 +1,3 @@ +import RichText from "./RichText"; + +export default RichText; diff --git a/packages/commons-ui-next/src/index.js b/packages/commons-ui-next/src/index.js index 548f9c705..db436ed3c 100644 --- a/packages/commons-ui-next/src/index.js +++ b/packages/commons-ui-next/src/index.js @@ -4,3 +4,4 @@ export { default as Link } from "./Link"; export * from "./Link"; export { default as RichTypography } from "./RichTypography"; +export { default as RichText } from "./RichText"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79e25380a..6ccb664b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2513,6 +2513,9 @@ importers: clsx: specifier: 'catalog:' version: 2.1.1 + slate: + specifier: 'catalog:' + version: 0.103.0 devDependencies: '@babel/core': specifier: 'catalog:' From 60aa28c8cef938d02d56e1b167820003d1d52fde Mon Sep 17 00:00:00 2001 From: Kipruto <43873157+kelvinkipruto@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:01:18 +0300 Subject: [PATCH 3/9] Setup payload footer --- .../src/lib/data/common/index.js | 38 ++++++++++++- .../src/payload/globals/site/InitiativeTab.js | 57 ------------------- .../src/payload/globals/site/NavigationTab.js | 11 +++- .../src/payload/globals/site/index.js | 3 +- 4 files changed, 45 insertions(+), 64 deletions(-) delete mode 100644 apps/climatemappedafrica/src/payload/globals/site/InitiativeTab.js diff --git a/apps/climatemappedafrica/src/lib/data/common/index.js b/apps/climatemappedafrica/src/lib/data/common/index.js index 4025643d6..15b838a2e 100644 --- a/apps/climatemappedafrica/src/lib/data/common/index.js +++ b/apps/climatemappedafrica/src/lib/data/common/index.js @@ -1,7 +1,38 @@ -/* eslint-disable import/prefer-default-export */ - import { blockify } from "@/climatemappedafrica/lib/data/blockify"; +export function imageFromMedia(alt, url) { + return { alt, src: url }; +} + +function getFooter(siteSettings) { + const { + connect, + footerNavigation, + newsletter, + primaryLogo, + secondaryLogo, + description, + title, + } = siteSettings; + + const { menus: footerMenus, ...footerProps } = footerNavigation; + + const media = secondaryLogo || primaryLogo; + const footerLogoUrl = typeof media === "string" ? null : media.url; + + return { + connect, + description, + logo: imageFromMedia(title, footerLogoUrl), + links: { + ...footerProps, + links: footerMenus, + }, + newsletter, + title, + }; +} + export async function getPageProps(api, context) { // For now, ClimatemappedAfrica only supports single paths i.e. /, /about, etc., // so params.slug[0] is good enough @@ -21,9 +52,10 @@ export async function getPageProps(api, context) { const blocks = await blockify(page.blocks, api); const siteSettings = await api.findGlobal("settings-site"); + const footer = getFooter(siteSettings); return { blocks, - siteSettings, + footer, }; } diff --git a/apps/climatemappedafrica/src/payload/globals/site/InitiativeTab.js b/apps/climatemappedafrica/src/payload/globals/site/InitiativeTab.js deleted file mode 100644 index 9ad7647f8..000000000 --- a/apps/climatemappedafrica/src/payload/globals/site/InitiativeTab.js +++ /dev/null @@ -1,57 +0,0 @@ -import image from "../../fields/image"; -import link from "../../fields/links/link"; -import richText from "../../fields/richText"; - -const PartnersTab = { - label: "Initiative", - fields: [ - { - name: "initiative", - type: "group", - fields: [ - { - name: "title", - type: "text", - required: true, - }, - richText({ - name: "description", - required: true, - }), - { - name: "partners", - label: "Partners", - type: "array", - fields: [ - { - name: "name", - type: "text", - required: true, - }, - image({ - overrides: { - label: "Logo", - name: "logo", - required: true, - }, - }), - link({ - defaultValue: "custom", - disableLinkTypeSelection: true, - disableOpenInNewTab: true, - }), - ], - admin: { - components: { - RowLabel: ({ data, index = 0 }) => { - return data?.name || `Partner ${index + 1}`; - }, - }, - }, - }, - ], - }, - ], -}; - -export default PartnersTab; diff --git a/apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js b/apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js index 6c2a3cf8b..02da38263 100644 --- a/apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js +++ b/apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js @@ -45,13 +45,20 @@ const NavigationTab = { ], }, { - name: "secondaryNavigation", + name: "footerNavigation", type: "group", localized: true, fields: [ + { + type: "text", + name: "title", + required: true, + localized: true, + label: "Title", + }, { type: "collapsible", - label: "Title & Links", + label: "Links", fields: [ linkArray({ overrides: { diff --git a/apps/climatemappedafrica/src/payload/globals/site/index.js b/apps/climatemappedafrica/src/payload/globals/site/index.js index 1e56939e8..207b83587 100644 --- a/apps/climatemappedafrica/src/payload/globals/site/index.js +++ b/apps/climatemappedafrica/src/payload/globals/site/index.js @@ -1,6 +1,5 @@ import EngagementTab from "./EngagementTab"; import GeneralTab from "./GeneralTab"; -import InitiativeTab from "./InitiativeTab"; import NavigationTab from "./NavigationTab"; const Site = { @@ -15,7 +14,7 @@ const Site = { fields: [ { type: "tabs", - tabs: [GeneralTab, NavigationTab, EngagementTab, InitiativeTab], + tabs: [GeneralTab, NavigationTab, EngagementTab], }, ], }; From c1ca2b09e711ffb01a4b71eccdace50e3dabb4ca Mon Sep 17 00:00:00 2001 From: Kipruto <43873157+kelvinkipruto@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:32:45 +0300 Subject: [PATCH 4/9] Improve stayintouch to take direction --- .../commons-ui-core/src/StayInTouch/StayInTouch.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/commons-ui-core/src/StayInTouch/StayInTouch.js b/packages/commons-ui-core/src/StayInTouch/StayInTouch.js index ff63dcbda..896a8446b 100644 --- a/packages/commons-ui-core/src/StayInTouch/StayInTouch.js +++ b/packages/commons-ui-core/src/StayInTouch/StayInTouch.js @@ -8,7 +8,14 @@ import RichTypography from "@/commons-ui/core/RichTypography"; import SocialMediaIconLink from "@/commons-ui/core/SocialMediaIconLink"; const StayInTouch = React.forwardRef(function StayInTouch( - { LinkProps, TitleProps, links, sx, title }, + { + LinkProps, + TitleProps, + links, + sx, + title, + direction = { xs: "column", md: "row" }, + }, ref, ) { if (!links?.length) { @@ -16,7 +23,7 @@ const StayInTouch = React.forwardRef(function StayInTouch( } return ( Date: Wed, 9 Oct 2024 16:33:11 +0300 Subject: [PATCH 5/9] temporary stayintouch component fix --- packages/commons-ui-next/src/StayInTouch/index.js | 4 ++++ packages/commons-ui-next/src/index.js | 1 + 2 files changed, 5 insertions(+) create mode 100644 packages/commons-ui-next/src/StayInTouch/index.js diff --git a/packages/commons-ui-next/src/StayInTouch/index.js b/packages/commons-ui-next/src/StayInTouch/index.js new file mode 100644 index 000000000..64c5ba3af --- /dev/null +++ b/packages/commons-ui-next/src/StayInTouch/index.js @@ -0,0 +1,4 @@ +// This is a temporary solution for packages still using the old commons-ui-core package. +import { StayInTouch } from "@commons-ui/core"; + +export default StayInTouch; diff --git a/packages/commons-ui-next/src/index.js b/packages/commons-ui-next/src/index.js index db436ed3c..049ca3c32 100644 --- a/packages/commons-ui-next/src/index.js +++ b/packages/commons-ui-next/src/index.js @@ -5,3 +5,4 @@ export * from "./Link"; export { default as RichTypography } from "./RichTypography"; export { default as RichText } from "./RichText"; +export { default as StayInTouch } from "./StayInTouch"; From 8262ac1750fe9967689943c1a055f5ef2e9096cf Mon Sep 17 00:00:00 2001 From: Kipruto <43873157+kelvinkipruto@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:43:26 +0300 Subject: [PATCH 6/9] Working footer --- apps/climatemappedafrica/next.config.js | 23 +-- .../src/components/Footer/index.js | 108 ++++++++++++ .../src/components/Footer/index.stories.js | 160 ++++++++++++++++++ .../src/components/Footer/useStyles.js | 128 ++++++++++++++ .../src/components/Page/Base.js | 8 +- .../src/pages/[[...slug]].js | 6 +- 6 files changed, 417 insertions(+), 16 deletions(-) create mode 100644 apps/climatemappedafrica/src/components/Footer/index.js create mode 100644 apps/climatemappedafrica/src/components/Footer/index.stories.js create mode 100644 apps/climatemappedafrica/src/components/Footer/useStyles.js diff --git a/apps/climatemappedafrica/next.config.js b/apps/climatemappedafrica/next.config.js index c282b8971..fa6f8ae11 100644 --- a/apps/climatemappedafrica/next.config.js +++ b/apps/climatemappedafrica/next.config.js @@ -32,16 +32,19 @@ module.exports = { "@hurumap/next", ], webpack: (config) => { - config.module.rules.push({ - test: /\.svg$/, - use: [ - "@svgr/webpack", - { - loader: "svg-url-loader", - options: {}, - }, - ], - }); + config.module.rules.push( + { + test: /\.svg$/i, + type: "asset", + resourceQuery: /url/, // *.svg?url + }, + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + resourceQuery: { not: [/url/] }, // exclude react component if *.svg?url + use: ["@svgr/webpack"], + }, + ); // eslint-disable-next-line no-param-reassign config.resolve.fallback = { ...config.resolve.fallback, diff --git a/apps/climatemappedafrica/src/components/Footer/index.js b/apps/climatemappedafrica/src/components/Footer/index.js new file mode 100644 index 000000000..fb57a814e --- /dev/null +++ b/apps/climatemappedafrica/src/components/Footer/index.js @@ -0,0 +1,108 @@ +import { QuickLinks, LogoButton, Copyright } from "@commons-ui/core"; +import { Link, RichText, StayInTouch } from "@commons-ui/next"; +import { Grid } from "@mui/material"; +import React from "react"; + +import useStyles from "./useStyles"; + +import Section from "@/climatemappedafrica/components/Section"; + +function Footer(props) { + const { title, connect, description, logo: logoProps, links } = props; + const classes = useStyles(props); + return ( +
+
+ + + {logoProps && ( + + )} + + + {description && ( + + )} + + + + + + {links && ( + + )} + + + + + + + +
+
+ ); +} + +export default Footer; diff --git a/apps/climatemappedafrica/src/components/Footer/index.stories.js b/apps/climatemappedafrica/src/components/Footer/index.stories.js new file mode 100644 index 000000000..9c46cbae2 --- /dev/null +++ b/apps/climatemappedafrica/src/components/Footer/index.stories.js @@ -0,0 +1,160 @@ +/* eslint-disable import/no-anonymous-default-export */ +import React from "react"; + +import Footer from "."; + +export default { + title: "ClimateMappedAfrica/Sections/Footer", + argTypes: { + title: { + control: { + type: "text", + }, + }, + socialMedia: { + control: { + type: "object", + }, + }, + quickLinks: { + control: { + type: "array", + }, + }, + description: { + control: { + type: "text", + }, + }, + aboutVariant: { + control: { + type: "select", + }, + options: ["subtitle1", "body1"], + }, + copyrightProps: { + control: { + type: "object", + }, + }, + logoProps: { + control: { + type: "object", + }, + }, + }, +}; + +function Template({ ...args }) { + return