From 9b3e2bdb1f9cc82750300b24faeb8fd9bf17c7fd Mon Sep 17 00:00:00 2001 From: federicodelpiano Date: Fri, 8 Mar 2024 17:14:52 -0300 Subject: [PATCH 1/3] feat: create conscia package --- .../src/components/cms/commerce-connector.tsx | 29 +-- composable-ui/src/server/api/routers/cms.ts | 12 +- packages/conscia/.eslintrc.js | 4 + packages/conscia/README.md | 25 +++ packages/conscia/index.ts | 1 + packages/conscia/package.json | 22 +++ packages/conscia/src/index.ts | 3 + packages/conscia/src/service/client.ts | 9 + packages/conscia/src/service/index.ts | 3 + packages/conscia/src/service/page.ts | 18 ++ packages/conscia/src/service/templates.ts | 18 ++ packages/conscia/src/types.ts | 126 ++++++++++++ packages/conscia/src/utils/images.ts | 3 + packages/conscia/src/utils/index.ts | 2 + packages/conscia/src/utils/page.ts | 185 ++++++++++++++++++ packages/conscia/tsconfig.json | 5 + packages/types/package.json | 3 + .../types/src/cms/components/article-card.ts | 18 ++ .../types/src/cms/components/banner-full.ts | 36 ++++ .../types/src/cms/components/banner-split.ts | 22 +++ .../src/cms/components/banner-text-only.ts | 18 ++ .../src/cms/components/commerce-connector.ts | 44 +++++ .../types/src/cms/components/cover-card.ts | 19 ++ packages/types/src/cms/components/fields.ts | 10 + packages/types/src/cms/components/grid.ts | 25 +++ packages/types/src/cms/components/index.ts | 8 + .../types/src/cms/components/text-card.ts | 16 ++ packages/types/src/cms/index.ts | 176 +---------------- packages/types/src/cms/page.ts | 30 +++ pnpm-lock.yaml | 32 ++- 30 files changed, 715 insertions(+), 207 deletions(-) create mode 100644 packages/conscia/.eslintrc.js create mode 100644 packages/conscia/README.md create mode 100644 packages/conscia/index.ts create mode 100644 packages/conscia/package.json create mode 100644 packages/conscia/src/index.ts create mode 100644 packages/conscia/src/service/client.ts create mode 100644 packages/conscia/src/service/index.ts create mode 100644 packages/conscia/src/service/page.ts create mode 100644 packages/conscia/src/service/templates.ts create mode 100644 packages/conscia/src/types.ts create mode 100644 packages/conscia/src/utils/images.ts create mode 100644 packages/conscia/src/utils/index.ts create mode 100644 packages/conscia/src/utils/page.ts create mode 100644 packages/conscia/tsconfig.json create mode 100644 packages/types/src/cms/components/article-card.ts create mode 100644 packages/types/src/cms/components/banner-full.ts create mode 100644 packages/types/src/cms/components/banner-split.ts create mode 100644 packages/types/src/cms/components/banner-text-only.ts create mode 100644 packages/types/src/cms/components/commerce-connector.ts create mode 100644 packages/types/src/cms/components/cover-card.ts create mode 100644 packages/types/src/cms/components/fields.ts create mode 100644 packages/types/src/cms/components/grid.ts create mode 100644 packages/types/src/cms/components/index.ts create mode 100644 packages/types/src/cms/components/text-card.ts create mode 100644 packages/types/src/cms/page.ts diff --git a/composable-ui/src/components/cms/commerce-connector.tsx b/composable-ui/src/components/cms/commerce-connector.tsx index de89109..c2bf978 100644 --- a/composable-ui/src/components/cms/commerce-connector.tsx +++ b/composable-ui/src/components/cms/commerce-connector.tsx @@ -1,34 +1,9 @@ import { Button, Container, Flex, HStack, Text } from '@chakra-ui/react' +import { CommerceConnectorProps, PriceProps } from '@composable/types' import { ProductCard } from '@composable/ui' import { useRouter } from 'next/router' -export interface GenericConnectorProps { - title?: string - ctaLabel?: string - ctaHref?: string - ctaHeight?: string - ctaMaxWidth?: string - ctaMinWidth?: string - products?: { - name: string - slug: string - brand?: string - img?: { - url?: string - alt?: string - } - price?: PriceProps - }[] -} - -export interface PriceProps { - current: number - currentFormatted: string - regular?: number - regularFormatted?: string -} - -export const CommerceConnector = (props: GenericConnectorProps) => { +export const CommerceConnector = (props: CommerceConnectorProps) => { const { title, ctaLabel, diff --git a/composable-ui/src/server/api/routers/cms.ts b/composable-ui/src/server/api/routers/cms.ts index ff17183..7a552e1 100644 --- a/composable-ui/src/server/api/routers/cms.ts +++ b/composable-ui/src/server/api/routers/cms.ts @@ -1,12 +1,18 @@ -import { createTRPCRouter, publicProcedure } from '../trpc' import { getPage } from '@composable/cms-generic' -import { PageProps } from '@composable/types' +import { PageSchema } from '@composable/types' import { z } from 'zod' +import { createTRPCRouter, publicProcedure } from '../trpc' export const cmsRouter = createTRPCRouter({ getPage: publicProcedure .input(z.object({ slug: z.string() })) .query(async ({ input }) => { - return getPage({ pageSlug: input.slug }) as PageProps + try { + const page = await getPage({ pageSlug: input.slug }) + return PageSchema.parse(page) + } catch (err) { + console.log(err) + return null + } }), }) diff --git a/packages/conscia/.eslintrc.js b/packages/conscia/.eslintrc.js new file mode 100644 index 0000000..b56159e --- /dev/null +++ b/packages/conscia/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ['custom'], +} diff --git a/packages/conscia/README.md b/packages/conscia/README.md new file mode 100644 index 0000000..b367495 --- /dev/null +++ b/packages/conscia/README.md @@ -0,0 +1,25 @@ +# Integrating Conscia Components into Composable-UI + + +# Installation and Setup + +1. **Navigating to the Project Directory:** Open your terminal and navigate to your local `composable-ui` project directory and then move to `composable-ui` subfolder. + ```bash + cd path/to/composable-ui/composable-ui + ``` +1. **Install the @composable/conscia package**: + ```bash + pnpm install @composable/conscia + ``` +1. **Set the required environment variables:** + + ```shell + NEXT_PUBLIC_CONSCIA_BASE_URL=https://...conscia.io/api + NEXT_PUBLIC_CONSCIA_TOKEN= + NEXT_PUBLIC_CONSCIA_CUSTOMER_CODE= + ``` +1. **Replace the getPage import for the @composable/conscia one:** + ```javascript + import { getPage } from '@composable/conscia' + ``` + diff --git a/packages/conscia/index.ts b/packages/conscia/index.ts new file mode 100644 index 0000000..6f39cd4 --- /dev/null +++ b/packages/conscia/index.ts @@ -0,0 +1 @@ +export * from './src' diff --git a/packages/conscia/package.json b/packages/conscia/package.json new file mode 100644 index 0000000..6bf25a4 --- /dev/null +++ b/packages/conscia/package.json @@ -0,0 +1,22 @@ +{ + "name": "@composable/conscia", + "version": "0.0.0", + "main": "./index.ts", + "types": "./index.ts", + "sideEffects": "false", + "scripts": { + "build": "echo \"Build script for @composable/conscia ...\"", + "lint": "eslint \"**/*.{js,ts,tsx}\" --max-warnings 0", + "ts": "tsc --noEmit --incremental" + }, + "dependencies": { + "@composable/types": "workspace:*", + "axios": "0.26.1" + }, + "devDependencies": { + "@types/node": "^18.6.3", + "eslint-config-custom": "workspace:*", + "tsconfig": "workspace:*", + "typescript": "^4.5.5" + } +} diff --git a/packages/conscia/src/index.ts b/packages/conscia/src/index.ts new file mode 100644 index 0000000..7b68f3c --- /dev/null +++ b/packages/conscia/src/index.ts @@ -0,0 +1,3 @@ +export * from './service' +export * from './utils' +export * from './types' diff --git a/packages/conscia/src/service/client.ts b/packages/conscia/src/service/client.ts new file mode 100644 index 0000000..0a162b3 --- /dev/null +++ b/packages/conscia/src/service/client.ts @@ -0,0 +1,9 @@ +import axios from 'axios' + +export const consciaClient = axios.create({ + baseURL: process.env.NEXT_PUBLIC_CONSCIA_BASE_URL, + headers: { + Authorization: `Bearer ${process.env.NEXT_PUBLIC_CONSCIA_TOKEN}`, + 'X-Customer-Code': process.env.NEXT_PUBLIC_CONSCIA_CUSTOMER_CODE ?? '', + }, +}) diff --git a/packages/conscia/src/service/index.ts b/packages/conscia/src/service/index.ts new file mode 100644 index 0000000..a34885e --- /dev/null +++ b/packages/conscia/src/service/index.ts @@ -0,0 +1,3 @@ +export * from './client' +export * from './page' +export * from './templates' diff --git a/packages/conscia/src/service/page.ts b/packages/conscia/src/service/page.ts new file mode 100644 index 0000000..7064ace --- /dev/null +++ b/packages/conscia/src/service/page.ts @@ -0,0 +1,18 @@ +import { PageProps } from '@composable/types' +import { ConsciaPageTemplateComponents } from '../types' +import { getTemplateData } from './templates' +import { transformPage } from '../utils' + +export const getPage = async ({ + pageSlug, +}: { + pageSlug: string +}): Promise => { + if (!pageSlug) { + return null + } + const consciaPage = await getTemplateData({ + templateCode: pageSlug, + }) + return consciaPage ? transformPage({ consciaPage, pageSlug }) : null +} diff --git a/packages/conscia/src/service/templates.ts b/packages/conscia/src/service/templates.ts new file mode 100644 index 0000000..5bec3f9 --- /dev/null +++ b/packages/conscia/src/service/templates.ts @@ -0,0 +1,18 @@ +import { ConsciaComponent, ConsciaTemplate } from '../types' +import { consciaClient } from './client' + +export const getTemplateData = async < + Components extends ConsciaComponent +>({ + templateCode, +}: { + templateCode: string +}) => { + const response = await consciaClient.post>( + '/experience/template/_query', + { + templateCode, + } + ) + return response.data +} diff --git a/packages/conscia/src/types.ts b/packages/conscia/src/types.ts new file mode 100644 index 0000000..f399e9b --- /dev/null +++ b/packages/conscia/src/types.ts @@ -0,0 +1,126 @@ +import { + BannerFullProps, + BannerSplitProps, + BannerTextOnlyProps, + CommerceConnectorProps, + CommerceProduct, + GridProps, + TextCardProps, +} from '@composable/types' + +export interface ConsciaTemplate> { + duration: number + components: Record + errors: any[] +} + +export interface ConsciaComponent { + '@extras': { + rule: { + metadata: any[] + attributes: Record + } + } + status: 'VALID' | 'INVALID' + response: ComponentData +} + +export type ConsciaHeroBanner = { + __typename: BannerSplitProps['__typename'] + containerMarginBottom?: number + containerMarginTop?: number + containerSize: BannerSplitProps['containerSize'] + textPosition: string + theme: string + id: BannerSplitProps['id'] + content: BannerSplitProps['content'] + title: BannerSplitProps['title'] + ctaAlphaHref: BannerSplitProps['ctaAlphaHref'] + ctaAlphaLabel: BannerSplitProps['ctaAlphaLabel'] + image: { + title: string + description: string + url: string + } +} + +export type ConsciaCTABanner = { + __typename: BannerFullProps['__typename'] + containerMarginBottom?: number + containerMarginTop?: number + containerSize: BannerFullProps['containerSize'] + overlayBackground: BannerFullProps['overlayBackground'] + textPosition: BannerFullProps['textPosition'] + theme: BannerFullProps['theme'] + id: BannerFullProps['id'] + content: BannerFullProps['content'] + title: BannerFullProps['title'] + ctaAlphaLabel: BannerFullProps['ctaAlphaLabel'] + ctaAlphaHref: BannerFullProps['ctaAlphaHref'] + image: { + url: string + title: string + } + linkHref1: BannerFullProps['linkHref1'] + linkLabel1: BannerFullProps['linkLabel1'] +} + +export type ConsciaFeatureCardsHeader = { + __typename: BannerTextOnlyProps['__typename'] + centered: BannerTextOnlyProps['centered'] + id: BannerTextOnlyProps['id'] + content: BannerTextOnlyProps['content'] + title: BannerTextOnlyProps['title'] + ctaAlphaLabel: BannerTextOnlyProps['ctaAlphaLabel'] + ctaAlphaHref: BannerTextOnlyProps['ctaAlphaHref'] +} + +export interface ConsciaFeaturedProducts { + __typename: CommerceConnectorProps['__typename'] + id: CommerceConnectorProps['id'] + title: CommerceConnectorProps['title'] + containerMarginBottom?: number + containerMarginTop?: number + containerSize: CommerceConnectorProps['containerSize'] + ctaMaxWidth: string + ctaMinWidth: string + ctaLabel: CommerceConnectorProps['ctaLabel'] + ctaHref: CommerceConnectorProps['ctaHref'] + products: (Omit & { image: CommerceProduct['img'] })[] +} + +export type ConsciaGrid = { + __typename: GridProps['__typename'] + id: GridProps['id'] + columns: GridProps['columns'] + containerMarginBottom?: number + containerMarginTop?: number + gridGap: GridProps['gridGap'] + containerSize: GridProps['containerSize'] + items: ConsciaTextCard[] +} + +export type ConsciaTextCard = { + __typename: TextCardProps['__typename'] + id: TextCardProps['id'] + title: TextCardProps['title'] + image: { + url: string + title: string + } + content: TextCardProps['content'] + ctaLabel: TextCardProps['ctaLabel'] + ctaHref: TextCardProps['ctaHref'] + theme: TextCardProps['theme'] + textAlign: TextCardProps['textAlign'] +} + +export type ConsciaPageTemplateComponents = ConsciaComponent< + | ConsciaHeroBanner + | ConsciaCTABanner + | ConsciaFeatureCardsHeader + | ConsciaFeaturedProducts + | ConsciaGrid +> +export type ConsciaPageTemplateResponse = + ConsciaTemplate diff --git a/packages/conscia/src/utils/images.ts b/packages/conscia/src/utils/images.ts new file mode 100644 index 0000000..e84f5e8 --- /dev/null +++ b/packages/conscia/src/utils/images.ts @@ -0,0 +1,3 @@ +export const parseImageUrl = (imageUrl: string) => { + return imageUrl.startsWith('//') ? `https:${imageUrl}` : imageUrl +} diff --git a/packages/conscia/src/utils/index.ts b/packages/conscia/src/utils/index.ts new file mode 100644 index 0000000..a9ea2d6 --- /dev/null +++ b/packages/conscia/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './images' +export * from './page' diff --git a/packages/conscia/src/utils/page.ts b/packages/conscia/src/utils/page.ts new file mode 100644 index 0000000..a75dea9 --- /dev/null +++ b/packages/conscia/src/utils/page.ts @@ -0,0 +1,185 @@ +import { + BannerFullProps, + BannerSplitProps, + BannerTextOnlyProps, + CommerceConnectorProps, + GridProps, + PageItem, + PageProps, + TextCardProps, +} from '@composable/types' +import { + ConsciaCTABanner, + ConsciaFeatureCardsHeader, + ConsciaFeaturedProducts, + ConsciaGrid, + ConsciaHeroBanner, + ConsciaPageTemplateComponents, + ConsciaPageTemplateResponse, + ConsciaTextCard, +} from '../types' +import { parseImageUrl } from './images' + +export const transformPage = ({ + consciaPage, + pageSlug, +}: { + consciaPage: ConsciaPageTemplateResponse + pageSlug: string +}): PageProps => { + return { + __typename: 'pageSlug', + id: pageSlug, + items: Object.values(consciaPage.components).map(transformPageComponent), + metaDescription: '', + metaKeywords: [], + metaTitle: '', + slug: pageSlug, + } +} + +const transformPageComponent = ( + component: ConsciaPageTemplateComponents +): PageItem => { + switch (component.response.__typename) { + case 'BannerSplit': + return transformHeroBanner(component.response) + case 'BannerFull': + return transformCTABanner(component.response) + case 'BannerTextOnly': + return transformFeatureCardsHeader(component.response) + case 'CommerceConnector': + return transformFeaturedProducts(component.response) + case 'Grid': + return transformGrid(component.response) + } + throw new Error( + `Unknown component ${component.response.__typename} found in template` + ) +} + +const transformHeroBanner = ( + heroBanner: ConsciaHeroBanner +): BannerSplitProps => { + const image = heroBanner.image + ? { + description: heroBanner.image.description, + title: heroBanner.image.title, + url: parseImageUrl(heroBanner.image.url), + } + : undefined + return { + __typename: heroBanner.__typename, + containerMarginBottom: heroBanner.containerMarginBottom?.toString(), + containerMarginTop: heroBanner.containerMarginTop?.toString(), + containerSize: heroBanner.containerSize, + content: heroBanner.content, + ctaAlphaHref: heroBanner.ctaAlphaHref, + ctaAlphaLabel: heroBanner.ctaAlphaLabel, + id: heroBanner.id, + imageDesktop: image, + imageMobile: image, + inverted: false, + isFullScreen: false, + isLazy: false, + title: heroBanner.title, + } +} + +const transformCTABanner = (ctaBanner: ConsciaCTABanner): BannerFullProps => { + const image = ctaBanner.image + ? { + title: ctaBanner.image.title ?? '', + url: parseImageUrl(ctaBanner.image.url), + description: '', + } + : undefined + return { + __typename: ctaBanner.__typename, + containerMarginBottom: ctaBanner.containerMarginBottom?.toString(), + containerMarginTop: ctaBanner.containerMarginTop?.toString(), + containerSize: ctaBanner.containerSize, + overlayBackground: ctaBanner.overlayBackground, + textPosition: ctaBanner.textPosition, + theme: ctaBanner.theme, + id: ctaBanner.id, + content: ctaBanner.content, + title: ctaBanner.title, + ctaAlphaLabel: ctaBanner.ctaAlphaLabel, + ctaAlphaHref: ctaBanner.ctaAlphaHref, + imageDesktop: image, + imageMobile: image, + linkHref1: ctaBanner.linkHref1, + linkLabel1: ctaBanner.linkLabel1, + } +} + +const transformFeatureCardsHeader = ( + featureCardsHeader: ConsciaFeatureCardsHeader +): BannerTextOnlyProps => { + return { + __typename: featureCardsHeader.__typename, + centered: featureCardsHeader.centered, + id: featureCardsHeader.id, + content: featureCardsHeader.content, + title: featureCardsHeader.title, + ctaAlphaLabel: featureCardsHeader.ctaAlphaLabel, + ctaAlphaHref: featureCardsHeader.ctaAlphaHref, + } +} + +const transformFeaturedProducts = ( + featuredProducts: ConsciaFeaturedProducts +): CommerceConnectorProps => { + return { + __typename: featuredProducts.__typename, + id: featuredProducts.id, + title: featuredProducts.title, + containerMarginBottom: featuredProducts.containerMarginBottom?.toString(), + containerMarginTop: featuredProducts.containerMarginTop?.toString(), + containerSize: featuredProducts.containerSize, + ctaLabel: featuredProducts.ctaLabel, + ctaHref: featuredProducts.ctaHref, + products: featuredProducts.products.map((product) => ({ + ...product, + img: product.image, + })), + productListType: 'id', + } +} + +const transformGrid = (grid: ConsciaGrid): GridProps => { + return { + __typename: grid.__typename, + id: grid.id, + columns: grid.columns, + containerMarginBottom: grid.containerMarginBottom?.toString(), + containerMarginTop: grid.containerMarginTop?.toString(), + gridGap: grid.gridGap, + containerSize: grid.containerSize, + items: grid.items.map((item) => { + switch (item.__typename) { + case 'TextCard': + return transformTextCard(item) + } + }), + } +} + +const transformTextCard = (textCard: ConsciaTextCard): TextCardProps => { + return { + __typename: textCard.__typename, + id: textCard.id, + title: textCard.title, + image: { + title: textCard.image.title, + url: parseImageUrl(textCard.image.url), + description: '', + }, + content: textCard.content, + ctaLabel: textCard.ctaLabel, + ctaHref: textCard.ctaHref, + theme: textCard.theme, + textAlign: textCard.textAlign, + } +} diff --git a/packages/conscia/tsconfig.json b/packages/conscia/tsconfig.json new file mode 100644 index 0000000..fd9038d --- /dev/null +++ b/packages/conscia/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "tsconfig/react-library.json", + "include": ["."], + "exclude": [".turbo", "dist", "tmp", "node_modules", "tsconfig.tsbuildinfo"] +} diff --git a/packages/types/package.json b/packages/types/package.json index fadaa60..cffad06 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -8,6 +8,9 @@ "lint": "eslint \"**/*.{js,ts,tsx}\" --max-warnings 0", "ts": "tsc --noEmit --incremental" }, + "dependencies": { + "zod": "^3.20.2" + }, "devDependencies": { "eslint-config-custom": "workspace:*", "tsconfig": "workspace:*", diff --git a/packages/types/src/cms/components/article-card.ts b/packages/types/src/cms/components/article-card.ts new file mode 100644 index 0000000..c3533e2 --- /dev/null +++ b/packages/types/src/cms/components/article-card.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' +import { ImageSchema } from './fields' + +export const ArticleCardSchema = z.object({ + __typename: z.literal('ArticleCard'), + containerMarginBottom: z.string().optional(), + containerMarginTop: z.string().optional(), + containerSize: z.string().optional(), + content: z.string(), + eyebrow: z.string().optional(), + href: z.string(), + id: z.string(), + image: ImageSchema, + textAlign: z.string(), + title: z.string(), +}) + +export type ArticleCardProps = z.infer diff --git a/packages/types/src/cms/components/banner-full.ts b/packages/types/src/cms/components/banner-full.ts new file mode 100644 index 0000000..0e27224 --- /dev/null +++ b/packages/types/src/cms/components/banner-full.ts @@ -0,0 +1,36 @@ +import { z } from 'zod' +import { ImageSchema } from './fields' + +export const BannerFullSchema = z.object({ + __typename: z.literal('BannerFull').optional(), + containerMarginBottom: z.string().nullish(), + containerMarginTop: z.string().nullish(), + containerSize: z.string().nullish(), + content: z.string(), + ctaAlphaHref: z.string().nullish(), + ctaAlphaLabel: z.string(), + ctaBetaHref: z.string().nullish(), + ctaBetaLabel: z.string().nullish(), + eyebrow: z.string().nullish(), + id: z.string(), + imageDesktop: ImageSchema.nullish(), + imageMobile: ImageSchema.nullish(), + linkHref1: z.string().nullish(), + linkHref2: z.string().nullish(), + linkHref3: z.string().nullish(), + linkHref4: z.string().nullish(), + linkHref5: z.string().nullish(), + linkHref6: z.string().nullish(), + linkLabel1: z.string().nullish(), + linkLabel2: z.string().nullish(), + linkLabel3: z.string().nullish(), + linkLabel4: z.string().nullish(), + linkLabel5: z.string().nullish(), + linkLabel6: z.string().nullish(), + overlayBackground: z.string().nullish(), + textPosition: z.string(), + theme: z.string(), + title: z.string(), +}) + +export type BannerFullProps = z.infer diff --git a/packages/types/src/cms/components/banner-split.ts b/packages/types/src/cms/components/banner-split.ts new file mode 100644 index 0000000..60cb698 --- /dev/null +++ b/packages/types/src/cms/components/banner-split.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' +import { ImageSchema } from './fields' + +export const BannerSplitSchema = z.object({ + __typename: z.literal('BannerSplit').optional(), + containerMarginBottom: z.string().nullish(), + containerMarginTop: z.string().nullish(), + containerSize: z.string().nullish(), + content: z.string(), + ctaAlphaHref: z.string().nullish(), + ctaAlphaLabel: z.string(), + eyebrow: z.string().nullish(), + id: z.string(), + imageDesktop: ImageSchema.nullish(), + imageMobile: ImageSchema.nullish(), + inverted: z.boolean(), + isFullScreen: z.boolean(), + isLazy: z.boolean(), + title: z.string(), +}) + +export type BannerSplitProps = z.infer diff --git a/packages/types/src/cms/components/banner-text-only.ts b/packages/types/src/cms/components/banner-text-only.ts new file mode 100644 index 0000000..8963875 --- /dev/null +++ b/packages/types/src/cms/components/banner-text-only.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' + +export const BannerTextOnlySchema = z.object({ + __typename: z.literal('BannerTextOnly').optional(), + centered: z.boolean().optional(), + containerMarginBottom: z.string().nullish(), + containerMarginTop: z.string().nullish(), + containerSize: z.string().nullish(), + content: z.string(), + ctaAlphaHref: z.string().nullish(), + ctaAlphaLabel: z.string(), + eyebrow: z.string().nullish(), + id: z.string(), + minHeight: z.string().nullish(), + title: z.string(), +}) + +export type BannerTextOnlyProps = z.infer diff --git a/packages/types/src/cms/components/commerce-connector.ts b/packages/types/src/cms/components/commerce-connector.ts new file mode 100644 index 0000000..cc3eb56 --- /dev/null +++ b/packages/types/src/cms/components/commerce-connector.ts @@ -0,0 +1,44 @@ +import { z } from 'zod' + +const PriceSchema = z.object({ + current: z.number(), + currentFormatted: z.string(), + regular: z.number().optional(), + regularFormatted: z.string().optional(), +}) + +export type PriceProps = z.infer + +const CommerceProductsSchema = z.object({ + brand: z.string().nullish(), + id: z.string(), + img: z + .object({ + url: z.string().optional(), + alt: z.string().optional(), + }) + .optional(), + name: z.string(), + price: PriceSchema.optional(), + slug: z.string(), +}) + +export type CommerceProduct = z.infer + +export const CommerceConnectorSchema = z.object({ + __typename: z.literal('CommerceConnector'), + containerMarginBottom: z.string().optional(), + containerMarginTop: z.string().optional(), + containerSize: z.string().optional(), + ctaHref: z.string().nullish(), + ctaLabel: z.string().nullish(), + ctaHeight: z.string().nullish(), + ctaMaxWidth: z.string().nullish(), + ctaMinWidth: z.string().nullish(), + id: z.string(), + productListType: z.union([z.literal('id'), z.literal('sku')]).optional(), + products: z.array(CommerceProductsSchema), + title: z.string().nullish(), +}) + +export type CommerceConnectorProps = z.infer diff --git a/packages/types/src/cms/components/cover-card.ts b/packages/types/src/cms/components/cover-card.ts new file mode 100644 index 0000000..43f0932 --- /dev/null +++ b/packages/types/src/cms/components/cover-card.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' +import { ImageSchema } from './fields' + +export const CoverCardSchema = z.object({ + __typename: z.literal('CoverCard'), + containerMarginBottom: z.string().nullish(), + containerMarginTop: z.string().nullish(), + containerSize: z.string().nullish(), + content: z.string(), + eyebrow: z.string().nullish(), + href: z.string(), + id: z.string(), + image: ImageSchema, + textAlign: z.string(), + theme: z.string(), + title: z.string(), +}) + +export type CoverCardProps = z.infer diff --git a/packages/types/src/cms/components/fields.ts b/packages/types/src/cms/components/fields.ts new file mode 100644 index 0000000..67f865f --- /dev/null +++ b/packages/types/src/cms/components/fields.ts @@ -0,0 +1,10 @@ +import { z } from 'zod' + +export const ImageSchema = z.object({ + description: z.string(), + title: z.string(), + url: z.string(), + minHeight: z.string().optional(), +}) + +export type ImageProps = z.infer diff --git a/packages/types/src/cms/components/grid.ts b/packages/types/src/cms/components/grid.ts new file mode 100644 index 0000000..c3c557a --- /dev/null +++ b/packages/types/src/cms/components/grid.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' +import { ArticleCardSchema } from './article-card' +import { CoverCardSchema } from './cover-card' +import { TextCardSchema } from './text-card' + +export const GridItemSchema = z.union([ + ArticleCardSchema, + CoverCardSchema, + TextCardSchema, +]) + +export type GridItem = z.infer + +export const GridSchema = z.object({ + __typename: z.literal('Grid'), + columns: z.number(), + containerMarginBottom: z.string().or(z.number()).nullable().optional(), + containerMarginTop: z.string().or(z.number()).nullable().optional(), + containerSize: z.string().nullable().optional(), + gridGap: z.number().optional(), + id: z.string(), + items: z.array(GridItemSchema), +}) + +export type GridProps = z.infer diff --git a/packages/types/src/cms/components/index.ts b/packages/types/src/cms/components/index.ts new file mode 100644 index 0000000..7f465a4 --- /dev/null +++ b/packages/types/src/cms/components/index.ts @@ -0,0 +1,8 @@ +export * from './banner-full' +export * from './banner-split' +export * from './banner-text-only' +export * from './commerce-connector' +export * from './cover-card' +export * from './fields' +export * from './grid' +export * from './text-card' diff --git a/packages/types/src/cms/components/text-card.ts b/packages/types/src/cms/components/text-card.ts new file mode 100644 index 0000000..4a8d197 --- /dev/null +++ b/packages/types/src/cms/components/text-card.ts @@ -0,0 +1,16 @@ +import { z } from 'zod' +import { ImageSchema } from './fields' + +export const TextCardSchema = z.object({ + __typename: z.literal('TextCard'), + content: z.string(), + ctaHref: z.string().nullish(), + ctaLabel: z.string().nullish(), + id: z.string(), + image: ImageSchema, + textAlign: z.string(), + theme: z.string(), + title: z.string(), +}) + +export type TextCardProps = z.infer diff --git a/packages/types/src/cms/index.ts b/packages/types/src/cms/index.ts index 71c50f4..c1dd753 100644 --- a/packages/types/src/cms/index.ts +++ b/packages/types/src/cms/index.ts @@ -1,174 +1,2 @@ -export interface ImageProps { - description: string - title: string - url: string - minHeight?: string -} - -export interface BannerSplitProps { - __typename?: 'BannerSplit' - containerMarginBottom?: string | null - containerMarginTop?: string | null - containerSize?: string | null - content: string - ctaAlphaHref?: string | null - ctaAlphaLabel: string - eyebrow?: string | null - id: string - imageDesktop?: ImageProps | null - imageMobile?: ImageProps | null - inverted: boolean - isFullScreen: boolean - isLazy: boolean - title: string -} - -export interface BannerFullProps { - __typename?: 'BannerFull' - containerMarginBottom?: string | null - containerMarginTop?: string | null - containerSize?: string | null - content: string - ctaAlphaHref?: string | null - ctaAlphaLabel: string - ctaBetaHref?: string | null - ctaBetaLabel?: string | null - eyebrow?: string | null - id: string - imageDesktop: ImageProps - imageMobile: ImageProps - linkHref1?: string | null - linkHref2?: string | null - linkHref3?: string | null - linkHref4?: string | null - linkHref5?: string | null - linkHref6?: string | null - linkLabel1?: string | null - linkLabel2?: string | null - linkLabel3?: string | null - linkLabel4?: string | null - linkLabel5?: string | null - linkLabel6?: string | null - overlayBackground?: string | null - textPosition: string - theme: string - title: string -} - -export interface BannerTextOnlyProps { - __typename?: 'BannerTextOnly' - centered: boolean - containerMarginBottom?: string | null - containerMarginTop?: string | null - containerSize?: string | null - content: string - ctaAlphaHref?: string | null - ctaAlphaLabel: string - eyebrow?: string | null - id: string - minHeight?: string | null - title: string -} - -export interface CoverCardProps { - __typename: 'CoverCard' - containerMarginBottom?: string | null - containerMarginTop?: string | null - containerSize?: string | null - content: string - eyebrow?: string | null - href: string - id: string - image: ImageProps - textAlign: string - theme: string - title: string -} - -export interface TextCardProps { - __typename: 'TextCard' - content: string - ctaHref?: string | null - ctaLabel: string - id: string - image: ImageProps - textAlign: string - theme: string - title: string -} - -export interface ArticleCardProps { - __typename: 'ArticleCard' - containerMarginBottom?: string - containerMarginTop?: string - containerSize?: string - content: string - eyebrow?: string - href: string - id: string - image: ImageProps - textAlign: string - title: string -} - -export interface PriceProps { - current: number - currentFormatted: string - regular?: number - regularFormatted?: string -} - -interface CommerceProducts { - brand?: string - id: string - img?: { - url?: string - alt?: string - } - name: string - price?: PriceProps - slug: string -} - -interface CommerceConnectorProps { - __typename: 'CommerceConnector' - containerMarginBottom?: string - containerMarginTop?: string - containerSize?: string - ctaHref: string - ctaLabel: string - id: string - productListType: 'id' | 'sku' - products: CommerceProducts[] - title: string -} - -export type PageItem = - | BannerFullProps - | BannerSplitProps - | BannerTextOnlyProps - | CommerceConnectorProps - | GridProps - -export interface PageProps { - __typename: string - id: string - items: PageItem[] - metaDescription: string - metaKeywords: string[] - metaTitle: string - slug: string -} - -type GridItem = ArticleCardProps | CoverCardProps | TextCardProps - -export interface GridProps { - __typename: 'Grid' - columns: number - containerMarginBottom?: string - containerMarginTop?: string - containerSize?: string - gridGap?: string - id: string - items: GridItem[] -} +export * from './components' +export * from './page' diff --git a/packages/types/src/cms/page.ts b/packages/types/src/cms/page.ts new file mode 100644 index 0000000..4346ea3 --- /dev/null +++ b/packages/types/src/cms/page.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' +import { + BannerFullSchema, + BannerSplitSchema, + BannerTextOnlySchema, + CommerceConnectorSchema, + GridSchema, +} from './components' + +export const PageItemSchema = z.union([ + BannerFullSchema, + BannerSplitSchema, + BannerTextOnlySchema, + CommerceConnectorSchema, + GridSchema, +]) + +export type PageItem = z.infer + +export const PageSchema = z.object({ + __typename: z.string(), + id: z.string(), + items: z.array(PageItemSchema), + metaDescription: z.string(), + metaKeywords: z.array(z.string()), + metaTitle: z.string(), + slug: z.string(), +}) + +export type PageProps = z.infer diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24f87e9..dca57dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,6 +225,28 @@ importers: specifier: ^4.5.5 version: 4.9.5 + packages/conscia: + dependencies: + '@composable/types': + specifier: workspace:* + version: link:../types + axios: + specifier: 0.26.1 + version: 0.26.1 + devDependencies: + '@types/node': + specifier: ^18.6.3 + version: 18.15.11 + eslint-config-custom: + specifier: workspace:* + version: link:../eslint-config-custom + tsconfig: + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: ^4.5.5 + version: 4.9.5 + packages/eslint-config-custom: dependencies: eslint: @@ -317,6 +339,10 @@ importers: packages/tsconfig: {} packages/types: + dependencies: + zod: + specifier: ^3.20.2 + version: 3.21.4 devDependencies: eslint-config-custom: specifier: workspace:* @@ -7407,7 +7433,7 @@ packages: /axios@0.26.1: resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==} dependencies: - follow-redirects: 1.15.2 + follow-redirects: 1.15.5 transitivePeerDependencies: - debug dev: false @@ -7415,7 +7441,7 @@ packages: /axios@0.27.2: resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} dependencies: - follow-redirects: 1.15.2 + follow-redirects: 1.15.5 form-data: 4.0.0 transitivePeerDependencies: - debug @@ -10206,6 +10232,7 @@ packages: peerDependenciesMeta: debug: optional: true + dev: true /follow-redirects@1.15.5: resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} @@ -10215,7 +10242,6 @@ packages: peerDependenciesMeta: debug: optional: true - dev: false /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} From 5a88459d3424b6d4352dc98310206cdc605cf240 Mon Sep 17 00:00:00 2001 From: federicodelpiano Date: Fri, 8 Mar 2024 17:20:01 -0300 Subject: [PATCH 2/3] Update index.ts --- packages/types/src/cms/components/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/cms/components/index.ts b/packages/types/src/cms/components/index.ts index 7f465a4..1b5d592 100644 --- a/packages/types/src/cms/components/index.ts +++ b/packages/types/src/cms/components/index.ts @@ -1,3 +1,4 @@ +export * from './article-card' export * from './banner-full' export * from './banner-split' export * from './banner-text-only' From 6034cc6e4a74caa568dfd3f7d4a836f6979dc0aa Mon Sep 17 00:00:00 2001 From: Danny Lake Date: Mon, 11 Mar 2024 14:38:10 -0700 Subject: [PATCH 3/3] docs: updated README for conscia package --- packages/conscia/README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/conscia/README.md b/packages/conscia/README.md index b367495..a4be448 100644 --- a/packages/conscia/README.md +++ b/packages/conscia/README.md @@ -18,8 +18,20 @@ NEXT_PUBLIC_CONSCIA_TOKEN= NEXT_PUBLIC_CONSCIA_CUSTOMER_CODE= ``` -1. **Replace the getPage import for the @composable/conscia one:** +1. **Update your storefront to use the Conscia data fetching service:** + +The `cmsRouter`, defined in `composable-ui/src/server/api/routers/cms.ts`, provides your storefront with a data fetching function called `getPage`, which retrieves the data that is used to populate the content on your storefront. By default this function returns data retrieved from a local file. + +In order to use data from Conscia, the `cmsRouter` needs to use the `getPage` data fetching service defined in `@composable/conscia`, instead of from `@composable/cms-generic`. Change the code as follows: + + ```javascript + // cms.ts - before changes + import { getPage } from '@composable/cms-generic' + +... + + // cms.ts - after changes import { getPage } from '@composable/conscia' ```