diff --git a/apps/trustlab/package.json b/apps/trustlab/package.json index 119cbae88..d77da84b1 100644 --- a/apps/trustlab/package.json +++ b/apps/trustlab/package.json @@ -1,6 +1,6 @@ { "name": "trustlab", - "version": "0.0.21", + "version": "0.0.22", "private": true, "scripts": { "dev": "next dev", diff --git a/apps/trustlab/payload-types.ts b/apps/trustlab/payload-types.ts index 221f7404a..c17fa5b4e 100644 --- a/apps/trustlab/payload-types.ts +++ b/apps/trustlab/payload-types.ts @@ -490,6 +490,14 @@ export interface Page { }; [k: string]: unknown; }; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; card: { title: string; cardType?: ("items" | "richtext") | null; @@ -1109,6 +1117,14 @@ export interface Page { }; [k: string]: unknown; } | null; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; opportunityType: "incubator" | "intelligence-briefing" | "baraza"; hasFilters?: boolean | null; filterByLabel?: string | null; @@ -1347,7 +1363,7 @@ export interface Page { subtitle?: string | null; variant: "chip" | "card"; /** - * Select organizations to display in this list + * Select organisations to display in this list */ organizations?: (string | Organisation)[] | null; /** @@ -1687,6 +1703,14 @@ export interface Page { }; [k: string]: unknown; }; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; image: string | Media; /** * Logo or signature image displayed below the description @@ -2104,6 +2128,14 @@ export interface Post { }; [k: string]: unknown; }; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; card: { title: string; cardType?: ("items" | "richtext") | null; @@ -2720,6 +2752,14 @@ export interface Post { }; [k: string]: unknown; } | null; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; opportunityType: "incubator" | "intelligence-briefing" | "baraza"; hasFilters?: boolean | null; filterByLabel?: string | null; @@ -2958,7 +2998,7 @@ export interface Post { subtitle?: string | null; variant: "chip" | "card"; /** - * Select organizations to display in this list + * Select organisations to display in this list */ organizations?: (string | Organisation)[] | null; /** @@ -3298,6 +3338,14 @@ export interface Post { }; [k: string]: unknown; }; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; image: string | Media; /** * Logo or signature image displayed below the description @@ -3468,8 +3516,8 @@ export interface Organisation { [k: string]: unknown; } | null; image?: (string | null) | Media; - link: { - label: string; + includeLink: boolean; + link?: { linkType?: ("custom" | "internal") | null; doc?: { relationTo: "pages"; @@ -3477,7 +3525,6 @@ export interface Organisation { } | null; url?: string | null; href: string; - newTab?: boolean | null; }; blocks?: | ( @@ -3707,6 +3754,14 @@ export interface Organisation { }; [k: string]: unknown; }; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; card: { title: string; cardType?: ("items" | "richtext") | null; @@ -4326,6 +4381,14 @@ export interface Organisation { }; [k: string]: unknown; } | null; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; opportunityType: "incubator" | "intelligence-briefing" | "baraza"; hasFilters?: boolean | null; filterByLabel?: string | null; @@ -4564,7 +4627,7 @@ export interface Organisation { subtitle?: string | null; variant: "chip" | "card"; /** - * Select organizations to display in this list + * Select organisations to display in this list */ organizations?: (string | Organisation)[] | null; /** @@ -4904,6 +4967,14 @@ export interface Organisation { }; [k: string]: unknown; }; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; image: string | Media; /** * Logo or signature image displayed below the description @@ -5012,7 +5083,7 @@ export interface Organisation { } )[] | null; - slug?: string | null; + slug: string; updatedAt: string; createdAt: string; } @@ -5439,6 +5510,14 @@ export interface Opportunity { }; [k: string]: unknown; }; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; card: { title: string; cardType?: ("items" | "richtext") | null; @@ -6058,6 +6137,14 @@ export interface Opportunity { }; [k: string]: unknown; } | null; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; opportunityType: "incubator" | "intelligence-briefing" | "baraza"; hasFilters?: boolean | null; filterByLabel?: string | null; @@ -6296,7 +6383,7 @@ export interface Opportunity { subtitle?: string | null; variant: "chip" | "card"; /** - * Select organizations to display in this list + * Select organisations to display in this list */ organizations?: (string | Organisation)[] | null; /** @@ -6636,6 +6723,14 @@ export interface Opportunity { }; [k: string]: unknown; }; + /** + * Background color in hex format + */ + backgroundColor: string; + /** + * Text color in hex format + */ + textColor: string; image: string | Media; /** * Logo or signature image displayed below the description @@ -7061,6 +7156,8 @@ export interface PagesSelect { | { title?: T; content?: T; + backgroundColor?: T; + textColor?: T; card?: | T | { @@ -7356,6 +7453,8 @@ export interface PagesSelect { | { title?: T; description?: T; + backgroundColor?: T; + textColor?: T; opportunityType?: T; hasFilters?: T; filterByLabel?: T; @@ -7672,6 +7771,8 @@ export interface PagesSelect { | { title?: T; description?: T; + backgroundColor?: T; + textColor?: T; image?: T; signatureIcon?: T; id?: T; @@ -7837,6 +7938,8 @@ export interface PostsSelect { | { title?: T; content?: T; + backgroundColor?: T; + textColor?: T; card?: | T | { @@ -8132,6 +8235,8 @@ export interface PostsSelect { | { title?: T; description?: T; + backgroundColor?: T; + textColor?: T; opportunityType?: T; hasFilters?: T; filterByLabel?: T; @@ -8448,6 +8553,8 @@ export interface PostsSelect { | { title?: T; description?: T; + backgroundColor?: T; + textColor?: T; image?: T; signatureIcon?: T; id?: T; @@ -8576,15 +8683,14 @@ export interface OrganisationsSelect { name?: T; description?: T; image?: T; + includeLink?: T; link?: | T | { - label?: T; linkType?: T; doc?: T; url?: T; href?: T; - newTab?: T; }; blocks?: | T @@ -8676,6 +8782,8 @@ export interface OrganisationsSelect { | { title?: T; content?: T; + backgroundColor?: T; + textColor?: T; card?: | T | { @@ -8971,6 +9079,8 @@ export interface OrganisationsSelect { | { title?: T; description?: T; + backgroundColor?: T; + textColor?: T; opportunityType?: T; hasFilters?: T; filterByLabel?: T; @@ -9287,6 +9397,8 @@ export interface OrganisationsSelect { | { title?: T; description?: T; + backgroundColor?: T; + textColor?: T; image?: T; signatureIcon?: T; id?: T; @@ -9541,6 +9653,8 @@ export interface OpportunitiesSelect { | { title?: T; content?: T; + backgroundColor?: T; + textColor?: T; card?: | T | { @@ -9836,6 +9950,8 @@ export interface OpportunitiesSelect { | { title?: T; description?: T; + backgroundColor?: T; + textColor?: T; opportunityType?: T; hasFilters?: T; filterByLabel?: T; @@ -10152,6 +10268,8 @@ export interface OpportunitiesSelect { | { title?: T; description?: T; + backgroundColor?: T; + textColor?: T; image?: T; signatureIcon?: T; id?: T; diff --git a/apps/trustlab/src/components/ParticipatingOrganizationList/ChipList.js b/apps/trustlab/src/components/ParticipatingOrganizationList/ChipList.js index 015c3ed41..6401c3bc8 100644 --- a/apps/trustlab/src/components/ParticipatingOrganizationList/ChipList.js +++ b/apps/trustlab/src/components/ParticipatingOrganizationList/ChipList.js @@ -3,7 +3,7 @@ import { Link } from "@commons-ui/next"; import { Box, Chip, SvgIcon, Typography } from "@mui/material"; import { forwardRef } from "react"; -import ExternalLinkIcon from "@/trustlab/assets/icons/Type=external-link, Size=24, Color=CurrentColor.svg"; +import LinkIcon from "@/trustlab/assets/icons/Type=external-link, Size=24, Color=CurrentColor.svg"; const ChipList = forwardRef(function ChipList(props, ref) { const { title, organizations = [], sx } = props; @@ -50,8 +50,6 @@ const ChipList = forwardRef(function ChipList(props, ref) { ? { component: Link, href: org.link.href, - target: "_blank", - rel: "noopener noreferrer", clickable: true, } : {}; @@ -72,7 +70,7 @@ const ChipList = forwardRef(function ChipList(props, ref) { {hasLink && ( { - if (!org || typeof org !== "object") { +function propsifyOrg(buttonLabel) { + return function propsifyOrgWithButtonLabel(org) { + // valid org must have at least an id and a name + if (!(org?.id && org.name)) { return null; } const image = org.image ?? null; - - // For card variant, generate link from breadcrumbs/slug - const href = - variant === "card" && org.slug - ? `/${[pagePath, org.slug].filter(Boolean).join("/")}` - : org.link?.href; - + let href = null; + if (org.includeLink && org.link?.href) { + if (org.link.linkType === "custom") { + // link.href manually set to external url + ({ href } = org.link); + } else { + // linkType == internal: link.href points to org parent page via beforeValidate hook + href = `${org.link.href}/${org.slug}`; + } + } + const link = href ? { href } : null; return { id: org.id, name: org.name, description: org.description ?? null, image, - link: href ? { href } : null, + link, buttonLabel: buttonLabel || "Learn More", }; - }); + }; +} + +function getParticipatingOrganizationListBlock(block) { + const { + title, + subtitle = null, + variant, + organizations: orgs, + buttonLabel, + ...rest + } = block; + + const organizations = + orgs?.map(propsifyOrg(buttonLabel)).filter(Boolean) ?? []; return { ...rest, @@ -52,7 +47,7 @@ async function getParticipatingOrganizationListBlock(block, api) { title, subtitle, variant, - organizations: resolvedOrganizations.filter(Boolean), + organizations, }; } diff --git a/apps/trustlab/src/lib/data/pagify/opportunities.js b/apps/trustlab/src/lib/data/pagify/opportunities.js index 792589d27..ee567f382 100644 --- a/apps/trustlab/src/lib/data/pagify/opportunities.js +++ b/apps/trustlab/src/lib/data/pagify/opportunities.js @@ -1,10 +1,9 @@ -import blockify from "@/trustlab/lib/data/blockify"; import formatDate from "@/trustlab/payload/utils/formatDate"; const overviewBlockTypes = ["content-overview", "opportunity-overview"]; function pagifyOpportunities(collection) { - return async function pagifyOpportunity(api, context, parentPage) { + return async function pagifyOpportunityCollection(api, context, parentPage) { const { params, locale } = context; const { slugs } = params; if (!slugs || slugs.length < 2) { @@ -26,13 +25,13 @@ function pagifyOpportunities(collection) { const doc = docs[0]; const date = formatDate(doc.date); const contentBlocks = doc.blocks || []; - const processedBlocks = await blockify(contentBlocks, api, { locale }); - const blocks = processedBlocks.filter(Boolean).map((block) => { + // blockify is called after pagefy so *MUST* not call it here + const blocks = contentBlocks.filter(Boolean).map((block) => { if (overviewBlockTypes.includes(block.blockType)) { return { ...block, date, - location: doc.location || null, + location: doc.location ?? null, title: block.title || "Overview", }; } diff --git a/apps/trustlab/src/payload/blocks/ParticipatingOrganizationList.js b/apps/trustlab/src/payload/blocks/ParticipatingOrganizationList.js index be47bc4b4..b63a3d345 100644 --- a/apps/trustlab/src/payload/blocks/ParticipatingOrganizationList.js +++ b/apps/trustlab/src/payload/blocks/ParticipatingOrganizationList.js @@ -1,8 +1,8 @@ const ParticipatingOrganizationList = { slug: "participating-organization-list", labels: { - singular: "Participating Organization List", - plural: "Participating Organization Lists", + singular: "Participating Organisation List", + plural: "Participating Organisation Lists", }, imageURL: "/images/cms/blocks/participating-organization-list.png", fields: [ @@ -34,9 +34,11 @@ const ParticipatingOrganizationList = { type: "relationship", relationTo: "organisations", hasMany: true, - label: { en: "Organizations" }, + label: { en: "Organisations" }, admin: { - description: "Select organizations to display in this list", + description: "Select organisations to display in this list", + isSortable: true, + sortOptions: "name", }, }, { diff --git a/apps/trustlab/src/payload/collections/Organisations.js b/apps/trustlab/src/payload/collections/Organisations.js index 546181997..613287bd5 100644 --- a/apps/trustlab/src/payload/collections/Organisations.js +++ b/apps/trustlab/src/payload/collections/Organisations.js @@ -6,8 +6,8 @@ import blocks from "@/trustlab/payload/blocks"; const Organisations = { slug: "organisations", labels: { - singular: "Organization", - plural: "Organizations", + singular: "Organisation", + plural: "Organisations", }, admin: { group: "Publication", @@ -36,9 +36,22 @@ const Organisations = { name: "image", }, }), + { + name: "includeLink", + type: "checkbox", + required: true, + defaultValue: true, // We already have some links in CMS so default to showing them + }, linkGroup({ + linkConfig: { + disableLabel: true, + disableOpenInNewTab: true, + }, overrides: { - label: "Organization Link", + required: false, + admin: { + condition: (_, siblingData) => Boolean(siblingData?.includeLink), + }, }, }), { @@ -48,6 +61,9 @@ const Organisations = { }, slug({ fieldToUse: "name", + overrides: { + required: true, + }, }), ], };