From 8070c77e813fdab1a6537f41b6909d601a45244d Mon Sep 17 00:00:00 2001 From: TomTyack Date: Fri, 20 Jun 2025 14:56:31 +1000 Subject: [PATCH 01/13] fix for vercel tailwind --- src/theme/conversion/tailwind.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/theme/conversion/tailwind.config.js b/src/theme/conversion/tailwind.config.js index 4a00da9..f5efe47 100644 --- a/src/theme/conversion/tailwind.config.js +++ b/src/theme/conversion/tailwind.config.js @@ -6,14 +6,20 @@ module.exports = { "pages/**/*.{ts,tsx}", "theme/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", + "./node_modules/@conversiondigital/headless-basics-components/src/components/**/*.{ts,tsx}", "./../headless-basics-components/src/components/**/*.{ts,tsx}", + "../../node_modules/@conversiondigital/headless-basics-components/src/components/**/*.{ts,tsx}", "./../../../headless-basics-components/src/components/**/*.{ts,tsx}", + "./node_modules/@conversiondigital/headless-basics-components/src/theme/default/**/*.{ts,tsx}", "./../headless-basics-components/src/theme/default/**/*.{ts,tsx}", + "../../node_modules/@conversiondigital/headless-basics-components/src/theme/default/**/*.{ts,tsx}", "./../../../headless-basics-components/src/theme/default/**/*.{ts,tsx}", + "./node_modules/@conversiondigital/headless-basics-components/src/theme/conversion/**/*.{ts,tsx}", "./../headless-basics-components/src/theme/conversion/**/*.{ts,tsx}", + "../../node_modules/@conversiondigital/headless-basics-components/src/theme/conversion/**/*.{ts,tsx}", "./../../../headless-basics-components/src/theme/conversion/**/*.{ts,tsx}", './src/**/*.{js,jsx,ts,tsx}' ], From b2449cf30cfe63c4f4335d03a73b9e73f4878216 Mon Sep 17 00:00:00 2001 From: Steven Cao Date: Mon, 23 Jun 2025 09:40:22 +0700 Subject: [PATCH 02/13] HEAD-124: fixed config sanity --- .../conversion/components/cdserviceintro/sanity-schema.ts | 2 +- .../components/cdserviceofferings/sanity-schema.ts | 2 +- .../conversion/components/cdservicestats/sanity-schema.ts | 2 +- src/theme/conversion/index.ts | 6 +----- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/theme/conversion/components/cdserviceintro/sanity-schema.ts b/src/theme/conversion/components/cdserviceintro/sanity-schema.ts index be81d82..419c1a6 100644 --- a/src/theme/conversion/components/cdserviceintro/sanity-schema.ts +++ b/src/theme/conversion/components/cdserviceintro/sanity-schema.ts @@ -4,7 +4,7 @@ import { DocumentTextIcon } from '@sanity/icons' export default defineType({ name: 'cdserviceintro', title: 'Service Introduction (CD)', - type: 'document', + type: 'object', icon: DocumentTextIcon, fields: [ defineField({ diff --git a/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts b/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts index 6694026..a94a148 100644 --- a/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts +++ b/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts @@ -4,7 +4,7 @@ import { TagIcon } from '@sanity/icons' export default defineType({ name: 'cdserviceofferings', title: 'Service Offerings (CD)', - type: 'document', + type: 'object', icon: TagIcon, fields: [ defineField({ diff --git a/src/theme/conversion/components/cdservicestats/sanity-schema.ts b/src/theme/conversion/components/cdservicestats/sanity-schema.ts index 5fd7e57..62ccd54 100644 --- a/src/theme/conversion/components/cdservicestats/sanity-schema.ts +++ b/src/theme/conversion/components/cdservicestats/sanity-schema.ts @@ -4,7 +4,7 @@ import { BarChartIcon } from '@sanity/icons' export default defineType({ name: 'cdservicestats', title: 'Service Statistics (CD)', - type: 'document', + type: 'object', icon: BarChartIcon, fields: [ defineField({ diff --git a/src/theme/conversion/index.ts b/src/theme/conversion/index.ts index 4454fef..98908ef 100644 --- a/src/theme/conversion/index.ts +++ b/src/theme/conversion/index.ts @@ -16,8 +16,4 @@ export { default as cdcta } from "./components/cdcta/index"; // New service page components export { default as cdserviceintro } from "./components/cdserviceintro/index"; export { default as cdservicestats } from "./components/cdservicestats/index"; -export { default as cdserviceofferings } from "./components/cdserviceofferings/index"; -export { default as cdservicedetail } from "./components/cdservicedetail/index"; - -// Page structures -export { default as ServicePage } from "./structures/servicepage/index"; \ No newline at end of file +export { default as cdserviceofferings } from "./components/cdserviceofferings/index"; \ No newline at end of file From ac01e4109e86e957b6c2e85ca22fdff55e8f7d12 Mon Sep 17 00:00:00 2001 From: Steven Cao Date: Mon, 30 Jun 2025 21:59:10 +0700 Subject: [PATCH 03/13] HEAD-124: update sanity schema service page --- .../cdserviceintro/sanity-schema.ts | 26 +++-- .../cdserviceofferings/sanity-schema.ts | 110 ++++++++++-------- .../cdservicestats/sanity-schema.ts | 40 ++++--- .../components/sitemap/sanity-query.ts | 1 + 4 files changed, 99 insertions(+), 78 deletions(-) diff --git a/src/theme/conversion/components/cdserviceintro/sanity-schema.ts b/src/theme/conversion/components/cdserviceintro/sanity-schema.ts index 419c1a6..2966ee9 100644 --- a/src/theme/conversion/components/cdserviceintro/sanity-schema.ts +++ b/src/theme/conversion/components/cdserviceintro/sanity-schema.ts @@ -1,10 +1,11 @@ import { defineField, defineType } from 'sanity' import { DocumentTextIcon } from '@sanity/icons' +// Define a separate document type for cdserviceintro export default defineType({ name: 'cdserviceintro', title: 'Service Introduction (CD)', - type: 'object', + type: 'document', icon: DocumentTextIcon, fields: [ defineField({ @@ -61,8 +62,14 @@ export default defineType({ fields: [ { name: 'href', - type: 'url', + type: 'string', title: 'URL' + }, + { + name: 'blank', + type: 'boolean', + title: 'Open in new tab', + initialValue: false } ] } @@ -76,14 +83,15 @@ export default defineType({ name: 'sortOrder', title: 'Sort Order', type: 'number' - }), - defineField({ - name: 'globalComponentSource', - title: 'Global Component Source', - type: 'reference', - to: [{ type: 'cdserviceintro' }], - description: 'Select a global re-usable service introduction.' }) + // Temporarily removing the globalComponentSource field as it may be causing circular references + // defineField({ + // name: 'globalComponentSource', + // title: 'Global Component Source', + // type: 'reference', + // to: [{ type: 'cdserviceintro' }], + // description: 'Select a global re-usable service introduction.' + // }) ], preview: { select: { diff --git a/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts b/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts index a94a148..79b8416 100644 --- a/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts +++ b/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts @@ -1,11 +1,66 @@ import { defineField, defineType } from 'sanity' -import { TagIcon } from '@sanity/icons' +import { EyeOpenIcon } from '@sanity/icons' + +// Define a separate type for the offering object +export const cdserviceOfferingItem = defineType({ + name: 'cdserviceOfferingItem', + title: 'Service Offering Item', + type: 'object', + fields: [ + defineField({ + name: 'title', + title: 'Title', + type: 'string', + description: 'The name of the service offering' + }), + defineField({ + name: 'icon', + title: 'Icon', + type: 'string', + description: 'The icon name to display for this offering' + }), + defineField({ + name: 'id', + title: 'ID', + type: 'slug', + description: 'A unique identifier for this service offering', + }) + ] +}) + +// Define a separate type for the service detail object +export const cdserviceDetailItem = defineType({ + name: 'cdserviceDetailItem', + title: 'Service Detail Item', + type: 'object', + fields: [ + defineField({ + name: 'id', + title: 'ID', + type: 'string', + description: 'A unique identifier for this service detail (should match an offering ID)' + }), + defineField({ + name: 'title', + title: 'Title', + type: 'string', + description: 'The title of the service detail' + }), + defineField({ + name: 'content', + title: 'Content', + type: 'array', + of: [{ type: 'block' }], + description: 'The rich text content for this service detail' + }) + ] +}) export default defineType({ name: 'cdserviceofferings', title: 'Service Offerings (CD)', type: 'object', - icon: TagIcon, + icon: EyeOpenIcon, fields: [ defineField({ name: 'selectableVariant', @@ -34,30 +89,7 @@ export default defineType({ title: 'Service Offerings', type: 'array', of: [ - { - type: 'object', - title: 'Offering', - fields: [ - { - name: 'title', - title: 'Title', - type: 'string', - description: 'The name of the service offering' - }, - { - name: 'icon', - title: 'Icon', - type: 'string', - description: 'The icon name to display for this offering' - }, - { - name: 'id', - title: 'ID', - type: 'slug', - description: 'A unique identifier for this service offering', - } - ] - } + { type: 'cdserviceOfferingItem' } ], }), defineField({ @@ -65,31 +97,7 @@ export default defineType({ title: 'Service Details', type: 'array', of: [ - { - type: 'object', - title: 'Service Detail', - fields: [ - { - name: 'id', - title: 'ID', - type: 'string', - description: 'A unique identifier for this service detail (should match an offering ID)' - }, - { - name: 'title', - title: 'Title', - type: 'string', - description: 'The title of the service detail' - }, - { - name: 'content', - title: 'Content', - type: 'array', - of: [{ type: 'block' }], - description: 'The rich text content for this service detail' - } - ] - } + { type: 'cdserviceDetailItem' } ], description: 'The detailed content for each service offering' }), diff --git a/src/theme/conversion/components/cdservicestats/sanity-schema.ts b/src/theme/conversion/components/cdservicestats/sanity-schema.ts index 62ccd54..0751039 100644 --- a/src/theme/conversion/components/cdservicestats/sanity-schema.ts +++ b/src/theme/conversion/components/cdservicestats/sanity-schema.ts @@ -1,6 +1,27 @@ import { defineField, defineType } from 'sanity' import { BarChartIcon } from '@sanity/icons' +// Define a separate type for the statistic object +export const cdserviceStatItem = defineType({ + name: 'cdserviceStatItem', + title: 'Service Statistic Item', + type: 'object', + fields: [ + defineField({ + name: 'value', + title: 'Value', + type: 'string', + description: 'The statistical value to display (e.g., "93%", "5.14")' + }), + defineField({ + name: 'description', + title: 'Description', + type: 'string', + description: 'The description of what the statistic represents' + }) + ] +}) + export default defineType({ name: 'cdservicestats', title: 'Service Statistics (CD)', @@ -22,24 +43,7 @@ export default defineType({ title: 'Statistics', type: 'array', of: [ - { - type: 'object', - title: 'Statistic', - fields: [ - { - name: 'value', - title: 'Value', - type: 'string', - description: 'The statistical value to display (e.g., "93%", "5.14")' - }, - { - name: 'description', - title: 'Description', - type: 'string', - description: 'The description of what the statistic represents' - } - ] - } + { type: 'cdserviceStatItem' } ], }), defineField({ diff --git a/src/theme/default/components/sitemap/sanity-query.ts b/src/theme/default/components/sitemap/sanity-query.ts index dcaff75..aaba696 100644 --- a/src/theme/default/components/sitemap/sanity-query.ts +++ b/src/theme/default/components/sitemap/sanity-query.ts @@ -26,6 +26,7 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails) { } components { __typename + _type } } } From cf0c0c35ed7edc8ae22c6a88d187235c4d632ced Mon Sep 17 00:00:00 2001 From: Steven Cao Date: Tue, 1 Jul 2025 23:35:22 +0700 Subject: [PATCH 04/13] HEAD-124: update sanity schema service page --- src/theme/default/components/sitemap/sanity-query.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/theme/default/components/sitemap/sanity-query.ts b/src/theme/default/components/sitemap/sanity-query.ts index aaba696..dcaff75 100644 --- a/src/theme/default/components/sitemap/sanity-query.ts +++ b/src/theme/default/components/sitemap/sanity-query.ts @@ -26,7 +26,6 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails) { } components { __typename - _type } } } From e44611e123502ba65191a58310107ddaf0ba5816 Mon Sep 17 00:00:00 2001 From: Steven Cao Date: Thu, 3 Jul 2025 04:26:45 +0700 Subject: [PATCH 05/13] feat(HEAD-124): update schema and component for /services/search-engine-optimisation page --- package.json | 3 +- .../components/cdfaqs/classification.ts | 1 + .../components/index.tsx | 10 +- .../variants/CdFaqsDefaultVariant.tsx | 79 +++++++++++ .../{cdservicestats => cdfaqs}/index.ts | 4 +- .../sanity-mapping.ts | 4 +- .../sanity-query.ts | 64 +++------ .../components/cdfaqs/sanity-schema.ts | 127 ++++++++++++++++++ .../{cdservicestats => cdfaqs}/view.tsx | 4 +- .../cdintroduction/classification.ts | 1 + .../components/index.tsx | 10 +- .../variants/CdIntroductionDefaultVariant.tsx | 24 ++++ .../index.ts | 4 +- .../sanity-mapping.ts | 4 +- .../sanity-query.ts | 20 ++- .../sanity-schema.ts | 40 +++--- .../view.tsx | 4 +- .../components/cdnav/sanity-schema.ts | 21 ++- .../cdserviceintro/classification.ts | 1 - .../variants/cdserviceintroDefaultVariant.tsx | 27 ---- .../cdserviceofferings/classification.ts | 1 - .../cdserviceofferingsDefaultVariant.tsx | 102 -------------- .../cdserviceofferings/sanity-schema.ts | 122 ----------------- .../cdservicestats/classification.ts | 1 - .../components/cdstatistics/classification.ts | 1 + .../components/index.tsx | 10 +- .../variants/CdStatisticsDefaultVariant.tsx} | 10 +- .../index.ts | 4 +- .../sanity-mapping.ts | 6 +- .../sanity-query.ts | 8 +- .../sanity-schema.ts | 33 +++-- .../view.tsx | 4 +- src/theme/conversion/index.ts | 6 +- .../structures/servicepage/sanity-mapping.ts | 97 ------------- .../structures/servicepage/sanity-query.ts | 117 ---------------- src/theme/conversion/styles/globals.css | 23 ++-- src/theme/conversion/tailwind.config.js | 3 +- 37 files changed, 382 insertions(+), 618 deletions(-) create mode 100644 src/theme/conversion/components/cdfaqs/classification.ts rename src/theme/conversion/components/{cdserviceintro => cdfaqs}/components/index.tsx (53%) create mode 100644 src/theme/conversion/components/cdfaqs/components/variants/CdFaqsDefaultVariant.tsx rename src/theme/conversion/components/{cdservicestats => cdfaqs}/index.ts (76%) rename src/theme/conversion/components/{cdserviceofferings => cdfaqs}/sanity-mapping.ts (86%) rename src/theme/conversion/components/{cdserviceofferings => cdfaqs}/sanity-query.ts (50%) create mode 100644 src/theme/conversion/components/cdfaqs/sanity-schema.ts rename src/theme/conversion/components/{cdservicestats => cdfaqs}/view.tsx (67%) create mode 100644 src/theme/conversion/components/cdintroduction/classification.ts rename src/theme/conversion/components/{cdservicestats => cdintroduction}/components/index.tsx (62%) create mode 100644 src/theme/conversion/components/cdintroduction/components/variants/CdIntroductionDefaultVariant.tsx rename src/theme/conversion/components/{cdserviceintro => cdintroduction}/index.ts (76%) rename src/theme/conversion/components/{cdserviceintro => cdintroduction}/sanity-mapping.ts (87%) rename src/theme/conversion/components/{cdserviceintro => cdintroduction}/sanity-query.ts (75%) rename src/theme/conversion/components/{cdserviceintro => cdintroduction}/sanity-schema.ts (68%) rename src/theme/conversion/components/{cdserviceintro => cdintroduction}/view.tsx (67%) delete mode 100644 src/theme/conversion/components/cdserviceintro/classification.ts delete mode 100644 src/theme/conversion/components/cdserviceintro/components/variants/cdserviceintroDefaultVariant.tsx delete mode 100644 src/theme/conversion/components/cdserviceofferings/classification.ts delete mode 100644 src/theme/conversion/components/cdserviceofferings/components/variants/cdserviceofferingsDefaultVariant.tsx delete mode 100644 src/theme/conversion/components/cdserviceofferings/sanity-schema.ts delete mode 100644 src/theme/conversion/components/cdservicestats/classification.ts create mode 100644 src/theme/conversion/components/cdstatistics/classification.ts rename src/theme/conversion/components/{cdserviceofferings => cdstatistics}/components/index.tsx (52%) rename src/theme/conversion/components/{cdservicestats/components/variants/cdservicestatsDefaultVariant.tsx => cdstatistics/components/variants/CdStatisticsDefaultVariant.tsx} (72%) rename src/theme/conversion/components/{cdserviceofferings => cdstatistics}/index.ts (74%) rename src/theme/conversion/components/{cdservicestats => cdstatistics}/sanity-mapping.ts (78%) rename src/theme/conversion/components/{cdservicestats => cdstatistics}/sanity-query.ts (89%) rename src/theme/conversion/components/{cdservicestats => cdstatistics}/sanity-schema.ts (70%) rename src/theme/conversion/components/{cdserviceofferings => cdstatistics}/view.tsx (65%) delete mode 100644 src/theme/conversion/structures/servicepage/sanity-mapping.ts delete mode 100644 src/theme/conversion/structures/servicepage/sanity-query.ts diff --git a/package.json b/package.json index be0b5d0..a3d5b2b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "test": "jest", "chromatic": "npx chromatic --project-token=chpt_219e0bf9f15c669", "postbuild": "find dist -name '*.js' -exec sed -i 's/\\.css\\.js/\\.css/g' {} +" - }, + }, "dependencies": { "@babel/cli": "7.23.9", "@babel/core": "7.24.0", @@ -48,6 +48,7 @@ "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/react-fontawesome": "^0.2.2", "@portabletext/react": "^3.0.0", + "@portabletext/to-html": "^2.0.14", "@radix-ui/colors": "^0.1.8", "@radix-ui/react-accessible-icon": "^1.1.0", "@radix-ui/react-accordion": "^1.2.0", diff --git a/src/theme/conversion/components/cdfaqs/classification.ts b/src/theme/conversion/components/cdfaqs/classification.ts new file mode 100644 index 0000000..7ae186a --- /dev/null +++ b/src/theme/conversion/components/cdfaqs/classification.ts @@ -0,0 +1 @@ +export default ["cdfaqs", "faqs", "cdfaqs"] diff --git a/src/theme/conversion/components/cdserviceintro/components/index.tsx b/src/theme/conversion/components/cdfaqs/components/index.tsx similarity index 53% rename from src/theme/conversion/components/cdserviceintro/components/index.tsx rename to src/theme/conversion/components/cdfaqs/components/index.tsx index 7223819..bd1c770 100644 --- a/src/theme/conversion/components/cdserviceintro/components/index.tsx +++ b/src/theme/conversion/components/cdfaqs/components/index.tsx @@ -2,19 +2,19 @@ import React from 'react'; import { componentBoilerPlate } from "@conversiondigital/headless-basics-data/src/component-tools/componentBoilerPlate"; import { ViewComponentProps } from "@conversiondigital/headless-basics-data/src/interfaces"; import { getLogger, logPrefix } from "@conversiondigital/headless-basics-data/src"; -import CdserviceintroDefaultVariant from './variants/cdserviceintroDefaultVariant'; +import CdFaqsDefaultVariant from './variants/CdFaqsDefaultVariant'; -export const log = getLogger("conversion.components.cdserviceintro"); +export const log = getLogger("conversion.components.cdfaqs"); -export default function CdserviceintroUI(dynamicComponent: ViewComponentProps) { +export default function CdFaqsUI(dynamicComponent: ViewComponentProps) { const { variant, matchingData } = componentBoilerPlate(dynamicComponent); if (!matchingData) return null; - log.trace(`${logPrefix()} cdserviceintroUI started, matchingData: ${JSON.stringify(matchingData)}`); + log.trace(`${logPrefix()} CdFaqsUI started, matchingData: ${JSON.stringify(matchingData)}`); switch (variant) { case 'default': default: - return ; + return ; } } \ No newline at end of file diff --git a/src/theme/conversion/components/cdfaqs/components/variants/CdFaqsDefaultVariant.tsx b/src/theme/conversion/components/cdfaqs/components/variants/CdFaqsDefaultVariant.tsx new file mode 100644 index 0000000..e10732a --- /dev/null +++ b/src/theme/conversion/components/cdfaqs/components/variants/CdFaqsDefaultVariant.tsx @@ -0,0 +1,79 @@ +"use client"; +import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; +import { toHTML } from "@portabletext/to-html"; +import React, { useState } from "react"; + +interface FaqItem { + title: string; + richtextRaw: any[]; +} + +const CdFaqsDefaultVariant: React.FC = ({ matchingData }) => { + const [indexActive, setIndexActive] = useState(0); + + const title = matchingData?.title || ""; + const description = matchingData?.description || ""; + const items = matchingData?.items || []; + const uniqueId = matchingData?._key || ""; + + return ( +
+
+
+
+

{title}

+

{description}

+
+
+
+ {items.map((item: FaqItem, index: number) => { + return ( +
setIndexActive(index)} + role="button" + tabIndex={0} + aria-pressed={index === indexActive} + > +

+ {item.title} +

+ + + +
+ ); + })} +
+
+ {items.map((item: FaqItem, index: number) => { + return ( +
+
+
+ ); + })} +
+
+
+
+
+ ); +}; + +export default CdFaqsDefaultVariant; diff --git a/src/theme/conversion/components/cdservicestats/index.ts b/src/theme/conversion/components/cdfaqs/index.ts similarity index 76% rename from src/theme/conversion/components/cdservicestats/index.ts rename to src/theme/conversion/components/cdfaqs/index.ts index e14148a..0d09c5d 100644 --- a/src/theme/conversion/components/cdservicestats/index.ts +++ b/src/theme/conversion/components/cdfaqs/index.ts @@ -2,10 +2,10 @@ import { getLogger, getThemeConfig } from "@conversiondigital/headless-basics-da import { View } from "./view"; import { ThemeConfig } from "@conversiondigital/headless-basics-data/src/interfaces" -getLogger("theme.components.cdservicestats") +getLogger("theme.components.cdfaqs") async function getConfig(): Promise { - const config = await getThemeConfig('cdservicestats'); + const config = await getThemeConfig('cdfaqs'); config.view = View; return config; } diff --git a/src/theme/conversion/components/cdserviceofferings/sanity-mapping.ts b/src/theme/conversion/components/cdfaqs/sanity-mapping.ts similarity index 86% rename from src/theme/conversion/components/cdserviceofferings/sanity-mapping.ts rename to src/theme/conversion/components/cdfaqs/sanity-mapping.ts index f32e369..75f5ebc 100644 --- a/src/theme/conversion/components/cdserviceofferings/sanity-mapping.ts +++ b/src/theme/conversion/components/cdfaqs/sanity-mapping.ts @@ -1,7 +1,7 @@ import { getLogger, logPrefix, PageAndSingleComponentDetails } from "@conversiondigital/headless-basics-data/src" import { extractComponentsFromSanityData } from "@conversiondigital/headless-basics-data/src/cms/sanity/sanityMappingUtils" -export const log = getLogger("conversion.components.sanity.cdserviceofferings.mapping") +export const log = getLogger("conversion.components.sanity.cdfaqs.mapping") export async function mapIdentifierData(pageAndComponentCombo: PageAndSingleComponentDetails) { log.trace( @@ -10,7 +10,7 @@ export async function mapIdentifierData(pageAndComponentCombo: PageAndSingleComp const content = pageAndComponentCombo?.component?.data - const matchingData = extractComponentsFromSanityData(content, "cdserviceofferings", log); + const matchingData = extractComponentsFromSanityData(content, "cdfaqs", log); return matchingData } diff --git a/src/theme/conversion/components/cdserviceofferings/sanity-query.ts b/src/theme/conversion/components/cdfaqs/sanity-query.ts similarity index 50% rename from src/theme/conversion/components/cdserviceofferings/sanity-query.ts rename to src/theme/conversion/components/cdfaqs/sanity-query.ts index e77ef55..efd2251 100644 --- a/src/theme/conversion/components/cdserviceofferings/sanity-query.ts +++ b/src/theme/conversion/components/cdfaqs/sanity-query.ts @@ -2,28 +2,20 @@ import { PageAndSingleComponentDetails } from "@conversiondigital/headless-basic export function query(pageAndComponentCombo: PageAndSingleComponentDetails): string { return ` - query GetCdserviceofferingsBySlug($slug: String!) { + query GetCdFaqsBySlug($slug: String!) { allPage(where: { slug: { current: { eq: $slug } } }) { components { __typename - ... on Cdserviceofferings { + ... on Cdfaqs { __typename _key _type selectableVariant title - intro - offerings { + description + items { title - icon - id { - current - } - } - services { - id - title - content + richtextRaw } sortOrder globalComponentSource { @@ -31,44 +23,28 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str _key _type title - intro - offerings { - title - icon - id { - current - } - } - services { - id + description + items { title - content + richtextRaw } } } } } - allServicePage { + allHomepage { components { __typename - ... on Cdserviceofferings { + ... on Cdfaqs { __typename _key _type selectableVariant title - intro - offerings { + description + items { title - icon - id { - current - } - } - services { - id - title - content + richtextRaw } sortOrder globalComponentSource { @@ -76,18 +52,10 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str _key _type title - intro - offerings { - title - icon - id { - current - } - } - services { - id + description + items { title - content + richtextRaw } } } diff --git a/src/theme/conversion/components/cdfaqs/sanity-schema.ts b/src/theme/conversion/components/cdfaqs/sanity-schema.ts new file mode 100644 index 0000000..158df17 --- /dev/null +++ b/src/theme/conversion/components/cdfaqs/sanity-schema.ts @@ -0,0 +1,127 @@ +import { defineField, defineType } from 'sanity' +import { EyeOpenIcon } from '@sanity/icons' + +// Define a separate type for the FAQ item object +export const cdFaqItem = defineType({ + name: 'cdFaqItem', + title: 'FAQ Item', + type: 'object', + fields: [ + defineField({ + name: 'title', + title: 'Title', + type: 'string', + description: 'The title of the FAQ item' + }), + defineField({ + name: 'richtext', + title: 'Description', + type: 'array', + of: [ + { + type: 'block', + styles: [ + {title: 'Normal', value: 'normal'}, + {title: 'H2', value: 'h2'}, + {title: 'H3', value: 'h3'}, + {title: 'H4', value: 'h4'}, + {title: 'Quote', value: 'blockquote'} + ], + lists: [ + {title: 'Bullet', value: 'bullet'}, + {title: 'Numbered', value: 'number'} + ], + marks: { + decorators: [ + {title: 'Strong', value: 'strong'}, + {title: 'Emphasis', value: 'em'}, + {title: 'Underline', value: 'underline'} + ], + annotations: [ + { + name: 'link', + type: 'object', + title: 'Link', + fields: [ + { + name: 'href', + type: 'string', + title: 'URL' + }, + { + name: 'blank', + type: 'boolean', + title: 'Open in new tab', + initialValue: false + } + ] + } + ] + } + } + ], + description: 'The rich text content for the FAQ item' + }) + ], + preview: { + select: { + title: 'title' + } + } +}) + +export default defineType({ + name: 'cdfaqs', + title: 'FAQs (CD)', + type: 'object', + icon: EyeOpenIcon, + fields: [ + defineField({ + name: 'selectableVariant', + title: 'Selectable Variant', + type: 'string', + options: { + list: [ + { title: 'Default', value: 'default' } + ] + } + }), + defineField({ + name: 'title', + title: 'Title', + type: 'string', + description: 'The main title for the FAQs section (e.g., "Frequently Asked Questions")' + }), + defineField({ + name: 'description', + title: 'Introduction Text', + type: 'text', + description: 'A brief description of the FAQs' + }), + defineField({ + name: 'items', + title: 'FAQ Items', + type: 'array', + of: [ + { type: 'cdFaqItem' } + ], + }), + defineField({ + name: 'sortOrder', + title: 'Sort Order', + type: 'number' + }), + defineField({ + name: 'globalComponentSource', + title: 'Global Component Source', + type: 'reference', + to: [{ type: 'cdfaqs' }], + description: 'Select a global re-usable FAQs component.' + }) + ], + preview: { + select: { + title: 'title' + } + } +}) \ No newline at end of file diff --git a/src/theme/conversion/components/cdservicestats/view.tsx b/src/theme/conversion/components/cdfaqs/view.tsx similarity index 67% rename from src/theme/conversion/components/cdservicestats/view.tsx rename to src/theme/conversion/components/cdfaqs/view.tsx index 39aaf49..54cae12 100644 --- a/src/theme/conversion/components/cdservicestats/view.tsx +++ b/src/theme/conversion/components/cdfaqs/view.tsx @@ -2,10 +2,10 @@ import { ViewComponentProps } from "@conversiondigital/headless-basics-data/src"; import dynamic from "next/dynamic" -const CdservicestatsUI = dynamic(() => import("./components"), { +const CdFaqsUI = dynamic(() => import("./components"), { loading: () => (

Loading...

) }); export async function View(dynamicComponent: ViewComponentProps) { - return + return } diff --git a/src/theme/conversion/components/cdintroduction/classification.ts b/src/theme/conversion/components/cdintroduction/classification.ts new file mode 100644 index 0000000..f479165 --- /dev/null +++ b/src/theme/conversion/components/cdintroduction/classification.ts @@ -0,0 +1 @@ +export default ["cdintroduction", "introduction", "introduction-cd"] \ No newline at end of file diff --git a/src/theme/conversion/components/cdservicestats/components/index.tsx b/src/theme/conversion/components/cdintroduction/components/index.tsx similarity index 62% rename from src/theme/conversion/components/cdservicestats/components/index.tsx rename to src/theme/conversion/components/cdintroduction/components/index.tsx index 4dd3aca..fb690d2 100644 --- a/src/theme/conversion/components/cdservicestats/components/index.tsx +++ b/src/theme/conversion/components/cdintroduction/components/index.tsx @@ -2,19 +2,19 @@ import React from 'react'; import { componentBoilerPlate } from "@conversiondigital/headless-basics-data/src/component-tools/componentBoilerPlate"; import { ViewComponentProps } from "@conversiondigital/headless-basics-data/src/interfaces"; import { getLogger, logPrefix } from "@conversiondigital/headless-basics-data/src"; -import CdservicestatsDefaultVariant from './variants/cdservicestatsDefaultVariant'; +import CdIntroductionDefaultVariant from './variants/CdIntroductionDefaultVariant'; -export const log = getLogger("conversion.components.cdservicestats"); +export const log = getLogger("conversion.components.cdintroduction"); -export default function CdservicestatsUI(dynamicComponent: ViewComponentProps) { +export default function CdIntroductionUI(dynamicComponent: ViewComponentProps) { const { variant, matchingData } = componentBoilerPlate(dynamicComponent); if (!matchingData) return null; - log.trace(`${logPrefix()} cdservicestatsUI started, matchingData: ${JSON.stringify(matchingData)}`); + log.trace(`${logPrefix()} CdIntroductionUI started, matchingData: ${JSON.stringify(matchingData)}`); switch (variant) { case 'default': default: - return ; + return ; } } \ No newline at end of file diff --git a/src/theme/conversion/components/cdintroduction/components/variants/CdIntroductionDefaultVariant.tsx b/src/theme/conversion/components/cdintroduction/components/variants/CdIntroductionDefaultVariant.tsx new file mode 100644 index 0000000..82d6c11 --- /dev/null +++ b/src/theme/conversion/components/cdintroduction/components/variants/CdIntroductionDefaultVariant.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; +import {toHTML} from '@portabletext/to-html' + +const CdIntroductionDefaultVariant: React.FC = ({ matchingData }) => { + const title = matchingData?.title || ''; + const content = matchingData?.richtextRaw || ''; + + return ( +
+
+
+

{title}

+
+
+
+
+ ); +}; + +export default CdIntroductionDefaultVariant; \ No newline at end of file diff --git a/src/theme/conversion/components/cdserviceintro/index.ts b/src/theme/conversion/components/cdintroduction/index.ts similarity index 76% rename from src/theme/conversion/components/cdserviceintro/index.ts rename to src/theme/conversion/components/cdintroduction/index.ts index 8cf23bb..393a44c 100644 --- a/src/theme/conversion/components/cdserviceintro/index.ts +++ b/src/theme/conversion/components/cdintroduction/index.ts @@ -2,10 +2,10 @@ import { getLogger, getThemeConfig } from "@conversiondigital/headless-basics-da import { View } from "./view"; import { ThemeConfig } from "@conversiondigital/headless-basics-data/src/interfaces" -getLogger("theme.components.cdserviceintro") +getLogger("theme.components.cdintroduction") async function getConfig(): Promise { - const config = await getThemeConfig('cdserviceintro'); + const config = await getThemeConfig('cdintroduction'); config.view = View; return config; } diff --git a/src/theme/conversion/components/cdserviceintro/sanity-mapping.ts b/src/theme/conversion/components/cdintroduction/sanity-mapping.ts similarity index 87% rename from src/theme/conversion/components/cdserviceintro/sanity-mapping.ts rename to src/theme/conversion/components/cdintroduction/sanity-mapping.ts index e7b9f29..240dbb8 100644 --- a/src/theme/conversion/components/cdserviceintro/sanity-mapping.ts +++ b/src/theme/conversion/components/cdintroduction/sanity-mapping.ts @@ -1,14 +1,14 @@ import { getLogger, logPrefix, PageAndSingleComponentDetails } from "@conversiondigital/headless-basics-data/src" import { extractComponentsFromSanityData } from "@conversiondigital/headless-basics-data/src/cms/sanity/sanityMappingUtils" -export const log = getLogger("conversion.components.sanity.cdserviceintro.mapping") +export const log = getLogger("conversion.components.sanity.cdintroduction.mapping") export async function mapIdentifierData(pageAndComponentCombo: PageAndSingleComponentDetails) { log.trace( `${logPrefix()}[${pageAndComponentCombo.component.identifier}][${pageAndComponentCombo.page.source}][${pageAndComponentCombo.page.preliminarySlug}] mapIdentifierData started, ${JSON.stringify(pageAndComponentCombo?.component?.data)}` ); const content = pageAndComponentCombo?.component?.data - const matchingData = extractComponentsFromSanityData(content, "cdserviceintro", log); + const matchingData = extractComponentsFromSanityData(content, "cdintroduction", log); return matchingData } \ No newline at end of file diff --git a/src/theme/conversion/components/cdserviceintro/sanity-query.ts b/src/theme/conversion/components/cdintroduction/sanity-query.ts similarity index 75% rename from src/theme/conversion/components/cdserviceintro/sanity-query.ts rename to src/theme/conversion/components/cdintroduction/sanity-query.ts index 260b921..3f2a4bd 100644 --- a/src/theme/conversion/components/cdserviceintro/sanity-query.ts +++ b/src/theme/conversion/components/cdintroduction/sanity-query.ts @@ -2,49 +2,45 @@ import { PageAndSingleComponentDetails } from "@conversiondigital/headless-basic export function query(pageAndComponentCombo: PageAndSingleComponentDetails): string { return ` - query GetCdserviceintroBySlug($slug: String!) { + query GetCdIntroductionBySlug($slug: String!) { allPage(where: { slug: { current: { eq: $slug } } }) { components { __typename - ... on Cdserviceintro { + ... on Cdintroduction { __typename _key _type selectableVariant - heading title - content + richtextRaw sortOrder globalComponentSource { __typename _key _type - heading title - content + richtextRaw } } } } - allServicePage { + allHomepage { components { __typename - ... on Cdserviceintro { + ... on Cdintroduction { __typename _key _type selectableVariant - heading title - content + richtextRaw sortOrder globalComponentSource { __typename _key _type - heading title - content + richtextRaw } } } diff --git a/src/theme/conversion/components/cdserviceintro/sanity-schema.ts b/src/theme/conversion/components/cdintroduction/sanity-schema.ts similarity index 68% rename from src/theme/conversion/components/cdserviceintro/sanity-schema.ts rename to src/theme/conversion/components/cdintroduction/sanity-schema.ts index 2966ee9..6842609 100644 --- a/src/theme/conversion/components/cdserviceintro/sanity-schema.ts +++ b/src/theme/conversion/components/cdintroduction/sanity-schema.ts @@ -1,10 +1,10 @@ import { defineField, defineType } from 'sanity' import { DocumentTextIcon } from '@sanity/icons' -// Define a separate document type for cdserviceintro +// Define a separate document type for cdintroduction export default defineType({ - name: 'cdserviceintro', - title: 'Service Introduction (CD)', + name: 'cdintroduction', + title: 'Introduction (CD)', type: 'document', icon: DocumentTextIcon, fields: [ @@ -18,21 +18,15 @@ export default defineType({ ] } }), - defineField({ - name: 'heading', - title: 'Heading', - type: 'string', - description: 'The main heading for the service introduction section' - }), defineField({ name: 'title', title: 'Title', type: 'string', - description: 'The secondary title for the service introduction section' + description: 'The secondary title for the introduction section' }), defineField({ - name: 'content', - title: 'Content', + name: 'richtext', + title: 'Rich Text', type: 'array', of: [ { @@ -77,26 +71,24 @@ export default defineType({ } } ], - description: 'The rich text content for the service introduction' + description: 'The rich text content for the introduction' }), defineField({ name: 'sortOrder', title: 'Sort Order', type: 'number' - }) - // Temporarily removing the globalComponentSource field as it may be causing circular references - // defineField({ - // name: 'globalComponentSource', - // title: 'Global Component Source', - // type: 'reference', - // to: [{ type: 'cdserviceintro' }], - // description: 'Select a global re-usable service introduction.' - // }) + }), + defineField({ + name: 'globalComponentSource', + title: 'Global Component Source', + type: 'reference', + to: [{ type: 'cdintroduction' }], + description: 'Select a global re-usable introduction.' + }), ], preview: { select: { - title: 'heading', - subtitle: 'title' + title: 'title', } } }) \ No newline at end of file diff --git a/src/theme/conversion/components/cdserviceintro/view.tsx b/src/theme/conversion/components/cdintroduction/view.tsx similarity index 67% rename from src/theme/conversion/components/cdserviceintro/view.tsx rename to src/theme/conversion/components/cdintroduction/view.tsx index 5fc6404..13c3448 100644 --- a/src/theme/conversion/components/cdserviceintro/view.tsx +++ b/src/theme/conversion/components/cdintroduction/view.tsx @@ -2,10 +2,10 @@ import { ViewComponentProps } from "@conversiondigital/headless-basics-data/src"; import dynamic from "next/dynamic" -const CdserviceintroUI = dynamic(() => import("./components"), { +const CdIntroductionUI = dynamic(() => import("./components"), { loading: () => (

Loading...

) }); export async function View(dynamicComponent: ViewComponentProps) { - return + return } \ No newline at end of file diff --git a/src/theme/conversion/components/cdnav/sanity-schema.ts b/src/theme/conversion/components/cdnav/sanity-schema.ts index 38e554e..6381457 100644 --- a/src/theme/conversion/components/cdnav/sanity-schema.ts +++ b/src/theme/conversion/components/cdnav/sanity-schema.ts @@ -1,6 +1,5 @@ import { defineField, defineType } from 'sanity' import { EyeOpenIcon } from '@sanity/icons' -import { linkItem } from '@conversiondigital/headless-basics-data/src/cms/sanity/sanityCommonSchema' // Define the dropdown menu type export const dropdownMenu = defineType({ @@ -22,6 +21,26 @@ export const dropdownMenu = defineType({ ] }) +export const linkItem = defineType({ + name: 'linkItem', + title: 'Link Item', + type: 'object', + fields: [ + defineField({ + name: 'label', + title: 'Label', + type: 'string', + validation: Rule => Rule.required() + }), + defineField({ + name: 'url', + title: 'URL', + type: 'url', + validation: Rule => Rule.required() + }) + ] +}) + export default defineType({ name: 'cdnav', title: 'Navigation (CD)', diff --git a/src/theme/conversion/components/cdserviceintro/classification.ts b/src/theme/conversion/components/cdserviceintro/classification.ts deleted file mode 100644 index 2d7f277..0000000 --- a/src/theme/conversion/components/cdserviceintro/classification.ts +++ /dev/null @@ -1 +0,0 @@ -export default ["cdserviceintro", "serviceintro", "service-intro"] \ No newline at end of file diff --git a/src/theme/conversion/components/cdserviceintro/components/variants/cdserviceintroDefaultVariant.tsx b/src/theme/conversion/components/cdserviceintro/components/variants/cdserviceintroDefaultVariant.tsx deleted file mode 100644 index 8139c8c..0000000 --- a/src/theme/conversion/components/cdserviceintro/components/variants/cdserviceintroDefaultVariant.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; - -const CdserviceintroDefaultVariant: React.FC = ({ matchingData }) => { - const heading = matchingData?.heading || ''; - const title = matchingData?.title || ''; - const content = matchingData?.content || ''; - - return ( -
-
-
-

{heading}

-
-
-

{title}

-
-
-
-
- ); -}; - -export default CdserviceintroDefaultVariant; \ No newline at end of file diff --git a/src/theme/conversion/components/cdserviceofferings/classification.ts b/src/theme/conversion/components/cdserviceofferings/classification.ts deleted file mode 100644 index d1209af..0000000 --- a/src/theme/conversion/components/cdserviceofferings/classification.ts +++ /dev/null @@ -1 +0,0 @@ -export default ["cdserviceofferings", "serviceofferings", "cdserviceofferings"] diff --git a/src/theme/conversion/components/cdserviceofferings/components/variants/cdserviceofferingsDefaultVariant.tsx b/src/theme/conversion/components/cdserviceofferings/components/variants/cdserviceofferingsDefaultVariant.tsx deleted file mode 100644 index 5d70cac..0000000 --- a/src/theme/conversion/components/cdserviceofferings/components/variants/cdserviceofferingsDefaultVariant.tsx +++ /dev/null @@ -1,102 +0,0 @@ -"use client" -import React, { useState, useEffect } from 'react'; -import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; - -interface Offering { - title: string; - icon: string; - id: { - current: string; - }; -} - -interface Service { - id: string; - title: string; - content: string; -} - -const CdserviceofferingsDefaultVariant: React.FC = ({ matchingData }) => { - const title = matchingData?.title || ''; - const intro = matchingData?.intro || ''; - const offerings = matchingData?.offerings || []; - const services = matchingData?.services || []; - - const [activeServiceId, setActiveServiceId] = useState(undefined); - const [isVisible, setIsVisible] = useState(true); - - const getIdValue = (offering: Offering) => offering.id?.current || ''; - - const currentService = services.find((service: Service) => service.id === activeServiceId); - - useEffect(() => { - if (!activeServiceId && offerings.length > 0) { - setActiveServiceId(getIdValue(offerings[0]) || '1'); - } - }, [offerings, activeServiceId]); - - const handleServiceClick = (serviceId: string, index: number) => { - if (serviceId === activeServiceId) return; - - setIsVisible(false); - - setTimeout(() => { - setActiveServiceId(serviceId || index.toString()); - setIsVisible(true); - }, 300); - }; - - return ( -
-
-
-
-

{title}

-

{intro}

-
-
-
- {offerings.map((offering: Offering, index: number) => { - const id = getIdValue(offering); - return ( -
handleServiceClick(id, index + 1)} - role="button" - tabIndex={0} - aria-pressed={id === activeServiceId} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - handleServiceClick(id, index + 1); - } - }} - > -

{offering.title}

- - - -
- ); - })} -
-
-
-
-
-
-
-
-
-
-
-
- ); -}; - -export default CdserviceofferingsDefaultVariant; \ No newline at end of file diff --git a/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts b/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts deleted file mode 100644 index 79b8416..0000000 --- a/src/theme/conversion/components/cdserviceofferings/sanity-schema.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { defineField, defineType } from 'sanity' -import { EyeOpenIcon } from '@sanity/icons' - -// Define a separate type for the offering object -export const cdserviceOfferingItem = defineType({ - name: 'cdserviceOfferingItem', - title: 'Service Offering Item', - type: 'object', - fields: [ - defineField({ - name: 'title', - title: 'Title', - type: 'string', - description: 'The name of the service offering' - }), - defineField({ - name: 'icon', - title: 'Icon', - type: 'string', - description: 'The icon name to display for this offering' - }), - defineField({ - name: 'id', - title: 'ID', - type: 'slug', - description: 'A unique identifier for this service offering', - }) - ] -}) - -// Define a separate type for the service detail object -export const cdserviceDetailItem = defineType({ - name: 'cdserviceDetailItem', - title: 'Service Detail Item', - type: 'object', - fields: [ - defineField({ - name: 'id', - title: 'ID', - type: 'string', - description: 'A unique identifier for this service detail (should match an offering ID)' - }), - defineField({ - name: 'title', - title: 'Title', - type: 'string', - description: 'The title of the service detail' - }), - defineField({ - name: 'content', - title: 'Content', - type: 'array', - of: [{ type: 'block' }], - description: 'The rich text content for this service detail' - }) - ] -}) - -export default defineType({ - name: 'cdserviceofferings', - title: 'Service Offerings (CD)', - type: 'object', - icon: EyeOpenIcon, - fields: [ - defineField({ - name: 'selectableVariant', - title: 'Selectable Variant', - type: 'string', - options: { - list: [ - { title: 'Default', value: 'default' } - ] - } - }), - defineField({ - name: 'title', - title: 'Title', - type: 'string', - description: 'The main title for the service offerings section (e.g., "What We Offer")' - }), - defineField({ - name: 'intro', - title: 'Introduction Text', - type: 'text', - description: 'A brief introduction to the service offerings' - }), - defineField({ - name: 'offerings', - title: 'Service Offerings', - type: 'array', - of: [ - { type: 'cdserviceOfferingItem' } - ], - }), - defineField({ - name: 'services', - title: 'Service Details', - type: 'array', - of: [ - { type: 'cdserviceDetailItem' } - ], - description: 'The detailed content for each service offering' - }), - defineField({ - name: 'sortOrder', - title: 'Sort Order', - type: 'number' - }), - defineField({ - name: 'globalComponentSource', - title: 'Global Component Source', - type: 'reference', - to: [{ type: 'cdserviceofferings' }], - description: 'Select a global re-usable service offerings component.' - }) - ], - preview: { - select: { - title: 'title' - } - } -}) \ No newline at end of file diff --git a/src/theme/conversion/components/cdservicestats/classification.ts b/src/theme/conversion/components/cdservicestats/classification.ts deleted file mode 100644 index f1f4872..0000000 --- a/src/theme/conversion/components/cdservicestats/classification.ts +++ /dev/null @@ -1 +0,0 @@ -export default ["cdservicestats", "servicestats", "cdservicestats"] diff --git a/src/theme/conversion/components/cdstatistics/classification.ts b/src/theme/conversion/components/cdstatistics/classification.ts new file mode 100644 index 0000000..e4e1dc7 --- /dev/null +++ b/src/theme/conversion/components/cdstatistics/classification.ts @@ -0,0 +1 @@ +export default ["cdstatistics", "statistics"] diff --git a/src/theme/conversion/components/cdserviceofferings/components/index.tsx b/src/theme/conversion/components/cdstatistics/components/index.tsx similarity index 52% rename from src/theme/conversion/components/cdserviceofferings/components/index.tsx rename to src/theme/conversion/components/cdstatistics/components/index.tsx index 71b0175..7a93e95 100644 --- a/src/theme/conversion/components/cdserviceofferings/components/index.tsx +++ b/src/theme/conversion/components/cdstatistics/components/index.tsx @@ -2,19 +2,19 @@ import React from 'react'; import { componentBoilerPlate } from "@conversiondigital/headless-basics-data/src/component-tools/componentBoilerPlate"; import { ViewComponentProps } from "@conversiondigital/headless-basics-data/src/interfaces"; import { getLogger, logPrefix } from "@conversiondigital/headless-basics-data/src"; -import CdserviceofferingsDefaultVariant from './variants/cdserviceofferingsDefaultVariant'; +import CdStatisticsDefaultVariant from './variants/CdStatisticsDefaultVariant'; -export const log = getLogger("conversion.components.cdserviceofferings"); +export const log = getLogger("conversion.components.cdstatistics"); -export default function CdserviceofferingsUI(dynamicComponent: ViewComponentProps) { +export default function CdStatisticsUI(dynamicComponent: ViewComponentProps) { const { variant, matchingData } = componentBoilerPlate(dynamicComponent); if (!matchingData) return null; - log.trace(`${logPrefix()} cdserviceofferingsUI started, matchingData: ${JSON.stringify(matchingData)}`); + log.trace(`${logPrefix()} CdStatisticsUI started, matchingData: ${JSON.stringify(matchingData)}`); switch (variant) { case 'default': default: - return ; + return ; } } \ No newline at end of file diff --git a/src/theme/conversion/components/cdservicestats/components/variants/cdservicestatsDefaultVariant.tsx b/src/theme/conversion/components/cdstatistics/components/variants/CdStatisticsDefaultVariant.tsx similarity index 72% rename from src/theme/conversion/components/cdservicestats/components/variants/cdservicestatsDefaultVariant.tsx rename to src/theme/conversion/components/cdstatistics/components/variants/CdStatisticsDefaultVariant.tsx index 47fa4a5..d041cd6 100644 --- a/src/theme/conversion/components/cdservicestats/components/variants/cdservicestatsDefaultVariant.tsx +++ b/src/theme/conversion/components/cdstatistics/components/variants/CdStatisticsDefaultVariant.tsx @@ -6,17 +6,17 @@ interface Stat { description: string; } -const CdservicestatsDefaultVariant: React.FC = ({ matchingData }) => { +const CdStatisticsDefaultVariant: React.FC = ({ matchingData }) => { const stats = matchingData?.stats || []; return ( -
+
{stats.map((stat: Stat, index: number) => (
-
{stat.value}
-
{stat.description}
+
{stat.value}
+
{stat.description}
))}
@@ -25,4 +25,4 @@ const CdservicestatsDefaultVariant: React.FC = ({ matchi ); }; -export default CdservicestatsDefaultVariant; \ No newline at end of file +export default CdStatisticsDefaultVariant; \ No newline at end of file diff --git a/src/theme/conversion/components/cdserviceofferings/index.ts b/src/theme/conversion/components/cdstatistics/index.ts similarity index 74% rename from src/theme/conversion/components/cdserviceofferings/index.ts rename to src/theme/conversion/components/cdstatistics/index.ts index 53eb50b..e116827 100644 --- a/src/theme/conversion/components/cdserviceofferings/index.ts +++ b/src/theme/conversion/components/cdstatistics/index.ts @@ -2,10 +2,10 @@ import { getLogger, getThemeConfig } from "@conversiondigital/headless-basics-da import { View } from "./view"; import { ThemeConfig } from "@conversiondigital/headless-basics-data/src/interfaces" -getLogger("theme.components.cdserviceofferings") +getLogger("theme.components.cdstatistics") async function getConfig(): Promise { - const config = await getThemeConfig('cdserviceofferings'); + const config = await getThemeConfig('cdstatistics'); config.view = View; return config; } diff --git a/src/theme/conversion/components/cdservicestats/sanity-mapping.ts b/src/theme/conversion/components/cdstatistics/sanity-mapping.ts similarity index 78% rename from src/theme/conversion/components/cdservicestats/sanity-mapping.ts rename to src/theme/conversion/components/cdstatistics/sanity-mapping.ts index 34a4002..4f87e81 100644 --- a/src/theme/conversion/components/cdservicestats/sanity-mapping.ts +++ b/src/theme/conversion/components/cdstatistics/sanity-mapping.ts @@ -1,7 +1,7 @@ import { getLogger, logPrefix, PageAndSingleComponentDetails } from "@conversiondigital/headless-basics-data/src" import { extractComponentsFromSanityData } from "@conversiondigital/headless-basics-data/src/cms/sanity/sanityMappingUtils" -export const log = getLogger("conversion.components.sanity.cdservicestats.mapping") +export const log = getLogger("conversion.components.sanity.cdstatistics.mapping") export async function mapIdentifierData(pageAndComponentCombo: PageAndSingleComponentDetails) { log.trace( @@ -9,8 +9,8 @@ export async function mapIdentifierData(pageAndComponentCombo: PageAndSingleComp ); const content = pageAndComponentCombo?.component?.data - // The second arg 'cdservicestats' must match the name in cdservicestats's sanity-schema - const matchingData = extractComponentsFromSanityData(content, "cdservicestats", log); + // The second arg 'cdstatistics' must match the name in cdstatistics's sanity-schema + const matchingData = extractComponentsFromSanityData(content, "cdstatistics", log); return matchingData } diff --git a/src/theme/conversion/components/cdservicestats/sanity-query.ts b/src/theme/conversion/components/cdstatistics/sanity-query.ts similarity index 89% rename from src/theme/conversion/components/cdservicestats/sanity-query.ts rename to src/theme/conversion/components/cdstatistics/sanity-query.ts index 8e2501c..2ecb493 100644 --- a/src/theme/conversion/components/cdservicestats/sanity-query.ts +++ b/src/theme/conversion/components/cdstatistics/sanity-query.ts @@ -2,11 +2,11 @@ import { PageAndSingleComponentDetails } from "@conversiondigital/headless-basic export function query(pageAndComponentCombo: PageAndSingleComponentDetails): string { return ` - query GetCdservicestatsBySlug($slug: String!) { + query GetCdStatisticsBySlug($slug: String!) { allPage(where: { slug: { current: { eq: $slug } } }) { components { __typename - ... on Cdservicestats { + ... on Cdstatistics { __typename _key _type @@ -28,10 +28,10 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str } } } - allServicePage { + allHomepage { components { __typename - ... on Cdservicestats { + ... on Cdstatistics { __typename _key _type diff --git a/src/theme/conversion/components/cdservicestats/sanity-schema.ts b/src/theme/conversion/components/cdstatistics/sanity-schema.ts similarity index 70% rename from src/theme/conversion/components/cdservicestats/sanity-schema.ts rename to src/theme/conversion/components/cdstatistics/sanity-schema.ts index 0751039..b20816e 100644 --- a/src/theme/conversion/components/cdservicestats/sanity-schema.ts +++ b/src/theme/conversion/components/cdstatistics/sanity-schema.ts @@ -2,9 +2,9 @@ import { defineField, defineType } from 'sanity' import { BarChartIcon } from '@sanity/icons' // Define a separate type for the statistic object -export const cdserviceStatItem = defineType({ - name: 'cdserviceStatItem', - title: 'Service Statistic Item', +export const cdStatisticItem = defineType({ + name: 'cdStatisticItem', + title: 'Statistic Item', type: 'object', fields: [ defineField({ @@ -19,12 +19,18 @@ export const cdserviceStatItem = defineType({ type: 'string', description: 'The description of what the statistic represents' }) - ] + ], + preview: { + select: { + title: 'value', + subtitle: 'description' + } + } }) export default defineType({ - name: 'cdservicestats', - title: 'Service Statistics (CD)', + name: 'cdstatistics', + title: 'Statistics (CD)', type: 'object', icon: BarChartIcon, fields: [ @@ -38,12 +44,18 @@ export default defineType({ ] } }), + defineField({ + name: 'title', + title: 'Title', + type: 'string', + description: 'The title of the statistics component' + }), defineField({ name: 'stats', title: 'Statistics', type: 'array', of: [ - { type: 'cdserviceStatItem' } + { type: 'cdStatisticItem' } ], }), defineField({ @@ -55,8 +67,13 @@ export default defineType({ name: 'globalComponentSource', title: 'Global Component Source', type: 'reference', - to: [{ type: 'cdservicestats' }], + to: [{ type: 'cdstatistics' }], description: 'Select a global re-usable service statistics component.' }) ], + preview: { + select: { + title: 'title', + } + } }) \ No newline at end of file diff --git a/src/theme/conversion/components/cdserviceofferings/view.tsx b/src/theme/conversion/components/cdstatistics/view.tsx similarity index 65% rename from src/theme/conversion/components/cdserviceofferings/view.tsx rename to src/theme/conversion/components/cdstatistics/view.tsx index 7fcc2c2..fa048e0 100644 --- a/src/theme/conversion/components/cdserviceofferings/view.tsx +++ b/src/theme/conversion/components/cdstatistics/view.tsx @@ -2,10 +2,10 @@ import { ViewComponentProps } from "@conversiondigital/headless-basics-data/src"; import dynamic from "next/dynamic" -const CdserviceofferingsUI = dynamic(() => import("./components"), { +const CdStatisticsUI = dynamic(() => import("./components"), { loading: () => (

Loading...

) }); export async function View(dynamicComponent: ViewComponentProps) { - return + return } diff --git a/src/theme/conversion/index.ts b/src/theme/conversion/index.ts index 98908ef..7ec25c3 100644 --- a/src/theme/conversion/index.ts +++ b/src/theme/conversion/index.ts @@ -14,6 +14,6 @@ export { default as cdclients } from "./components/cdclients/index"; // Insert cdcta export { default as cdcta } from "./components/cdcta/index"; // New service page components -export { default as cdserviceintro } from "./components/cdserviceintro/index"; -export { default as cdservicestats } from "./components/cdservicestats/index"; -export { default as cdserviceofferings } from "./components/cdserviceofferings/index"; \ No newline at end of file +export { default as cdintroduction } from "./components/cdintroduction/index"; +export { default as cdstatistics } from "./components/cdstatistics/index"; +export { default as cdfaqs } from "./components/cdfaqs/index"; \ No newline at end of file diff --git a/src/theme/conversion/structures/servicepage/sanity-mapping.ts b/src/theme/conversion/structures/servicepage/sanity-mapping.ts deleted file mode 100644 index ad50988..0000000 --- a/src/theme/conversion/structures/servicepage/sanity-mapping.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { getLogger, logPrefix, PageAndSingleComponentDetails } from "@conversiondigital/headless-basics-data/src"; -import { extractComponentsFromSanityData } from "@conversiondigital/headless-basics-data/src/cms/sanity/sanityMappingUtils"; - -export const log = getLogger("conversion.structures.sanity.servicepage.mapping"); - -export async function mapIdentifierData(pageAndComponentCombo: PageAndSingleComponentDetails) { - log.trace( - `${logPrefix()}[${pageAndComponentCombo.component.identifier}][${pageAndComponentCombo.page.source}][${pageAndComponentCombo.page.preliminarySlug}] mapIdentifierData started` - ); - - const content = pageAndComponentCombo?.component?.data; - const servicePage = content?.allServicePage && Array.isArray(content.allServicePage) ? content.allServicePage[0] : null; - - if (!servicePage) { - log.warn(`${logPrefix()} No ServicePage data found`); - return null; - } - - // Find the components by type - const serviceIntroComponent = servicePage.components?.find((comp: any) => comp.__typename === 'Cdserviceintro'); - const serviceStatsComponent = servicePage.components?.find((comp: any) => comp.__typename === 'Cdservicestats'); - const serviceOfferingsComponent = servicePage.components?.find((comp: any) => comp.__typename === 'Cdserviceofferings'); - const serviceDetailComponent = servicePage.components?.find((comp: any) => comp.__typename === 'Cdservicedetail'); - - // Process the service intro component - let serviceIntroData = null; - if (serviceIntroComponent) { - const hasGlobal = serviceIntroComponent.globalComponentSource; - serviceIntroData = { - title: serviceIntroComponent.title || (hasGlobal?.title || ''), - content: serviceIntroComponent.content || (hasGlobal?.content || ''), - selectableVariant: serviceIntroComponent.selectableVariant || (hasGlobal?.selectableVariant || 'default') - }; - } - - // Process the service stats component - let serviceStatsData = null; - if (serviceStatsComponent) { - const hasGlobal = serviceStatsComponent.globalComponentSource; - serviceStatsData = { - stats: serviceStatsComponent.stats && serviceStatsComponent.stats.length > 0 - ? serviceStatsComponent.stats - : (hasGlobal?.stats || []), - selectableVariant: serviceStatsComponent.selectableVariant || (hasGlobal?.selectableVariant || 'default') - }; - } - - // Process the service offerings component - let serviceOfferingsData = null; - if (serviceOfferingsComponent) { - const hasGlobal = serviceOfferingsComponent.globalComponentSource; - serviceOfferingsData = { - title: serviceOfferingsComponent.title || (hasGlobal?.title || ''), - intro: serviceOfferingsComponent.intro || (hasGlobal?.intro || ''), - offerings: serviceOfferingsComponent.offerings && serviceOfferingsComponent.offerings.length > 0 - ? serviceOfferingsComponent.offerings.map((offering: any) => ({ - title: offering.title, - icon: offering.icon, - id: offering.id?.current - })) - : (hasGlobal?.offerings?.map((offering: any) => ({ - title: offering.title, - icon: offering.icon, - id: offering.id?.current - })) || []), - selectableVariant: serviceOfferingsComponent.selectableVariant || (hasGlobal?.selectableVariant || 'default') - }; - } - - // Process the service detail component - let serviceDetailData = null; - if (serviceDetailComponent) { - const hasGlobal = serviceDetailComponent.globalComponentSource; - serviceDetailData = { - services: serviceDetailComponent.services && serviceDetailComponent.services.length > 0 - ? serviceDetailComponent.services.map((service: any) => ({ - title: service.title, - id: service.id?.current, - content: service.content - })) - : (hasGlobal?.services?.map((service: any) => ({ - title: service.title, - id: service.id?.current, - content: service.content - })) || []), - selectableVariant: serviceDetailComponent.selectableVariant || (hasGlobal?.selectableVariant || 'default') - }; - } - - return { - serviceIntroData, - serviceStatsData, - serviceOfferingsData, - serviceDetailData, - defaultActiveServiceId: serviceOfferingsData?.offerings?.[0]?.id - }; -} \ No newline at end of file diff --git a/src/theme/conversion/structures/servicepage/sanity-query.ts b/src/theme/conversion/structures/servicepage/sanity-query.ts deleted file mode 100644 index 8631c23..0000000 --- a/src/theme/conversion/structures/servicepage/sanity-query.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { getLogger, logPrefix, PageAndSingleComponentDetails } from "@conversiondigital/headless-basics-data/src"; - -export const log = getLogger("conversion.structures.sanity.servicepage.query"); - -export function query(pageAndComponentCombo: PageAndSingleComponentDetails) { - log.info(`${logPrefix()}[servicepage][sanity-query][query] called for slug: ${pageAndComponentCombo?.page?.preliminarySlug}`); - - return ` - query GetServicePageBySlug($slug: String!) { - allServicePage(where: { slug: { current: { eq: $slug } } }) { - title - slug { - current - } - components { - __typename - ... on Cdserviceintro { - __typename - _key - _type - selectableVariant - title - content - sortOrder - globalComponentSource { - __typename - _key - _type - title - content - } - } - ... on Cdservicestats { - __typename - _key - _type - selectableVariant - stats { - value - description - } - sortOrder - globalComponentSource { - __typename - _key - _type - stats { - value - description - } - } - } - ... on Cdserviceofferings { - __typename - _key - _type - selectableVariant - title - intro - offerings { - title - icon - id { - current - } - } - sortOrder - globalComponentSource { - __typename - _key - _type - title - intro - offerings { - title - icon - id { - current - } - } - } - } - ... on Cdservicedetail { - __typename - _key - _type - selectableVariant - services { - title - id { - current - } - content - } - sortOrder - globalComponentSource { - __typename - _key - _type - services { - title - id { - current - } - content - } - } - } - } - } - } - `; -} - -export function getQuery() { - return query; -} \ No newline at end of file diff --git a/src/theme/conversion/styles/globals.css b/src/theme/conversion/styles/globals.css index f957ed0..cbf4625 100644 --- a/src/theme/conversion/styles/globals.css +++ b/src/theme/conversion/styles/globals.css @@ -35,6 +35,10 @@ background-color: var(--off-white); } +@theme { + --color-primary-bg: #0d0e47; +} + :root { --color-primary-bg: #0D0E47; --color-primary-text: white; @@ -71,6 +75,7 @@ --border-gray-700: #374151; --br-white: #DEDEDE; --border-purple: #6C2BD9; + --body-color: #0d0e47; } body { @@ -329,20 +334,20 @@ nav { } -.contentServiceIntroWrapper { +.contentIntroductionWrapper { color: #0d0e47; } -.contentServiceIntroWrapper p{ +.contentIntroductionWrapper p{ margin-top: 30px; color: #0d0e47; font-family: var(--font-figtree); } -.contentServiceIntroWrapper p:first-child, .contentServiceIntroWrapper h3:first-child{ +.contentIntroductionWrapper p:first-child, .contentIntroductionWrapper h3:first-child{ margin-top: 0; } -.contentServiceIntroWrapper h3{ +.contentIntroductionWrapper h3{ margin-top: 53px; margin-bottom: 22px; font-size: 30px; @@ -351,7 +356,7 @@ nav { color: #0d0e47; } -.contentServiceOfferingsWrapper { +.contentFaqsWrapper { color: white; font-family: var(--font-figtree); font-size: 18px; @@ -359,7 +364,7 @@ nav { font-weight: 500; } -.contentServiceOfferingsWrapper p{ +.contentFaqsWrapper p{ margin-top: 30px; color: white; font-family: var(--font-figtree); @@ -368,7 +373,7 @@ nav { font-weight: 500; } -.contentServiceOfferingsWrapper ul { +.contentFaqsWrapper ul { list-style: initial; font-size: 18px; line-height: 36px; @@ -376,11 +381,11 @@ nav { margin-left: 30px; } -.contentServiceOfferingsWrapper p:first-child, .contentServiceOfferingsWrapper h3:first-child{ +.contentFaqsWrapper p:first-child, .contentFaqsWrapper h3:first-child{ margin-top: 0; } -.contentServiceOfferingsWrapper h3, .contentServiceOfferingsWrapper h2{ +.contentFaqsWrapper h3, .contentFaqsWrapper h2{ margin-top: 53px; margin-bottom: 22px; font-size: 30px; diff --git a/src/theme/conversion/tailwind.config.js b/src/theme/conversion/tailwind.config.js index f5efe47..108e942 100644 --- a/src/theme/conversion/tailwind.config.js +++ b/src/theme/conversion/tailwind.config.js @@ -71,7 +71,8 @@ module.exports = { DEFAULT: "#FACF41", light: "#FFE799", }, - "body-color": "#0d0e47", + "body-color": "var(--body-color)", + "primary-bg": "var(--color-primary-bg)", }, backgroundImage: { "gradient-dark": "linear-gradient(277deg, #171717 55.29%, #3D3D3D 98.19%)", From 77299ea0e4744a2836f2d913f7b0838e952f51fd Mon Sep 17 00:00:00 2001 From: Anh Pham Date: Sun, 6 Jul 2025 20:41:29 -0500 Subject: [PATCH 06/13] Fixed UI blockers --- .../variants/cdclientsDefaultVariant.tsx | 166 +++++++++++--- .../components/variants/demoVariant.tsx | 37 ++- .../components/cdclients/sanity-query.ts | 28 ++- .../components/cdclients/sanity-schema.ts | 13 +- .../variants/cdctaDefaultVariant.tsx | 26 ++- .../variants/cdinsightsDefaultVariant.tsx | 70 +++--- .../variants/cdmissionDefaultVariant.tsx | 51 +++-- .../variants/cdnavDefaultVariant.tsx | 213 +++++++++++++----- .../variants/cdpartnersDefaultVariant.tsx | 146 +++++++++--- .../components/variants/demoVariant.tsx | 43 +++- .../components/cdpartners/sanity-query.ts | 34 ++- .../components/cdpartners/sanity-schema.ts | 13 +- .../variants/cdservicesDefaultVariant.tsx | 24 +- .../variants/cdtestimonialsDefaultVariant.tsx | 64 +++--- .../components/variants/defaultVariant.tsx | 16 +- 15 files changed, 671 insertions(+), 273 deletions(-) diff --git a/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx b/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx index c3b144e..bbb7447 100644 --- a/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx +++ b/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx @@ -2,52 +2,146 @@ import React from "react"; import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; import { getCmsImage } from "@conversiondigital/headless-basics-data/src/cms/tools/multiCmsImageTools"; import { buttonIcon as ButtonIcon } from "../../../../styles/icons/icons"; +import Link from "next/link"; +interface ClientItem { + name: string; + link?: string; + inactiveLogo?: any; + activeLogo?: any; +} -export default function DefaultVariant(props: StandardComponentProps) { +interface CdclientsDefaultVariantProps extends StandardComponentProps { + matchingData: { + title?: string; + subtitle?: string; + clientsList?: ClientItem[]; + buttonText?: string; + buttonUrl?: string; + }; +} + +const CdclientsDefaultVariant: React.FC = (props) => { const { matchingData } = props; const title = matchingData?.title?.toUpperCase() || "OUR CLIENTS"; - const clients = matchingData?.clientsList || []; - const buttonText = matchingData?.buttonText || "View All Clients"; - const buttonLink = matchingData?.buttonLink || "#"; + const subtitle = matchingData?.subtitle; + const clientsList = matchingData?.clientsList || []; + const buttonText = matchingData?.buttonText; + const buttonUrl = matchingData?.buttonUrl; return ( -
-
+
+
+ {/* Header */}
-

{title}

+ {title && ( +

+ {title} +

+ )} + {subtitle && ( +

+ {subtitle} +

+ )}
-
- {Array.isArray(clients) && - clients.map((client: any, index: number) => { - const logoUrl = client?.logo?.asset?.url; - return ( -
- {logoUrl && ( - {client?.name - )} -
- ); - })} -
- + + + {/* Clients Grid */} + {clientsList.length > 0 && ( +
+ {clientsList.map((client, index) => ( +
+ {client.link ? ( + +
+ {/* Inactive Logo */} + {client.inactiveLogo && ( + {`${client.name} + )} + + {/* Active Logo */} + {client.activeLogo && ( + {`${client.name} + )} + + {/* Fallback if only inactive logo exists */} + {client.inactiveLogo && !client.activeLogo && ( + {`${client.name} + )} +
+
+ ) : ( +
+ {/* Inactive Logo */} + {client.inactiveLogo && ( + {`${client.name} + )} + + {/* Active Logo */} + {client.activeLogo && ( + {`${client.name} + )} + + {/* Fallback if only inactive logo exists */} + {client.inactiveLogo && !client.activeLogo && ( + {`${client.name} + )} +
+ )} +
+ ))} +
+ )} + + {/* Button */} + {buttonText && buttonUrl && ( +
+ + {buttonText} + + +
+ )}
); }; + +export default CdclientsDefaultVariant; diff --git a/src/theme/conversion/components/cdclients/components/variants/demoVariant.tsx b/src/theme/conversion/components/cdclients/components/variants/demoVariant.tsx index 7d4a20b..f54e3fb 100644 --- a/src/theme/conversion/components/cdclients/components/variants/demoVariant.tsx +++ b/src/theme/conversion/components/cdclients/components/variants/demoVariant.tsx @@ -17,15 +17,38 @@ const DemoVariant: React.FC = ({ {clientsList.map((client: any, index: number) => ( diff --git a/src/theme/conversion/components/cdclients/sanity-query.ts b/src/theme/conversion/components/cdclients/sanity-query.ts index dd36d5f..329e360 100644 --- a/src/theme/conversion/components/cdclients/sanity-query.ts +++ b/src/theme/conversion/components/cdclients/sanity-query.ts @@ -17,7 +17,12 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str clientsList { name link - logo { + inactiveLogo { + asset { + url + } + } + activeLogo { asset { url } @@ -35,7 +40,12 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str clientsList { name link - logo { + inactiveLogo { + asset { + url + } + } + activeLogo { asset { url } @@ -59,7 +69,12 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str clientsList { name link - logo { + inactiveLogo { + asset { + url + } + } + activeLogo { asset { url } @@ -77,7 +92,12 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str clientsList { name link - logo { + inactiveLogo { + asset { + url + } + } + activeLogo { asset { url } diff --git a/src/theme/conversion/components/cdclients/sanity-schema.ts b/src/theme/conversion/components/cdclients/sanity-schema.ts index f0c0fc3..0f3eb48 100644 --- a/src/theme/conversion/components/cdclients/sanity-schema.ts +++ b/src/theme/conversion/components/cdclients/sanity-schema.ts @@ -18,9 +18,16 @@ export const clientItem = defineType({ type: 'url' }), defineField({ - name: 'logo', - title: 'Client Logo', - type: 'image' + name: 'inactiveLogo', + title: 'Inactive Logo', + type: 'image', + description: 'Logo shown by default (grayscale or muted version)' + }), + defineField({ + name: 'activeLogo', + title: 'Active Logo', + type: 'image', + description: 'Logo shown on hover (colored or highlighted version)' }) ] }); diff --git a/src/theme/conversion/components/cdcta/components/variants/cdctaDefaultVariant.tsx b/src/theme/conversion/components/cdcta/components/variants/cdctaDefaultVariant.tsx index 1b8d558..f743a72 100644 --- a/src/theme/conversion/components/cdcta/components/variants/cdctaDefaultVariant.tsx +++ b/src/theme/conversion/components/cdcta/components/variants/cdctaDefaultVariant.tsx @@ -13,14 +13,24 @@ const DefaultVariant: React.FC = ({ const buttonUrl = matchingData?.buttonUrl || "#"; return ( -
-

- {title} -

- - {buttonLabel} - - +
+
+

+ {title} +

+ {subtitle && ( +

+ {subtitle} +

+ )} + + {buttonLabel} + + +
); }; diff --git a/src/theme/conversion/components/cdinsights/components/variants/cdinsightsDefaultVariant.tsx b/src/theme/conversion/components/cdinsights/components/variants/cdinsightsDefaultVariant.tsx index 7f72ce9..8cd8100 100644 --- a/src/theme/conversion/components/cdinsights/components/variants/cdinsightsDefaultVariant.tsx +++ b/src/theme/conversion/components/cdinsights/components/variants/cdinsightsDefaultVariant.tsx @@ -11,25 +11,25 @@ export default function DefaultVariant(props: StandardComponentProps) { const buttonText = matchingData?.buttonText || "View all insights"; return ( -
+
-
-

+
+

{heading}

-

+

{tagline}

-
+
{Array.isArray(items) && items.map((item: any, index: number) => { const { hasImage, imageLocation } = getCmsImage(item); const url = hasImage ? imageLocation : item?.imageUrl; return ( -
+
{url && ( -
+
{item?.title )} -
- {item.topics && item.topics.length > 0 && ( -
- {item.topics.map((topic: any, i: number) => ( - - {i > 0 && ", "} - - {topic.name} - - - ))} -
- )} +
+ {item.topics && item.topics.length > 0 && ( +
+ {item.topics.map((topic: any, i: number) => ( + + {i > 0 && ", "} + + {topic.name} + + + ))} +
+ )} -

- {item?.title?.toUpperCase()} -

- {item.buttonText && item.buttonUrl && ( - - {item.buttonText} - - - )} -
+

+ {item?.title?.toUpperCase()} +

+ {item.buttonText && item.buttonUrl && ( + + {item.buttonText} + + + )} +
); })}
-
+
{buttonText} diff --git a/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx b/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx index e7ae22a..7c626f7 100644 --- a/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx +++ b/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx @@ -1,3 +1,4 @@ +'use client' import React from "react"; import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; import { getCmsImage } from "@conversiondigital/headless-basics-data/src/cms/tools/multiCmsImageTools"; @@ -15,26 +16,26 @@ export default function CdmissionDefaultVariant(props: StandardComponentProps) { const { hasImage, imageLocation, altText } = getCmsImage(matchingData); return ( -
-
+
+
{/* Left Column */} -
-

+
+

{title}

-

+

{description}

{/* Right Column - Icon List */} -
+
{keyPoints.map((point: any, i: number) => (
-
- +
+
-

{point.description}

+

{point.description}

))}
@@ -42,37 +43,39 @@ export default function CdmissionDefaultVariant(props: StandardComponentProps) { {/* Promises Section */}
-
-
-

{promiseTitle}

-
-
-
-
+ className="bg-white text-[#0D0E47] py-8 sm:py-12 md:py-16 lg:py-20 px-4 sm:px-6 md:px-8 lg:px-12"> +
+
+

{promiseTitle}

+
+
+
+
{promises[0]?.icon?.asset?.url && ( {promises[0].title} )} -
+
{promises[0]?.title}
{hasImage && ( -
+
{altText
)} -

- {promiseSubtitle} -

+
+

+ {promiseSubtitle} +

+
diff --git a/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx b/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx index 8f23166..2d2b2da 100644 --- a/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx +++ b/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx @@ -1,4 +1,5 @@ -import React from "react"; +'use client' +import React, { useState } from "react"; import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; import { getCmsImage } from "@conversiondigital/headless-basics-data/src/cms/tools/multiCmsImageTools"; @@ -15,79 +16,177 @@ export default function cdnavDefaultVariant(props: StandardComponentProps) { const { hasImage: hasMobile, imageLocation: mobileSrc } = getCmsImage({ image: matchingData?.mobileImage }); const { hasImage: hasDesktop, imageLocation: desktopSrc } = getCmsImage({ image: matchingData?.desktopImage }); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [openDropdowns, setOpenDropdowns] = useState([]); + + const toggleDropdown = (index: number) => { + setOpenDropdowns(prev => + prev.includes(index) + ? prev.filter(i => i !== index) + : [...prev, index] + ); + }; + return (
diff --git a/src/theme/conversion/components/cdpartners/components/variants/cdpartnersDefaultVariant.tsx b/src/theme/conversion/components/cdpartners/components/variants/cdpartnersDefaultVariant.tsx index 3dbb38c..24f0486 100644 --- a/src/theme/conversion/components/cdpartners/components/variants/cdpartnersDefaultVariant.tsx +++ b/src/theme/conversion/components/cdpartners/components/variants/cdpartnersDefaultVariant.tsx @@ -1,41 +1,127 @@ import React from "react"; import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; -import { getCmsImage } from "@conversiondigital/headless-basics-data/src/cms/tools/multiCmsImageTools"; -export default function DefaultVariant(props: StandardComponentProps) { const { matchingData } = props; - // Basic fields - const title = matchingData?.title?.toUpperCase() || "OUR PARTNERS"; - const subtitle = matchingData?.subtitle || "We work with leading technology providers."; - const partners = matchingData?.partnerLogos || []; +interface PartnerItem { + name: string; + url?: string; + inactiveLogo?: any; + activeLogo?: any; +} + +interface CdpartnersDefaultVariantProps extends StandardComponentProps { + matchingData: { + title?: string; + subtitle?: string; + partnerLogos?: PartnerItem[]; + }; +} + +const CdpartnersDefaultVariant: React.FC = (props) => { + const { matchingData } = props; + const title = matchingData?.title || "OUR PARTNERS"; + const subtitle = matchingData?.subtitle; + const partnerLogos = matchingData?.partnerLogos || []; return ( -
-
+
+
+ {/* Header */}
-

{title}

+ {title && ( +

+ {title} +

+ )} + {subtitle && ( +

+ {subtitle} +

+ )}
-
- {Array.isArray(partners) && - partners.map((partner: any, index: number) => { - const logoUrl = partner?.logo?.asset?.url; - return ( -
- {logoUrl && ( - {partner?.name - )} -
- ); - })} -
+ + {/* Partners Grid */} + {partnerLogos.length > 0 && ( +
+ {partnerLogos.map((partner: PartnerItem, index: number) => ( +
+ {partner.url ? ( + +
+ {/* Inactive Logo */} + {partner.inactiveLogo && ( + {`${partner.name} + )} + + {/* Active Logo */} + {partner.activeLogo && ( + {`${partner.name} + )} + + {/* Fallback if only inactive logo exists */} + {partner.inactiveLogo && !partner.activeLogo && ( + {`${partner.name} + )} +
+
+ ) : ( +
+ {/* Inactive Logo */} + {partner.inactiveLogo && ( + {`${partner.name} + )} + + {/* Active Logo */} + {partner.activeLogo && ( + {`${partner.name} + )} + + {/* Fallback if only inactive logo exists */} + {partner.inactiveLogo && !partner.activeLogo && ( + {`${partner.name} + )} +
+ )} +
+ ))} +
+ )} +
); -} \ No newline at end of file +}; + +export default CdpartnersDefaultVariant; \ No newline at end of file diff --git a/src/theme/conversion/components/cdpartners/components/variants/demoVariant.tsx b/src/theme/conversion/components/cdpartners/components/variants/demoVariant.tsx index b934003..c434a86 100644 --- a/src/theme/conversion/components/cdpartners/components/variants/demoVariant.tsx +++ b/src/theme/conversion/components/cdpartners/components/variants/demoVariant.tsx @@ -5,22 +5,45 @@ import { demoVariantData } from "./data/demoVariantData"; const DemoVariant: React.FC = ({ matchingData }) => { const title = matchingData?.title || demoVariantData.title; const subtitle = matchingData?.subtitle || demoVariantData.subtitle; - const logos = matchingData?.partnerLogos || demoVariantData.partnerLogos; + const partnerLogos = matchingData?.partnerLogos || demoVariantData.partnerLogos; return (

{title}

{subtitle}

- {logos.map((partner: any, index: number) => ( -
- {partner.imageUrl && ( - - {partner.partnerName + {partnerLogos.map((partner: any, index: number) => ( + diff --git a/src/theme/conversion/components/cdpartners/sanity-query.ts b/src/theme/conversion/components/cdpartners/sanity-query.ts index e023cc0..502bfa5 100644 --- a/src/theme/conversion/components/cdpartners/sanity-query.ts +++ b/src/theme/conversion/components/cdpartners/sanity-query.ts @@ -15,7 +15,13 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str subtitle partnerLogos { name - logo { + url + inactiveLogo { + asset { + url + } + } + activeLogo { asset { url } @@ -26,11 +32,18 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str __typename _key _type + selectableVariant title subtitle partnerLogos { name - logo { + url + inactiveLogo { + asset { + url + } + } + activeLogo { asset { url } @@ -52,7 +65,13 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str subtitle partnerLogos { name - logo { + url + inactiveLogo { + asset { + url + } + } + activeLogo { asset { url } @@ -63,11 +82,18 @@ export function query(pageAndComponentCombo: PageAndSingleComponentDetails): str __typename _key _type + selectableVariant title subtitle partnerLogos { name - logo { + url + inactiveLogo { + asset { + url + } + } + activeLogo { asset { url } diff --git a/src/theme/conversion/components/cdpartners/sanity-schema.ts b/src/theme/conversion/components/cdpartners/sanity-schema.ts index c424f1f..a576aef 100644 --- a/src/theme/conversion/components/cdpartners/sanity-schema.ts +++ b/src/theme/conversion/components/cdpartners/sanity-schema.ts @@ -17,9 +17,16 @@ export const partnerItem = defineType({ type: 'url' }), defineField({ - name: 'logo', - title: 'Partner Logo', - type: 'image' + name: 'inactiveLogo', + title: 'Inactive Logo', + type: 'image', + description: 'Logo shown by default (grayscale or muted version)' + }), + defineField({ + name: 'activeLogo', + title: 'Active Logo', + type: 'image', + description: 'Logo shown on hover (colored or highlighted version)' }) ] }) diff --git a/src/theme/conversion/components/cdservices/components/variants/cdservicesDefaultVariant.tsx b/src/theme/conversion/components/cdservices/components/variants/cdservicesDefaultVariant.tsx index fbe922e..ce70ad1 100644 --- a/src/theme/conversion/components/cdservices/components/variants/cdservicesDefaultVariant.tsx +++ b/src/theme/conversion/components/cdservices/components/variants/cdservicesDefaultVariant.tsx @@ -13,38 +13,38 @@ export default function DefaultVariant(props: StandardComponentProps) { const subtitle = matchingData?.subtitle || "How we help businesses thrive"; return ( -
+
-
-

{title}

-
-

{subtitle}

+
+

{title}

+
+

{subtitle}

{/* Optional image */} {hasImage && ( -
+
{altText}
)} -
+
{servicesList.map((serviceItem: any, index: number) => ( -
+
{serviceItem?.image?.asset?.url && ( {serviceItem.title} )} -

{serviceItem.title?.toUpperCase()}

-

{serviceItem.description}

+

{serviceItem.title?.toUpperCase()}

+

{serviceItem.description}

))}
diff --git a/src/theme/conversion/components/cdtestimonials/components/variants/cdtestimonialsDefaultVariant.tsx b/src/theme/conversion/components/cdtestimonials/components/variants/cdtestimonialsDefaultVariant.tsx index d7b3578..17ebb70 100644 --- a/src/theme/conversion/components/cdtestimonials/components/variants/cdtestimonialsDefaultVariant.tsx +++ b/src/theme/conversion/components/cdtestimonials/components/variants/cdtestimonialsDefaultVariant.tsx @@ -20,28 +20,28 @@ export default function DefaultVariant(props: StandardComponentProps) { return (
-
-
-
-

{title}

-
+
+
+
+

{title}

+
-
+
{Array.isArray(testimonials) && testimonials.map((item: any, index: number) => { const imageUrl = item?.image?.asset?.url; return ( -
-
"
-
"
+
+
"
+
"
-

{item?.testimonial || "Client testimonial"}

+

{item?.testimonial || "Client testimonial"}

-
-

{item?.name || "Client Name"}

-

{item?.position || "Position"}

+
+

{item?.name || "Client Name"}

+

{item?.position || "Position"}

@@ -52,25 +52,25 @@ export default function DefaultVariant(props: StandardComponentProps) {
{videoTestimonials.length > 0 && ( -
-
-
-

{videoTitle}

-
-

{videoSubtitle}

+
+
+
+

{videoTitle}

+
+

{videoSubtitle}

{/* Spotlight Video */} {videoTestimonials[0] && ( -
-
-
-

{videoTestimonials[0].name?.toUpperCase()}

-

{videoTestimonials[0].position?.toUpperCase()}

-

{videoTestimonials[0].testimonial}

+
+
+
+

{videoTestimonials[0].name?.toUpperCase()}

+

{videoTestimonials[0].position?.toUpperCase()}

+

{videoTestimonials[0].testimonial}

-
+
1 && ( <> -

More Videos

-
+

More Videos

+
{videoTestimonials.slice(1).map((testimonial: VideoTestimonial, index: number) => ( -
+
{testimonial?.videoUrl && ( )} -
-

{testimonial?.name}

-

{testimonial?.position}

+
+

{testimonial?.name}

+

{testimonial?.position}

))} diff --git a/src/theme/conversion/components/herobanner/components/variants/defaultVariant.tsx b/src/theme/conversion/components/herobanner/components/variants/defaultVariant.tsx index 6529e86..f246d94 100644 --- a/src/theme/conversion/components/herobanner/components/variants/defaultVariant.tsx +++ b/src/theme/conversion/components/herobanner/components/variants/defaultVariant.tsx @@ -11,19 +11,19 @@ export default function DefaultVariant(props: StandardComponentProps) { const buttonLabel = matchingData?.button?.label const buttonLink = matchingData?.button?.link return ( -
-
-

+
+
+

{(matchingData?.title || "Default HeroBanner Title").toUpperCase()}

-

+

{matchingData?.subtitle || "Default HeroBanner Subtitle"}

{buttonLabel && buttonLink && ( -
+
{buttonLabel} @@ -32,11 +32,11 @@ export default function DefaultVariant(props: StandardComponentProps) { )}
{imageUrl && ( -
+
{altText}
)} From 1febb5996708d4ddcdf94e5347eb74cf5cab5139 Mon Sep 17 00:00:00 2001 From: Anh Pham Date: Sun, 6 Jul 2025 21:44:38 -0500 Subject: [PATCH 07/13] UI Fixes --- .../conversion/components/cdfooter/sanity-mapping.ts | 2 +- .../components/variants/cdnavDefaultVariant.tsx | 12 ++++++------ .../components/herobanner/sanity-mapping.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/theme/conversion/components/cdfooter/sanity-mapping.ts b/src/theme/conversion/components/cdfooter/sanity-mapping.ts index 522b1bd..64afa32 100644 --- a/src/theme/conversion/components/cdfooter/sanity-mapping.ts +++ b/src/theme/conversion/components/cdfooter/sanity-mapping.ts @@ -14,7 +14,7 @@ export async function mapIdentifierData(pageAndComponentCombo: PageAndSingleComp log.trace(`${logPrefix()} thisComponentsOrder: ${thisComponentsOrder}`); - const matchingData = extractComponentsFromSanityData(content, "cdfooter", log, true, '', thisComponentsOrder); + const matchingData = extractComponentsFromSanityData(content, "cdfooter", log); return matchingData } \ No newline at end of file diff --git a/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx b/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx index 2d2b2da..78b376e 100644 --- a/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx +++ b/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx @@ -28,7 +28,7 @@ export default function cdnavDefaultVariant(props: StandardComponentProps) { }; return ( -

); } diff --git a/src/theme/conversion/components/cdstatistics/components/index.tsx b/src/theme/conversion/components/cdstatistics/components/index.tsx index 7a93e95..93a160b 100644 --- a/src/theme/conversion/components/cdstatistics/components/index.tsx +++ b/src/theme/conversion/components/cdstatistics/components/index.tsx @@ -1,19 +1,45 @@ -import React from 'react'; +import React from "react"; import { componentBoilerPlate } from "@conversiondigital/headless-basics-data/src/component-tools/componentBoilerPlate"; import { ViewComponentProps } from "@conversiondigital/headless-basics-data/src/interfaces"; import { getLogger, logPrefix } from "@conversiondigital/headless-basics-data/src"; -import CdStatisticsDefaultVariant from './variants/CdStatisticsDefaultVariant'; +import CdStatisticsDefaultVariant from "./variants/CdStatisticsDefaultVariant"; export const log = getLogger("conversion.components.cdstatistics"); +// Interface for individual statistic item +export interface StatisticItem { + value: string; + description: string; +} + +// Interface for global component source +interface GlobalComponentSource { + __typename: string; + _key: string; + _type: string; + stats: StatisticItem[]; +} + +// Interface for CdStatistics component data +export interface CdStatisticsData { + __typename: string; + _key: string; + _type: string; + selectableVariant: string; + title?: string; + stats: StatisticItem[]; + sortOrder: number; + globalComponentSource?: GlobalComponentSource; +} + export default function CdStatisticsUI(dynamicComponent: ViewComponentProps) { - const { variant, matchingData } = componentBoilerPlate(dynamicComponent); + const { variant, matchingData } = componentBoilerPlate(dynamicComponent); if (!matchingData) return null; log.trace(`${logPrefix()} CdStatisticsUI started, matchingData: ${JSON.stringify(matchingData)}`); switch (variant) { - case 'default': + case "default": default: return ; } diff --git a/src/theme/conversion/components/cdstatistics/components/variants/CdStatisticsDefaultVariant.tsx b/src/theme/conversion/components/cdstatistics/components/variants/CdStatisticsDefaultVariant.tsx index d041cd6..5033835 100644 --- a/src/theme/conversion/components/cdstatistics/components/variants/CdStatisticsDefaultVariant.tsx +++ b/src/theme/conversion/components/cdstatistics/components/variants/CdStatisticsDefaultVariant.tsx @@ -1,19 +1,19 @@ -import React from 'react'; +import React from "react"; import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; +import { CdStatisticsData, StatisticItem } from "../index"; -interface Stat { - value: string; - description: string; +interface CdStatisticsDefaultVariantProps extends StandardComponentProps { + matchingData: CdStatisticsData; } -const CdStatisticsDefaultVariant: React.FC = ({ matchingData }) => { +const CdStatisticsDefaultVariant: React.FC = ({ matchingData }) => { const stats = matchingData?.stats || []; return (
- {stats.map((stat: Stat, index: number) => ( + {stats.map((stat: StatisticItem, index: number) => (
{stat.value}
{stat.description}
diff --git a/src/theme/conversion/components/herobanner/components/variants/IntroductionVariant.tsx b/src/theme/conversion/components/herobanner/components/variants/IntroductionVariant.tsx index a7e7d4e..49d7e3c 100644 --- a/src/theme/conversion/components/herobanner/components/variants/IntroductionVariant.tsx +++ b/src/theme/conversion/components/herobanner/components/variants/IntroductionVariant.tsx @@ -5,8 +5,8 @@ export default function IntroductionVariant(props: StandardComponentProps) { const { title, subtitle, category } = matchingData; return ( -
-
+
+
{category}

{title}

From 55ff32433ab622dfd13c8f29922fc39086bab1de Mon Sep 17 00:00:00 2001 From: Anh Pham Date: Tue, 8 Jul 2025 19:18:43 -0500 Subject: [PATCH 11/13] Fixed Nav and Footer to be global components, cleaned up unnecessary fields --- migrate-nav-footer.js | 146 ++++++++++++++++++ remove-old-components.js | 75 +++++++++ remove-old-components.mjs | 82 ++++++++++ .../components/cdfooter/sanity-query.ts | 46 ++++++ .../components/cdfooter/sanity-schema.ts | 63 +++++++- .../components/cdnav/sanity-query.ts | 28 ++++ .../components/cdnav/sanity-schema.ts | 61 +++++++- 7 files changed, 497 insertions(+), 4 deletions(-) create mode 100644 migrate-nav-footer.js create mode 100644 remove-old-components.js create mode 100644 remove-old-components.mjs diff --git a/migrate-nav-footer.js b/migrate-nav-footer.js new file mode 100644 index 0000000..d9fbdaf --- /dev/null +++ b/migrate-nav-footer.js @@ -0,0 +1,146 @@ +// Migration script to convert old cdnav/cdfooter to new reference components +const sanityClient = require('@sanity/client') +require('dotenv').config({ path: '../../.env.local' }) + +// Configure Sanity client +const client = sanityClient({ + projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || process.env.SANITY_STUDIO_PROJECT_ID, + dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production', + apiVersion: '2023-01-01', + token: process.env.SANITY_API_TOKEN, + useCdn: false +}) + +async function findGlobalComponents() { + console.log('šŸ” Finding global navigation and footer components...') + + // Find global navigation + const globalNavs = await client.fetch(`*[_type == "cdnav"] | order(_createdAt desc)[0...5]{ + _id, + title, + _createdAt + }`) + + // Find global footer + const globalFooters = await client.fetch(`*[_type == "cdfooter"] | order(_createdAt desc)[0...5]{ + _id, + title, + _createdAt + }`) + + console.log('\nšŸ“‹ Available Global Navigation Components:') + globalNavs.forEach((nav, i) => { + console.log(` ${i + 1}. ${nav.title || 'Untitled'} (ID: ${nav._id})`) + }) + + console.log('\nšŸ“‹ Available Global Footer Components:') + globalFooters.forEach((footer, i) => { + console.log(` ${i + 1}. ${footer.title || 'Untitled'} (ID: ${footer._id})`) + }) + + // Return the first ones (most recent) as defaults + return { + navId: globalNavs[0]?._id, + footerId: globalFooters[0]?._id + } +} + +async function migrateComponents() { + try { + console.log('šŸš€ Starting migration...\n') + + // Get global component IDs + const { navId, footerId } = await findGlobalComponents() + + if (!navId || !footerId) { + console.error('āŒ Error: Could not find global navigation or footer components.') + console.log('\nšŸ“ Please create global components first:') + console.log(' 1. Go to Sanity Studio') + console.log(' 2. Navigate to "Global Components"') + console.log(' 3. Create a Navigation (CD) document') + console.log(' 4. Create a Footer (CD) document') + console.log(' 5. Run this migration script again') + return + } + + console.log(`\nāœ… Using global navigation: ${navId}`) + console.log(`āœ… Using global footer: ${footerId}\n`) + + // Fetch all pages and homepage with old components + const documents = await client.fetch(` + *[_type in ["page", "homepage"] && defined(components) && count(components[_type in ["cdnav", "cdfooter"]]) > 0]{ + _id, + _type, + _rev, + title, + "componentTypes": components[]._type, + components + } + `) + + console.log(`šŸ“„ Found ${documents.length} documents to migrate\n`) + + for (const doc of documents) { + console.log(`\nšŸ”„ Processing ${doc._type}: ${doc.title || doc._id}`) + console.log(` Current components: ${doc.componentTypes.join(', ')}`) + + // Transform components + const updatedComponents = doc.components.map((component, index) => { + if (component._type === 'cdnav') { + console.log(` āœļø Converting cdnav at position ${index} to cdnavReference`) + return { + _type: 'cdnavReference', + _key: component._key || `nav-${Date.now()}`, + globalComponentSource: { + _type: 'reference', + _ref: navId + } + } + } + + if (component._type === 'cdfooter') { + console.log(` āœļø Converting cdfooter at position ${index} to cdfooterReference`) + return { + _type: 'cdfooterReference', + _key: component._key || `footer-${Date.now()}`, + globalComponentSource: { + _type: 'reference', + _ref: footerId + } + } + } + + return component + }) + + // Update the document + try { + await client + .patch(doc._id) + .set({ components: updatedComponents }) + .commit() + + console.log(` āœ… Successfully updated ${doc._type}: ${doc.title || doc._id}`) + } catch (error) { + console.error(` āŒ Failed to update ${doc._id}:`, error.message) + } + } + + console.log('\n✨ Migration complete!') + console.log('\nšŸ“ Next steps:') + console.log(' 1. Rebuild your project: pnpm build') + console.log(' 2. Redeploy GraphQL API: pnpm sanity graphql deploy --force') + console.log(' 3. Test your site') + console.log(' 4. Remove temporary types from page.ts schema') + + } catch (error) { + console.error('\nāŒ Migration failed:', error) + console.error('\nšŸ’” Make sure you have:') + console.error(' - Valid Sanity API token in SANITY_API_TOKEN') + console.error(' - Correct project ID and dataset') + console.error(' - Created global navigation and footer components') + } +} + +// Run the migration +migrateComponents() \ No newline at end of file diff --git a/remove-old-components.js b/remove-old-components.js new file mode 100644 index 0000000..43c43e6 --- /dev/null +++ b/remove-old-components.js @@ -0,0 +1,75 @@ +// Script to remove old cdnav/cdfooter components from pages +const sanityClient = require('@sanity/client') +require('dotenv').config({ path: '../../.env.local' }) + +// Configure Sanity client +const client = sanityClient({ + projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || process.env.SANITY_STUDIO_PROJECT_ID, + dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production', + apiVersion: '2023-01-01', + token: process.env.SANITY_API_TOKEN, + useCdn: false +}) + +async function removeOldComponents() { + try { + console.log('šŸš€ Starting cleanup of old components...\n') + + // Fetch all pages and homepage with old components + const documents = await client.fetch(` + *[_type in ["page", "homepage"] && defined(components) && count(components[_type in ["cdnav", "cdfooter"]]) > 0]{ + _id, + _type, + _rev, + title, + "oldComponentCount": count(components[_type in ["cdnav", "cdfooter"]]), + components + } + `) + + console.log(`šŸ“„ Found ${documents.length} documents with old components\n`) + + for (const doc of documents) { + console.log(`\nšŸ”„ Processing ${doc._type}: ${doc.title || doc._id}`) + console.log(` Found ${doc.oldComponentCount} old components to remove`) + + // Filter out old components + const updatedComponents = doc.components.filter(component => { + if (component._type === 'cdnav' || component._type === 'cdfooter') { + console.log(` āŒ Removing ${component._type}`) + return false + } + return true + }) + + // Update the document + try { + await client + .patch(doc._id) + .set({ components: updatedComponents }) + .commit() + + console.log(` āœ… Successfully cleaned ${doc._type}: ${doc.title || doc._id}`) + console.log(` šŸ“Š Components: ${doc.components.length} → ${updatedComponents.length}`) + } catch (error) { + console.error(` āŒ Failed to update ${doc._id}:`, error.message) + } + } + + console.log('\n✨ Cleanup complete!') + console.log('\nšŸ“ Next steps:') + console.log(' 1. Go to Sanity Studio') + console.log(' 2. Add new Navigation and Footer reference components to your pages') + console.log(' 3. Select the global components you created') + console.log(' 4. Publish the changes') + + } catch (error) { + console.error('\nāŒ Cleanup failed:', error) + console.error('\nšŸ’” Make sure you have:') + console.error(' - Valid Sanity API token in SANITY_API_TOKEN') + console.error(' - Correct project ID and dataset') + } +} + +// Run the cleanup +removeOldComponents() \ No newline at end of file diff --git a/remove-old-components.mjs b/remove-old-components.mjs new file mode 100644 index 0000000..ff170d4 --- /dev/null +++ b/remove-old-components.mjs @@ -0,0 +1,82 @@ +// Script to remove old cdnav/cdfooter components from pages +import { createClient } from '@sanity/client' +import dotenv from 'dotenv' +import { fileURLToPath } from 'url' +import { dirname, join } from 'path' + +// Load environment variables +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) +dotenv.config({ path: join(__dirname, '../../.env.local') }) + +// Configure Sanity client +const client = createClient({ + projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || process.env.SANITY_STUDIO_PROJECT_ID, + dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production', + apiVersion: '2023-01-01', + token: process.env.SANITY_API_TOKEN, + useCdn: false +}) + +async function removeOldComponents() { + try { + console.log('šŸš€ Starting cleanup of old components...\n') + + // Fetch all pages and homepage with old components + const documents = await client.fetch(` + *[_type in ["page", "homepage"] && defined(components) && count(components[_type in ["cdnav", "cdfooter"]]) > 0]{ + _id, + _type, + _rev, + title, + "oldComponentCount": count(components[_type in ["cdnav", "cdfooter"]]), + components + } + `) + + console.log(`šŸ“„ Found ${documents.length} documents with old components\n`) + + for (const doc of documents) { + console.log(`\nšŸ”„ Processing ${doc._type}: ${doc.title || doc._id}`) + console.log(` Found ${doc.oldComponentCount} old components to remove`) + + // Filter out old components + const updatedComponents = doc.components.filter(component => { + if (component._type === 'cdnav' || component._type === 'cdfooter') { + console.log(` āŒ Removing ${component._type}`) + return false + } + return true + }) + + // Update the document + try { + await client + .patch(doc._id) + .set({ components: updatedComponents }) + .commit() + + console.log(` āœ… Successfully cleaned ${doc._type}: ${doc.title || doc._id}`) + console.log(` šŸ“Š Components: ${doc.components.length} → ${updatedComponents.length}`) + } catch (error) { + console.error(` āŒ Failed to update ${doc._id}:`, error.message) + } + } + + console.log('\n✨ Cleanup complete!') + console.log('\nšŸ“ Next steps:') + console.log(' 1. Go to Sanity Studio') + console.log(' 2. Add new Navigation and Footer reference components to your pages') + console.log(' 3. Select the global components you created') + console.log(' 4. Publish the changes') + + } catch (error) { + console.error('\nāŒ Cleanup failed:', error) + console.error('\nšŸ’” Make sure you have:') + console.error(' - Valid Sanity API token in SANITY_API_TOKEN') + console.error(' - Correct project ID and dataset') + } +} + +// Run the cleanup +removeOldComponents() \ No newline at end of file diff --git a/src/theme/conversion/components/cdfooter/sanity-query.ts b/src/theme/conversion/components/cdfooter/sanity-query.ts index 2a59fe8..59d44b5 100644 --- a/src/theme/conversion/components/cdfooter/sanity-query.ts +++ b/src/theme/conversion/components/cdfooter/sanity-query.ts @@ -3,6 +3,52 @@ import { PageAndSingleComponentDetails } from "@conversiondigital/headless-basic export function query(pageAndComponentCombo: PageAndSingleComponentDetails): string { return ` query GetCdfooterBySlug($slug: String!) { + allCdfooter { + __typename + _id + _key + _type + selectableVariant + title + subtitle + additionalInformation { + phoneNumber + email + address + } + copyrightMessage + logo { + asset { + url + } + } + secondaryLogo { + asset { + url + } + } + backgroundImage { + asset { + url + } + } + socialLinks { + platform + url + logo { + asset { + url + } + } + } + linkGroups { + title + links { + label + url + } + } + } allPage(where: { slug: { current: { eq: $slug } } }) { components { __typename diff --git a/src/theme/conversion/components/cdfooter/sanity-schema.ts b/src/theme/conversion/components/cdfooter/sanity-schema.ts index 5f35f28..ce47667 100644 --- a/src/theme/conversion/components/cdfooter/sanity-schema.ts +++ b/src/theme/conversion/components/cdfooter/sanity-schema.ts @@ -1,5 +1,5 @@ import { defineField, defineType, defineArrayMember } from 'sanity' -import { EyeOpenIcon } from '@sanity/icons' +import { EyeOpenIcon, BlockElementIcon } from '@sanity/icons' import { linkItem } from '@conversiondigital/headless-basics-data/src/cms/sanity/sanityCommonSchema' // Define the socialLink type @@ -85,7 +85,7 @@ export const additionalInformationType = defineType({ export default defineType({ name: 'cdfooter', title: 'Footer (CD)', - type: 'object', + type: 'document', icon: EyeOpenIcon, fields: [ defineField({ @@ -191,4 +191,63 @@ export default defineType({ } }) +// Override settings type for footer +export const cdfooterOverrideSettings = defineType({ + name: 'cdfooterOverrideSettings', + title: 'Footer Override Settings', + type: 'object', + fields: [ + defineField({ + name: 'hideOnThisPage', + title: 'Hide Footer on This Page', + type: 'boolean' + }), + defineField({ + name: 'customCopyright', + title: 'Custom Copyright Message', + type: 'string', + description: 'Override the copyright message for this page only' + }) + ] +}) +// Simplified reference component for use in pages +export const cdfooterReference = defineType({ + name: 'cdfooterReference', + title: 'Footer', + type: 'object', + icon: BlockElementIcon, + fields: [ + defineField({ + name: 'globalComponentSource', + title: 'Select Footer', + type: 'reference', + to: [{ type: 'cdfooter' }], + description: 'Choose a global footer component.', + validation: Rule => Rule.required() + }), + defineField({ + name: 'overrideSettings', + title: 'Override Settings', + type: 'cdfooterOverrideSettings', + description: 'Optional: Override specific settings for this page only' + }) + ], + preview: { + select: { + title: 'globalComponentSource.title', + hidden: 'overrideSettings.hideOnThisPage', + customCopyright: 'overrideSettings.customCopyright' + }, + prepare({ title, hidden, customCopyright }) { + const subtitle = [] + if (hidden) subtitle.push('Hidden') + if (customCopyright) subtitle.push('Custom Copyright') + + return { + title: title || 'Footer', + subtitle: subtitle.length > 0 ? subtitle.join(', ') : 'Default settings' + } + } + } +}) diff --git a/src/theme/conversion/components/cdnav/sanity-query.ts b/src/theme/conversion/components/cdnav/sanity-query.ts index fd8d590..0670907 100644 --- a/src/theme/conversion/components/cdnav/sanity-query.ts +++ b/src/theme/conversion/components/cdnav/sanity-query.ts @@ -3,6 +3,34 @@ import { PageAndSingleComponentDetails } from "@conversiondigital/headless-basic export function query(pageAndComponentCombo: PageAndSingleComponentDetails): string { return ` query GetCdnavBySlug($slug: String!) { + allCdnav { + __typename + _id + _key + _type + selectableVariant + title + subtitle + logo { + asset { + url + } + } + isTransparent + links { + label + url + } + dropdownMenus { + label + dropdownLinks { + label + url + } + } + buttonText + buttonUrl + } allPage(where: { slug: { current: { eq: $slug } } }) { components { __typename diff --git a/src/theme/conversion/components/cdnav/sanity-schema.ts b/src/theme/conversion/components/cdnav/sanity-schema.ts index 38e554e..a871609 100644 --- a/src/theme/conversion/components/cdnav/sanity-schema.ts +++ b/src/theme/conversion/components/cdnav/sanity-schema.ts @@ -1,5 +1,5 @@ import { defineField, defineType } from 'sanity' -import { EyeOpenIcon } from '@sanity/icons' +import { EyeOpenIcon, LinkIcon } from '@sanity/icons' import { linkItem } from '@conversiondigital/headless-basics-data/src/cms/sanity/sanityCommonSchema' // Define the dropdown menu type @@ -112,5 +112,62 @@ export default defineType({ } }) +// Override settings type for navigation +export const cdnavOverrideSettings = defineType({ + name: 'cdnavOverrideSettings', + title: 'Navigation Override Settings', + type: 'object', + fields: [ + defineField({ + name: 'isTransparent', + title: 'Make Transparent', + type: 'boolean' + }), + defineField({ + name: 'hideOnThisPage', + title: 'Hide Navigation on This Page', + type: 'boolean' + }) + ] +}) - +// Simplified reference component for use in pages +export const cdnavReference = defineType({ + name: 'cdnavReference', + title: 'Navigation', + type: 'object', + icon: LinkIcon, + fields: [ + defineField({ + name: 'globalComponentSource', + title: 'Select Navigation', + type: 'reference', + to: [{ type: 'cdnav' }], + description: 'Choose a global navigation component.', + validation: Rule => Rule.required() + }), + defineField({ + name: 'overrideSettings', + title: 'Override Settings', + type: 'cdnavOverrideSettings', + description: 'Optional: Override specific settings for this page only' + }) + ], + preview: { + select: { + title: 'globalComponentSource.title', + transparent: 'overrideSettings.isTransparent', + hidden: 'overrideSettings.hideOnThisPage' + }, + prepare({ title, transparent, hidden }) { + const subtitle = [] + if (hidden) subtitle.push('Hidden') + if (transparent) subtitle.push('Transparent') + + return { + title: title || 'Navigation', + subtitle: subtitle.length > 0 ? subtitle.join(', ') : 'Default settings' + } + } + } +}) \ No newline at end of file From 89cd0d4b8a0c09e393d395c5a370d2b44ba78bd6 Mon Sep 17 00:00:00 2001 From: Anh Pham Date: Tue, 8 Jul 2025 19:59:17 -0500 Subject: [PATCH 12/13] Minor UI Fixes --- .../variants/cdclientsDefaultVariant.tsx | 11 +---------- .../variants/cdpartnersDefaultVariant.tsx | 10 +--------- .../variants/cdtestimonialsDefaultVariant.tsx | 14 +++++++------- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx b/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx index bbb7447..83ccb20 100644 --- a/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx +++ b/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx @@ -32,7 +32,6 @@ const CdclientsDefaultVariant: React.FC = (props) return (
- {/* Header */}
{title && (

@@ -48,7 +47,6 @@ const CdclientsDefaultVariant: React.FC = (props)

- {/* Clients Grid */} {clientsList.length > 0 && (
{clientsList.map((client, index) => ( @@ -64,7 +62,6 @@ const CdclientsDefaultVariant: React.FC = (props) className="block w-full h-full" >
- {/* Inactive Logo */} {client.inactiveLogo && ( = (props) /> )} - {/* Active Logo */} {client.activeLogo && ( = (props) /> )} - {/* Fallback if only inactive logo exists */} {client.inactiveLogo && !client.activeLogo && ( = (props) ) : (
- {/* Inactive Logo */} {client.inactiveLogo && ( = (props) /> )} - {/* Active Logo */} {client.activeLogo && ( = (props) className="absolute inset-0 w-full h-16 object-contain opacity-0 group-hover:opacity-100 transition-all duration-300 ease-in-out" /> )} - - {/* Fallback if only inactive logo exists */} + {client.inactiveLogo && !client.activeLogo && ( = (props)
)} - {/* Button */} {buttonText && buttonUrl && (
= (props) => { const { matchingData } = props; - const title = matchingData?.title || "OUR PARTNERS"; + const title = matchingData?.title?.toUpperCase() || "OUR PARTNERS"; const subtitle = matchingData?.subtitle; const partnerLogos = matchingData?.partnerLogos || []; return (
- {/* Header */}
{title && (

@@ -40,7 +39,6 @@ const CdpartnersDefaultVariant: React.FC = (props

- {/* Partners Grid */} {partnerLogos.length > 0 && (
{partnerLogos.map((partner: PartnerItem, index: number) => ( @@ -56,7 +54,6 @@ const CdpartnersDefaultVariant: React.FC = (props className="block w-full h-full" >
- {/* Inactive Logo */} {partner.inactiveLogo && ( = (props /> )} - {/* Active Logo */} {partner.activeLogo && ( = (props /> )} - {/* Fallback if only inactive logo exists */} {partner.inactiveLogo && !partner.activeLogo && ( = (props ) : (
- {/* Inactive Logo */} {partner.inactiveLogo && ( = (props /> )} - {/* Active Logo */} {partner.activeLogo && ( = (props /> )} - {/* Fallback if only inactive logo exists */} {partner.inactiveLogo && !partner.activeLogo && (
-
-
-

{title}

-
+
+
+

{title}

+
@@ -55,9 +55,9 @@ export default function DefaultVariant(props: StandardComponentProps) {
-

{videoTitle}

+

{videoTitle}

-

{videoSubtitle}

+

{videoSubtitle}

@@ -66,7 +66,7 @@ export default function DefaultVariant(props: StandardComponentProps) {
-

{videoTestimonials[0].name?.toUpperCase()}

+

{videoTestimonials[0].name?.toUpperCase()}

{videoTestimonials[0].position?.toUpperCase()}

{videoTestimonials[0].testimonial}

From 36d2362411bef30c1f8df6a4415e64c54665a3ac Mon Sep 17 00:00:00 2001 From: TomTyack Date: Wed, 9 Jul 2025 17:34:39 +1000 Subject: [PATCH 13/13] leaf components --- .../variants/cdmissionDefaultVariant.tsx | 1 - .../components/variants/MobileNavigation.tsx | 115 ++++++++++++++++++ .../variants/cdnavDefaultVariant.tsx | 104 ++-------------- 3 files changed, 125 insertions(+), 95 deletions(-) create mode 100644 src/theme/conversion/components/cdnav/components/variants/MobileNavigation.tsx diff --git a/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx b/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx index 7c626f7..9f84efd 100644 --- a/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx +++ b/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx @@ -1,4 +1,3 @@ -'use client' import React from "react"; import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; import { getCmsImage } from "@conversiondigital/headless-basics-data/src/cms/tools/multiCmsImageTools"; diff --git a/src/theme/conversion/components/cdnav/components/variants/MobileNavigation.tsx b/src/theme/conversion/components/cdnav/components/variants/MobileNavigation.tsx new file mode 100644 index 0000000..3ec8396 --- /dev/null +++ b/src/theme/conversion/components/cdnav/components/variants/MobileNavigation.tsx @@ -0,0 +1,115 @@ +'use client' +import React, { useState } from "react"; + +interface MobileNavigationProps { + logo: string | null; + links: any[]; + dropdownMenus: any[]; + buttonText: string; + buttonUrl: string; +} + +export default function MobileNavigation({ + logo, + links, + dropdownMenus, + buttonText, + buttonUrl +}: MobileNavigationProps) { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [openDropdowns, setOpenDropdowns] = useState([]); + + const toggleDropdown = (index: number) => { + setOpenDropdowns(prev => + prev.includes(index) + ? prev.filter(i => i !== index) + : [...prev, index] + ); + }; + + return ( + <> + {/* Mobile Navigation Header */} +
+
+ {logo ? ( +
+ Nav Logo +
+ ) : ( +

No Logo

+ )} +
+ +
+ + {/* Mobile Menu */} +
+
+ {links.map((linkItem: any, idx: number) => ( + setIsMenuOpen(false)} + > + {linkItem.label ?? "Nav Link"} + + ))} + {dropdownMenus.map((menu: any, idx: number) => ( +
+ +
+ {menu.dropdownLinks?.map((link: any, linkIdx: number) => ( + setIsMenuOpen(false)} + > + {link.label ?? "Dropdown Link"} + + ))} +
+
+ ))} + +
+
+ + {/* Mobile Menu Backdrop */} + {isMenuOpen && ( +
setIsMenuOpen(false)} + /> + )} + + ); +}; \ No newline at end of file diff --git a/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx b/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx index 78b376e..e14c774 100644 --- a/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx +++ b/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx @@ -1,10 +1,10 @@ -'use client' -import React, { useState } from "react"; +import React from "react"; import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; import { getCmsImage } from "@conversiondigital/headless-basics-data/src/cms/tools/multiCmsImageTools"; +import MobileNavigation from "./MobileNavigation"; export default function cdnavDefaultVariant(props: StandardComponentProps) { - const { blueprint, componentInformation, matchingData } = props; + const { matchingData } = props; const subtitle = matchingData?.subtitle || ""; const isTransparent = matchingData?.isTransparent === true; const logo = matchingData?.logo?.asset?.url || null; @@ -16,17 +16,6 @@ export default function cdnavDefaultVariant(props: StandardComponentProps) { const { hasImage: hasMobile, imageLocation: mobileSrc } = getCmsImage({ image: matchingData?.mobileImage }); const { hasImage: hasDesktop, imageLocation: desktopSrc } = getCmsImage({ image: matchingData?.desktopImage }); - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [openDropdowns, setOpenDropdowns] = useState([]); - - const toggleDropdown = (index: number) => { - setOpenDropdowns(prev => - prev.includes(index) - ? prev.filter(i => i !== index) - : [...prev, index] - ); - }; - return (