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/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/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/cdclients/components/variants/cdclientsDefaultVariant.tsx b/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx index c3b144e..83ccb20 100644 --- a/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx +++ b/src/theme/conversion/components/cdclients/components/variants/cdclientsDefaultVariant.tsx @@ -2,52 +2,137 @@ 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 ( -
-
+
+
-

{title}

+ {title && ( +

+ {title} +

+ )} + {subtitle && ( +

+ {subtitle} +

+ )}
-
- {Array.isArray(clients) && - clients.map((client: any, index: number) => { - const logoUrl = client?.logo?.asset?.url; - return ( -
- {logoUrl && ( - {client?.name - )} -
- ); - })} -
- + + + {clientsList.length > 0 && ( +
+ {clientsList.map((client, index) => ( +
+ {client.link ? ( + +
+ {client.inactiveLogo && ( + {`${client.name} + )} + + {client.activeLogo && ( + {`${client.name} + )} + + {client.inactiveLogo && !client.activeLogo && ( + {`${client.name} + )} +
+
+ ) : ( +
+ {client.inactiveLogo && ( + {`${client.name} + )} + + {client.activeLogo && ( + {`${client.name} + )} + + {client.inactiveLogo && !client.activeLogo && ( + {`${client.name} + )} +
+ )} +
+ ))} +
+ )} + + {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) => (
- {client.logo && ( + {(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 + )} +
)}
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/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/cdfaqs/components/index.tsx b/src/theme/conversion/components/cdfaqs/components/index.tsx new file mode 100644 index 0000000..87a2fb0 --- /dev/null +++ b/src/theme/conversion/components/cdfaqs/components/index.tsx @@ -0,0 +1,49 @@ +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 CdFaqsDefaultVariant from "./variants/CdFaqsDefaultVariant"; + +export const log = getLogger("conversion.components.cdfaqs"); + +// Interface for individual FAQ item +export interface FaqItem { + title: string; + richtextRaw: any[]; +} + +// Interface for global component source +interface GlobalComponentSource { + __typename: string; + _key: string; + _type: string; + title: string; + description: string; + items: FaqItem[]; +} + +// Interface for CdFaqs component data +export interface CdFaqsData { + __typename: string; + _key: string; + _type: string; + selectableVariant: string; + title: string; + description: string; + items: FaqItem[]; + sortOrder: number; + globalComponentSource?: GlobalComponentSource; +} + +export default function CdFaqsUI(dynamicComponent: ViewComponentProps) { + const { variant, matchingData } = componentBoilerPlate(dynamicComponent); + if (!matchingData) return null; + + log.trace(`${logPrefix()} CdFaqsUI started, matchingData: ${JSON.stringify(matchingData)}`); + + switch (variant) { + case "default": + default: + 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..f1eda5f --- /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 { CdFaqsData, FaqItem } from "../index"; +import { toHTML } from "@portabletext/to-html"; +import React, { useState } from "react"; + +interface CdFaqsDefaultVariantProps extends StandardComponentProps { + matchingData: CdFaqsData; +} + +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/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/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/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/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/cdintroduction/components/index.tsx b/src/theme/conversion/components/cdintroduction/components/index.tsx new file mode 100644 index 0000000..3b0f8d4 --- /dev/null +++ b/src/theme/conversion/components/cdintroduction/components/index.tsx @@ -0,0 +1,41 @@ +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 CdIntroductionDefaultVariant from "./variants/CdIntroductionDefaultVariant"; + +export const log = getLogger("conversion.components.cdintroduction"); + +// Interface for global component source +interface GlobalComponentSource { + __typename: string; + _key: string; + _type: string; + title: string; + richtextRaw: any; +} + +// Interface for CdIntroduction component data +export interface CdIntroductionData { + __typename: string; + _key: string; + _type: string; + selectableVariant: string; + title: string; + richtextRaw: any; + sortOrder: number; + globalComponentSource?: GlobalComponentSource; +} + +export default function CdIntroductionUI(dynamicComponent: ViewComponentProps) { + const { variant, matchingData } = componentBoilerPlate(dynamicComponent); + if (!matchingData) return null; + + log.trace(`${logPrefix()} CdIntroductionUI started, matchingData: ${JSON.stringify(matchingData)}`); + + switch (variant) { + case "default": + default: + return ; + } +} 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..6a176db --- /dev/null +++ b/src/theme/conversion/components/cdintroduction/components/variants/CdIntroductionDefaultVariant.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { StandardComponentProps } from "@conversiondigital/headless-basics-components/src/interfaces/standardComponentProps"; +import { CdIntroductionData } from '../index'; +import {toHTML} from '@portabletext/to-html' + +interface CdIntroductionDefaultVariantProps extends StandardComponentProps { + matchingData: CdIntroductionData; +} + +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 73% rename from src/theme/conversion/components/cdserviceintro/sanity-schema.ts rename to src/theme/conversion/components/cdintroduction/sanity-schema.ts index be81d82..6842609 100644 --- a/src/theme/conversion/components/cdserviceintro/sanity-schema.ts +++ b/src/theme/conversion/components/cdintroduction/sanity-schema.ts @@ -1,9 +1,10 @@ import { defineField, defineType } from 'sanity' import { DocumentTextIcon } from '@sanity/icons' +// 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: [ @@ -17,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: [ { @@ -61,8 +56,14 @@ export default defineType({ fields: [ { name: 'href', - type: 'url', + type: 'string', title: 'URL' + }, + { + name: 'blank', + type: 'boolean', + title: 'Open in new tab', + initialValue: false } ] } @@ -70,7 +71,7 @@ export default defineType({ } } ], - description: 'The rich text content for the service introduction' + description: 'The rich text content for the introduction' }), defineField({ name: 'sortOrder', @@ -81,14 +82,13 @@ export default defineType({ name: 'globalComponentSource', title: 'Global Component Source', type: 'reference', - to: [{ type: 'cdserviceintro' }], - description: 'Select a global re-usable service introduction.' - }) + 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/cdmission/components/variants/cdmissionDefaultVariant.tsx b/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx index e7ae22a..9f84efd 100644 --- a/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx +++ b/src/theme/conversion/components/cdmission/components/variants/cdmissionDefaultVariant.tsx @@ -15,26 +15,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 +42,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/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 */} +
+ + {/* 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 8f23166..e14c774 100644 --- a/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx +++ b/src/theme/conversion/components/cdnav/components/variants/cdnavDefaultVariant.tsx @@ -1,9 +1,10 @@ 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,78 +17,92 @@ export default function cdnavDefaultVariant(props: StandardComponentProps) { const { hasImage: hasDesktop, imageLocation: desktopSrc } = getCmsImage({ image: matchingData?.desktopImage }); return ( -