diff --git a/apps/climatemappedafrica/next.config.js b/apps/climatemappedafrica/next.config.js
index 2c50a972d..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,
@@ -51,18 +54,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/package.json b/apps/climatemappedafrica/package.json
index e5a2e3fd2..460c2aa51 100644
--- a/apps/climatemappedafrica/package.json
+++ b/apps/climatemappedafrica/package.json
@@ -36,6 +36,7 @@
"@apollo/client": "catalog:",
"@commons-ui/core": "catalog:",
"@commons-ui/next": "workspace:*",
+ "@commons-ui/payload": "workspace:*",
"@emotion/react": "catalog:",
"@emotion/styled": "catalog:",
"@hurumap/core": "workspace:*",
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/components/Footer/index.js b/apps/climatemappedafrica/src/components/Footer/index.js
new file mode 100644
index 000000000..eff78692f
--- /dev/null
+++ b/apps/climatemappedafrica/src/components/Footer/index.js
@@ -0,0 +1,102 @@
+import { QuickLinks, LogoButton, Copyright } from "@commons-ui/core";
+import { Link, StayInTouch } from "@commons-ui/next";
+import { RichText } from "@commons-ui/payload";
+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 ;
+}
+
+export const Default = Template.bind({});
+
+Default.parameters = {
+ nextjs: {
+ router: {
+ pathname: "/?path=/story/sections-footer--default",
+ },
+ },
+};
+
+Default.args = {
+ connect: {
+ title: "Stay in Touch",
+ links: [
+ {
+ platform: "Facebook",
+ url: "https://www.google.com",
+ id: "67050ad20d400b5e511b2871",
+ },
+ {
+ platform: "Twitter",
+ url: "https://Twitter.com",
+ id: "6706844a6339b76017e6c782",
+ },
+ ],
+ },
+ description: [
+ {
+ children: [
+ {
+ text: "This site is an ",
+ children: null,
+ },
+ {
+ newTab: false,
+ type: "link",
+ url: "https://github.com/CodeForAfrica/ui/tree/main/apps/roboshield",
+ children: [
+ {
+ text: "open source code",
+ children: null,
+ },
+ ],
+ href: "https://github.com/CodeForAfrica/ui/tree/main/apps/roboshield",
+ },
+ {
+ text: " built by ",
+ children: null,
+ },
+ {
+ newTab: false,
+ type: "link",
+ url: "https://codeforafrica.org/",
+ children: [
+ {
+ text: "Code for Africa",
+ children: null,
+ },
+ ],
+ href: "https://codeforafrica.org/",
+ },
+ {
+ text: ", the continent's largest network of civic technology and data journalism labs. All content is released under a ",
+ children: null,
+ },
+ {
+ newTab: false,
+ type: "link",
+ url: "https://creativecommons.org/licenses/by/4.0/",
+ children: [
+ {
+ text: "Creative Commons 4 Attribution",
+ children: null,
+ },
+ ],
+ href: "https://creativecommons.org/licenses/by/4.0/",
+ },
+ {
+ text: " License. Reuse it to help empower your own community.",
+ children: null,
+ },
+ ],
+ },
+ ],
+ links: {
+ title: "Resources",
+ links: [
+ {
+ label: "About",
+ linkType: "custom",
+ url: "/",
+ href: "/",
+ id: "67063751048cfcc79c43a6f9",
+ },
+ {
+ label: "Privacy Policy",
+ linkType: "custom",
+ url: "/",
+ href: "/",
+ id: "6706375d048cfcc79c43a6fa",
+ },
+ ],
+ },
+ logo: {
+ alt: "ClimateMappedAfrica",
+ src: "http://localhost:3000/media/Group-4426.svg",
+ },
+ title: "Climate Mapped Africa",
+};
diff --git a/apps/climatemappedafrica/src/components/Footer/useStyles.js b/apps/climatemappedafrica/src/components/Footer/useStyles.js
new file mode 100644
index 000000000..603fb2cc9
--- /dev/null
+++ b/apps/climatemappedafrica/src/components/Footer/useStyles.js
@@ -0,0 +1,128 @@
+import makeStyles from "@mui/styles/makeStyles";
+
+const useStyles = makeStyles(({ breakpoints, palette, typography }) => ({
+ root: {
+ background: palette.grey.dark,
+ height: "auto",
+ padding: `${typography.pxToRem(80)} 0`,
+ [breakpoints.up("md")]: {
+ paddingTop: `${typography.pxToRem(58)}`,
+ paddingBottom: `${typography.pxToRem(82)}`,
+ },
+ },
+ section: {},
+ logoButton: {
+ margin: "0 auto",
+ padding: 0,
+ [breakpoints.up("lg")]: {
+ margin: 0,
+ },
+ },
+ allLinks: {
+ margin: "0 auto",
+ flexDirection: "row",
+ justifyContent: "center",
+ marginTop: typography.pxToRem(44.19),
+ },
+ stayInTouch: {
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ letterspacing: typography.pxToRem(0.7),
+ [breakpoints.up("lg")]: {
+ alignItems: "flex-start",
+ },
+ },
+ stayInTouchIcon: {
+ height: "auto",
+ objectFit: "none",
+ display: "flex",
+ width: "auto",
+ },
+ stayInTouchText: {
+ color: palette.text.secondary,
+ fontSize: typography.subtitle2.fontSize,
+ fontWeight: "bold",
+ padding: `${typography.pxToRem(10)} ${typography.pxToRem(8)}`,
+ [breakpoints.up("lg")]: {
+ padding: 0,
+ },
+ },
+ stayInTouchLink: {
+ padding: 0,
+ },
+ stayInTouchLinks: {
+ justifyContent: "center",
+ marginLeft: typography.pxToRem(-14), // (48 - 20) / 2
+ marginTop: typography.pxToRem(24),
+ "& > a": {
+ height: typography.pxToRem(48),
+ width: typography.pxToRem(48),
+ borderRight: "none",
+ display: "flex",
+ justifyContent: "center",
+ },
+ },
+ quickLinkRoot: {
+ textAlign: "center",
+ padding: `${typography.pxToRem(32)} 0 `,
+ [breakpoints.up("lg")]: {
+ textAlign: "inherit",
+ padding: 0,
+ },
+ },
+ quickList: {
+ listStyle: "none",
+ color: palette.text.secondary,
+ padding: 0,
+ letterspacing: typography.pxToRem(0.7),
+ "& > li": {
+ marginTop: typography.pxToRem(16),
+ },
+ },
+ quickLink: {
+ fontSize: typography.subtitle1.fontSize,
+ color: palette.text.secondary,
+ fontWeight: "normal",
+ "&:hover": {
+ color: palette.primary.light,
+ },
+ },
+ quickLinksTitle: {
+ color: palette.text.secondary,
+ fontSize: typography.subtitle2.fontSize,
+ fontWeight: "bold",
+ },
+ description: {
+ color: palette.text.secondary,
+ padding: `${typography.pxToRem(32)} 0`,
+ fontSize: typography.subtitle1.fontSize,
+ textAlign: "center",
+ [breakpoints.up("lg")]: {
+ textAlign: "left",
+ },
+ },
+ copyright: {
+ margin: 0,
+ display: "flex",
+ flexWrap: "wrap",
+ flexDirection: "row",
+ justifyContent: "center",
+ [breakpoints.up("lg")]: {
+ justifyContent: "flex-start",
+ },
+ "& > a": {
+ marginTop: typography.pxToRem(3),
+ },
+ },
+ copyrightText: {
+ color: palette.text.secondary,
+ order: 5,
+ padding: `0 ${typography.pxToRem(5)}`,
+ [breakpoints.up("lg")]: {
+ padding: `0 ${typography.pxToRem(10)}`,
+ },
+ },
+}));
+
+export default useStyles;
diff --git a/apps/climatemappedafrica/src/components/Page/Base.js b/apps/climatemappedafrica/src/components/Page/Base.js
index d7421469d..45cf7010e 100644
--- a/apps/climatemappedafrica/src/components/Page/Base.js
+++ b/apps/climatemappedafrica/src/components/Page/Base.js
@@ -1,7 +1,10 @@
+import { useMediaQuery } from "@mui/material";
+import { useTheme } from "@mui/material/styles";
import { NextSeo } from "next-seo";
import PropTypes from "prop-types";
import React from "react";
+import Footer from "@/climatemappedafrica/components/Footer";
import Navigation from "@/climatemappedafrica/components/Navigation";
import { navigationArgs } from "@/climatemappedafrica/config";
import getNavigationMenu from "@/climatemappedafrica/functions/menus/getNavigationMenu";
@@ -9,12 +12,14 @@ import getNavigationMenu from "@/climatemappedafrica/functions/menus/getNavigati
/**
* Base page that can be used to build all other pages.
*/
-function BasePage({ children, menus, variant, ...props }) {
+function BasePage({ children, menus, variant, footer: footerProps, ...props }) {
const seo = {};
const navigation = getNavigationMenu(menus?.primaryMenu || []);
const { menuProps, socialLinks } = navigation;
const { desktopLogoProps, mobileLogoProps, drawerLogoProps } = navigationArgs;
+ const theme = useTheme();
+ const isDesktop = useMediaQuery(theme.breakpoints.up("lg"));
const navigationProps = {
...props,
...menus,
@@ -53,6 +58,7 @@ function BasePage({ children, menus, variant, ...props }) {
noindex={seo?.metaRobotsNoindex !== "index"}
/>
{children}
+ {!(variant === "explore" && isDesktop) && }
>
);
}
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..15b838a2e
--- /dev/null
+++ b/apps/climatemappedafrica/src/lib/data/common/index.js
@@ -0,0 +1,61 @@
+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
+ 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");
+ const footer = getFooter(siteSettings);
+
+ return {
+ blocks,
+ footer,
+ };
+}
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..0cd7f4d68
--- /dev/null
+++ b/apps/climatemappedafrica/src/pages/[[...slug]].js
@@ -0,0 +1,10 @@
+import Page from "@/climatemappedafrica/components/Page";
+import { getPageServerSideProps } from "@/climatemappedafrica/lib/data";
+
+export default function Index(props) {
+ return ;
+}
+
+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/NavigationTab.js b/apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js
new file mode 100644
index 000000000..02da38263
--- /dev/null
+++ b/apps/climatemappedafrica/src/payload/globals/site/NavigationTab.js
@@ -0,0 +1,87 @@
+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: "footerNavigation",
+ type: "group",
+ localized: true,
+ fields: [
+ {
+ type: "text",
+ name: "title",
+ required: true,
+ localized: true,
+ label: "Title",
+ },
+ {
+ type: "collapsible",
+ label: "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..207b83587
--- /dev/null
+++ b/apps/climatemappedafrica/src/payload/globals/site/index.js
@@ -0,0 +1,22 @@
+import EngagementTab from "./EngagementTab";
+import GeneralTab from "./GeneralTab";
+import NavigationTab from "./NavigationTab";
+
+const Site = {
+ slug: "settings-site",
+ label: "Site",
+ access: {
+ read: () => true,
+ },
+ admin: {
+ group: "Settings",
+ },
+ fields: [
+ {
+ type: "tabs",
+ tabs: [GeneralTab, NavigationTab, EngagementTab],
+ },
+ ],
+};
+
+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;
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 (
/../commons-ui-core/src/$1",
+ "^@/commons-ui/next/(.*)$": "/../commons-ui-next/src/$1",
+ "^@/commons-ui/payload/(.*)$": "/src/$1",
+ },
+};
diff --git a/packages/commons-ui-payload/jest.setup.js b/packages/commons-ui-payload/jest.setup.js
new file mode 100644
index 000000000..6f6929f58
--- /dev/null
+++ b/packages/commons-ui-payload/jest.setup.js
@@ -0,0 +1,12 @@
+/* eslint-env jest */
+
+jest.mock("next/router", () => ({
+ useRouter: jest.fn().mockImplementation(() => ({
+ asPath: "",
+ isReady: true,
+ push: jest.fn(),
+ query: {},
+ })),
+}));
+
+module.exports = require("@commons-ui/testing-library/jest.setup");
diff --git a/packages/commons-ui-payload/jsconfig.json b/packages/commons-ui-payload/jsconfig.json
new file mode 100644
index 000000000..2b74ff6a9
--- /dev/null
+++ b/packages/commons-ui-payload/jsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/commons-ui/payload/*": ["./src/*"]
+ }
+ },
+ "exclude": ["node_modules"]
+}
diff --git a/packages/commons-ui-payload/package.json b/packages/commons-ui-payload/package.json
new file mode 100644
index 000000000..c93cb6d71
--- /dev/null
+++ b/packages/commons-ui-payload/package.json
@@ -0,0 +1,66 @@
+{
+ "name": "@commons-ui/payload",
+ "version": "0.0.1",
+ "private": false,
+ "author": "Code for Africa ",
+ "description": "",
+ "main": "src/index.js",
+ "keywords": [
+ "react",
+ "react-component",
+ "mui",
+ "material-ui",
+ "material design"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/codeforafrica/ui.git",
+ "directory": "packages/commons-ui-core"
+ },
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/codeforafrica/ui/issues"
+ },
+ "scripts": {
+ "jest": "jest",
+ "lint-check": "TIMING=1 eslint './'",
+ "lint": "TIMING=1 eslint --fix './'",
+ "clean": "rm -rf .turbo node_modules dist"
+ },
+ "devDependencies": {
+ "@babel/core": "catalog:",
+ "@babel/preset-react": "catalog:",
+ "@commons-ui/testing-library": "workspace:*",
+ "@emotion/react": "catalog:",
+ "@emotion/styled": "catalog:",
+ "@mui/material": "catalog:",
+ "@mui/utils": "catalog:",
+ "@types/react": "catalog:",
+ "babel-loader": "catalog:",
+ "eslint": "catalog:",
+ "eslint-config-commons-ui": "workspace:*",
+ "identity-obj-proxy": "catalog:",
+ "jest": "catalog:",
+ "jest-config-commons-ui": "workspace:*",
+ "prettier": "catalog:",
+ "react": "catalog:",
+ "react-dom": "catalog:",
+ "react-test-renderer": "catalog:",
+ "require-from-string": "catalog:",
+ "typescript": "catalog:",
+ "webpack": "catalog:"
+ },
+ "peerDependencies": {
+ "@babel/core": "catalog:",
+ "@commons-ui/core": "workspace:*",
+ "@commons-ui/next": "workspace:*",
+ "@mui/material": "catalog:",
+ "clsx": "catalog:",
+ "next": "catalog:",
+ "prop-types": "catalog:",
+ "react": "catalog:",
+ "react-dom": "catalog:",
+ "slate": "catalog:"
+ },
+ "dependencies": {}
+}
diff --git a/packages/commons-ui-payload/src/RichText/RichText.js b/packages/commons-ui-payload/src/RichText/RichText.js
new file mode 100644
index 000000000..dd1c236bd
--- /dev/null
+++ b/packages/commons-ui-payload/src/RichText/RichText.js
@@ -0,0 +1,106 @@
+/* eslint-disable react/no-array-index-key */
+import { Link, RichTypography } from "@commons-ui/next";
+import { Box } from "@mui/material";
+import React, { Fragment } from "react";
+import { Text } from "slate";
+
+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-payload/src/RichText/RichText.snap.js b/packages/commons-ui-payload/src/RichText/RichText.snap.js
new file mode 100644
index 000000000..85112ef17
--- /dev/null
+++ b/packages/commons-ui-payload/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-payload/src/RichText/RichText.test.js b/packages/commons-ui-payload/src/RichText/RichText.test.js
new file mode 100644
index 000000000..bcbed0d5d
--- /dev/null
+++ b/packages/commons-ui-payload/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-payload/src/RichText/index.js b/packages/commons-ui-payload/src/RichText/index.js
new file mode 100644
index 000000000..49b1d7e02
--- /dev/null
+++ b/packages/commons-ui-payload/src/RichText/index.js
@@ -0,0 +1,3 @@
+import RichText from "./RichText";
+
+export default RichText;
diff --git a/packages/commons-ui-payload/src/index.js b/packages/commons-ui-payload/src/index.js
new file mode 100644
index 000000000..ace4dbf6b
--- /dev/null
+++ b/packages/commons-ui-payload/src/index.js
@@ -0,0 +1,3 @@
+/* eslint-disable import/prefer-default-export */
+
+export { default as RichText } from "./RichText";
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 79e25380a..b1c903e82 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1049,6 +1049,9 @@ importers:
'@commons-ui/next':
specifier: workspace:*
version: link:../../packages/commons-ui-next
+ '@commons-ui/payload':
+ specifier: workspace:*
+ version: link:../../packages/commons-ui-payload
'@emotion/react':
specifier: 'catalog:'
version: 11.13.3(@types/react@18.3.10)(react@18.3.1)
@@ -2513,6 +2516,9 @@ importers:
clsx:
specifier: 'catalog:'
version: 2.1.1
+ slate:
+ specifier: 'catalog:'
+ version: 0.103.0
devDependencies:
'@babel/core':
specifier: 'catalog:'
@@ -2566,6 +2572,91 @@ importers:
specifier: 'catalog:'
version: 5.95.0(@swc/core@1.7.26(@swc/helpers@0.5.5))(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.2)(webpack@5.95.0(@swc/core@1.7.26(@swc/helpers@0.5.5))))
+ packages/commons-ui-payload:
+ dependencies:
+ '@commons-ui/core':
+ specifier: workspace:*
+ version: link:../commons-ui-core
+ '@commons-ui/next':
+ specifier: workspace:*
+ version: link:../commons-ui-next
+ clsx:
+ specifier: 'catalog:'
+ version: 2.1.1
+ next:
+ specifier: 'catalog:'
+ version: 14.2.13(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.2)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.69.4)
+ prop-types:
+ specifier: 'catalog:'
+ version: 15.8.1
+ slate:
+ specifier: 'catalog:'
+ version: 0.103.0
+ devDependencies:
+ '@babel/core':
+ specifier: 'catalog:'
+ version: 7.25.2
+ '@babel/preset-react':
+ specifier: 'catalog:'
+ version: 7.24.7(@babel/core@7.25.2)
+ '@commons-ui/testing-library':
+ specifier: workspace:*
+ version: link:../commons-ui-testing-library
+ '@emotion/react':
+ specifier: 'catalog:'
+ version: 11.13.3(@types/react@18.3.10)(react@18.3.1)
+ '@emotion/styled':
+ specifier: 'catalog:'
+ version: 11.13.0(@emotion/react@11.13.3(@types/react@18.3.10)(react@18.3.1))(@types/react@18.3.10)(react@18.3.1)
+ '@mui/material':
+ specifier: 'catalog:'
+ version: 5.16.7(@emotion/react@11.13.3(@types/react@18.3.10)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.10)(react@18.3.1))(@types/react@18.3.10)(react@18.3.1))(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mui/utils':
+ specifier: 'catalog:'
+ version: 5.16.6(@types/react@18.3.10)(react@18.3.1)
+ '@types/react':
+ specifier: 'catalog:'
+ version: 18.3.10
+ babel-loader:
+ specifier: 'catalog:'
+ version: 9.2.1(@babel/core@7.25.2)(webpack@5.95.0(@swc/core@1.7.26(@swc/helpers@0.5.5)))
+ eslint:
+ specifier: 'catalog:'
+ version: 8.57.1
+ eslint-config-commons-ui:
+ specifier: workspace:*
+ version: link:../eslint-config-commons-ui
+ identity-obj-proxy:
+ specifier: 'catalog:'
+ version: 3.0.0
+ jest:
+ specifier: 'catalog:'
+ version: 29.7.0(@types/node@22.7.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.5))(@types/node@22.7.4)(typescript@5.6.2))
+ jest-config-commons-ui:
+ specifier: workspace:*
+ version: link:../jest-config-commons-ui
+ prettier:
+ specifier: 'catalog:'
+ version: 3.3.3
+ react:
+ specifier: 'catalog:'
+ version: 18.3.1
+ react-dom:
+ specifier: 'catalog:'
+ version: 18.3.1(react@18.3.1)
+ react-test-renderer:
+ specifier: 'catalog:'
+ version: 18.3.1(react@18.3.1)
+ require-from-string:
+ specifier: 'catalog:'
+ version: 2.0.2
+ typescript:
+ specifier: 'catalog:'
+ version: 5.6.2
+ webpack:
+ specifier: 'catalog:'
+ version: 5.95.0(@swc/core@1.7.26(@swc/helpers@0.5.5))(webpack-cli@4.10.0(webpack-bundle-analyzer@4.10.2)(webpack@5.95.0(@swc/core@1.7.26(@swc/helpers@0.5.5))))
+
packages/commons-ui-testing-library:
dependencies:
'@testing-library/jest-dom':