mirror of
https://github.com/vercel/commerce.git
synced 2025-05-13 05:07:51 +00:00
feat: implement accordion content type
Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
parent
a1d65a54c1
commit
e0da620ac9
@ -1,12 +1,8 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
import IconWithTextBlock, { IconBlockPlaceholder } from 'components/page/icon-with-text-block';
|
import PageContent from 'components/page/page-content';
|
||||||
import ImageWithTextBlock from 'components/page/image-with-text-block';
|
import { getPage } from 'lib/shopify';
|
||||||
import TextBlock from 'components/page/text-block';
|
|
||||||
import { getPage, getPageMetaObjects } from 'lib/shopify';
|
|
||||||
import { PageContent, PageMetafieldKey } from 'lib/shopify/types';
|
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { Suspense } from 'react';
|
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
@ -30,29 +26,11 @@ export async function generateMetadata({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
const contentMap: Record<PageMetafieldKey, (content: PageContent) => JSX.Element> = {
|
|
||||||
page_icon_section: (content) => (
|
|
||||||
<Suspense fallback={<IconBlockPlaceholder />}>
|
|
||||||
<IconWithTextBlock content={content} />
|
|
||||||
</Suspense>
|
|
||||||
),
|
|
||||||
page_image_content: (content) => <ImageWithTextBlock content={content} />,
|
|
||||||
page_section: (content) => <TextBlock content={content} />
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function Page({ params }: { params: { page: string } }) {
|
export default async function Page({ params }: { params: { page: string } }) {
|
||||||
const page = await getPage(params.page);
|
const page = await getPage(params.page);
|
||||||
|
|
||||||
if (!page) return notFound();
|
if (!page) return notFound();
|
||||||
|
|
||||||
const pageContents = (
|
|
||||||
await Promise.allSettled(page.metafields.map((metafield) => getPageMetaObjects(metafield)))
|
|
||||||
)
|
|
||||||
.filter((result) => result.status === 'fulfilled')
|
|
||||||
.map((result) => (result as PromiseFulfilledResult<PageContent | null>).value)
|
|
||||||
.filter(Boolean) as PageContent[];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mx-auto mb-2 max-w-7xl px-4 sm:px-6 lg:px-8">
|
<div className="mx-auto mb-2 max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||||
@ -63,8 +41,10 @@ export default async function Page({ params }: { params: { page: string } }) {
|
|||||||
<main>
|
<main>
|
||||||
<div className="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
|
<div className="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
|
||||||
<div className="flex flex-col space-y-16">
|
<div className="flex flex-col space-y-16">
|
||||||
{pageContents.map((content) => (
|
{page.metaobjects?.map((content) => (
|
||||||
<div key={content.id}>{contentMap[content.key](content)}</div>
|
<div key={content.id}>
|
||||||
|
<PageContent block={content} />
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
23
components/page/accordion-block-item.tsx
Normal file
23
components/page/accordion-block-item.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/react';
|
||||||
|
import { ChevronDownIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
const AccordionBlockItem = ({ title, children }: { title: string; children: ReactNode }) => {
|
||||||
|
return (
|
||||||
|
<Disclosure as="div" className="pt-6">
|
||||||
|
<dt>
|
||||||
|
<DisclosureButton className="group flex w-full items-start justify-between text-left text-gray-900">
|
||||||
|
<span className="text-lg font-semibold leading-7">{title}</span>
|
||||||
|
<ChevronDownIcon className="size-5 group-data-[open]:rotate-180" />
|
||||||
|
</DisclosureButton>
|
||||||
|
</dt>
|
||||||
|
<DisclosurePanel as="dd" className="mt-2 flex flex-col gap-4 py-4 text-base text-gray-800">
|
||||||
|
{children}
|
||||||
|
</DisclosurePanel>
|
||||||
|
</Disclosure>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccordionBlockItem;
|
39
components/page/accordion-block.tsx
Normal file
39
components/page/accordion-block.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { getMetaobjectById, getMetaobjectsByIds } from 'lib/shopify';
|
||||||
|
import { Metaobject } from 'lib/shopify/types';
|
||||||
|
import AccordionBlockItem from './accordion-block-item';
|
||||||
|
import PageContent from './page-content';
|
||||||
|
|
||||||
|
const AccordionItem = async ({ id }: { id: string }) => {
|
||||||
|
const accordionObject = await getMetaobjectById(id);
|
||||||
|
|
||||||
|
if (!accordionObject) return null;
|
||||||
|
|
||||||
|
const content = await getMetaobjectsByIds(JSON.parse(accordionObject.accordion_content || '[]'));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccordionBlockItem title={accordionObject.title || 'Section Title'}>
|
||||||
|
{content.map((block) => (
|
||||||
|
<PageContent block={block} key={block.id} />
|
||||||
|
))}
|
||||||
|
</AccordionBlockItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AccordionBlock = async ({ block }: { block: Metaobject }) => {
|
||||||
|
const accordionItemIds = JSON.parse(block.accordion || '[]') as string[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="divide-y divide-gray-900/10">
|
||||||
|
{block.title && (
|
||||||
|
<h3 className="mb-7 text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
|
||||||
|
)}
|
||||||
|
<dl className="w-full space-y-6 divide-y divide-gray-900/10">
|
||||||
|
{accordionItemIds.map((id) => (
|
||||||
|
<AccordionItem key={id} id={id} />
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccordionBlock;
|
@ -1,7 +1,7 @@
|
|||||||
import Grid from 'components/grid';
|
import Grid from 'components/grid';
|
||||||
import DynamicHeroIcon from 'components/hero-icon';
|
import DynamicHeroIcon from 'components/hero-icon';
|
||||||
import { getMetaobjects, getMetaobjectsByIds } from 'lib/shopify';
|
import { getMetaobjects, getMetaobjectsByIds } from 'lib/shopify';
|
||||||
import { PageContent, ScreenSize } from 'lib/shopify/types';
|
import { Metaobject, ScreenSize } from 'lib/shopify/types';
|
||||||
|
|
||||||
export const IconBlockPlaceholder = () => {
|
export const IconBlockPlaceholder = () => {
|
||||||
return (
|
return (
|
||||||
@ -13,15 +13,10 @@ export const IconBlockPlaceholder = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const IconWithTextBlock = async ({ content }: { content: PageContent }) => {
|
const IconWithTextBlock = async ({ block }: { block: Metaobject }) => {
|
||||||
// for icon with text content, we only need the first metaobject as the array always contains only one element due to the metafield definition set up on Shopify
|
|
||||||
const metaobject = content.metaobjects[0];
|
|
||||||
|
|
||||||
if (!metaobject) return null;
|
|
||||||
|
|
||||||
const [contentBlocks, layouts, screenSizes] = await Promise.all([
|
const [contentBlocks, layouts, screenSizes] = await Promise.all([
|
||||||
getMetaobjectsByIds(metaobject.content ? JSON.parse(metaobject.content) : []),
|
getMetaobjectsByIds(block.content ? JSON.parse(block.content) : []),
|
||||||
getMetaobjectsByIds(metaobject.layouts ? JSON.parse(metaobject.layouts) : []),
|
getMetaobjectsByIds(block.layouts ? JSON.parse(block.layouts) : []),
|
||||||
getMetaobjects('screen_sizes')
|
getMetaobjects('screen_sizes')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -75,15 +70,18 @@ const IconWithTextBlock = async ({ content }: { content: PageContent }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-5 px-4 md:px-0">
|
<div className="flex flex-col gap-5 px-4 md:px-0">
|
||||||
<h3 className="text-xl font-semibold leading-6 text-gray-900">{metaobject.title}</h3>
|
{block.title ? (
|
||||||
|
<h3 className="text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Grid className={validClassnames}>
|
<Grid className={validClassnames}>
|
||||||
{contentBlocks.map((block) => (
|
{contentBlocks.map((block) => (
|
||||||
<Grid.Item key={block.id} className="flex flex-col gap-2">
|
<Grid.Item key={block.id} className="flex flex-col gap-2">
|
||||||
{block.icon_name && (
|
{block.icon_name && (
|
||||||
<DynamicHeroIcon icon={block.icon_name} className="w-16 text-secondary" />
|
<DynamicHeroIcon icon={block.icon_name} className="w-16 text-secondary" />
|
||||||
)}
|
)}
|
||||||
<div className="text-lg font-medium">{block.title}</div>
|
{block.title && <div className="text-lg font-medium">{block.title}</div>}
|
||||||
<p className="text-base text-gray-800">{block.content}</p>
|
{block.content && <p className="text-base text-gray-800">{block.content}</p>}
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -1,35 +1,34 @@
|
|||||||
import { PageContent } from 'lib/shopify/types';
|
import { Metaobject } from 'lib/shopify/types';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import ImageDisplay from './image-display';
|
import ImageDisplay from './image-display';
|
||||||
import RichTextDisplay from './rich-text-display';
|
import RichTextDisplay from './rich-text-display';
|
||||||
|
|
||||||
const ImageWithTextBlock = ({ content }: { content: PageContent }) => {
|
const ImageWithTextBlock = ({ block }: { block: Metaobject }) => {
|
||||||
if (!content.metaobjects.length) return null;
|
const description = block.description ? JSON.parse(block.description) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-10">
|
<div className="flex flex-col gap-6 px-4 md:px-0">
|
||||||
{content.metaobjects.map((metaobject) => {
|
{block.title && (
|
||||||
const contentBlocks = JSON.parse(metaobject.description || '{}');
|
<h3 className="text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
|
||||||
|
)}
|
||||||
return (
|
{description ? (
|
||||||
<div className="flex flex-col gap-6 px-4 md:px-0" key={metaobject.id}>
|
|
||||||
<h3 className="text-xl font-semibold leading-6 text-gray-900">{metaobject.title}</h3>
|
|
||||||
<div className="grid grid-cols-1 gap-5 md:grid-cols-3">
|
<div className="grid grid-cols-1 gap-5 md:grid-cols-3">
|
||||||
<div className="relative col-span-1">
|
<div className="relative col-span-1">
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ImageDisplay
|
<ImageDisplay title={block.title || 'Image Preview'} fileId={block.file as string} />
|
||||||
title={metaobject.title as string}
|
|
||||||
fileId={metaobject.file as string}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<RichTextDisplay contentBlocks={contentBlocks.children} />
|
<RichTextDisplay contentBlocks={description.children} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="relative w-full">
|
||||||
|
<Suspense>
|
||||||
|
<ImageDisplay title={block.title || 'Image Preview'} fileId={block.file as string} />
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
)}
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
24
components/page/page-content.tsx
Normal file
24
components/page/page-content.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Metaobject, PageType } from 'lib/shopify/types';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
import AccordionBlock from './accordion-block';
|
||||||
|
import IconWithTextBlock, { IconBlockPlaceholder } from './icon-with-text-block';
|
||||||
|
import ImageWithTextBlock from './image-with-text-block';
|
||||||
|
import TextBlock from './text-block';
|
||||||
|
|
||||||
|
const PageContent = ({ block }: { block: Metaobject }) => {
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const contentMap: Record<PageType, (block: Metaobject) => JSX.Element> = {
|
||||||
|
icon_content_section: (block) => (
|
||||||
|
<Suspense fallback={<IconBlockPlaceholder />}>
|
||||||
|
<IconWithTextBlock block={block} />
|
||||||
|
</Suspense>
|
||||||
|
),
|
||||||
|
image: (block) => <ImageWithTextBlock block={block} />,
|
||||||
|
page_section: (block) => <TextBlock block={block} />,
|
||||||
|
accordion: (block) => <AccordionBlock block={block} />
|
||||||
|
};
|
||||||
|
|
||||||
|
return contentMap[block.type as PageType](block);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageContent;
|
@ -1,10 +1,18 @@
|
|||||||
type Content =
|
type Text = {
|
||||||
| { type: 'paragraph'; children: Array<{ type: 'text'; value: string; bold?: boolean }> }
|
|
||||||
| {
|
|
||||||
type: 'text';
|
type: 'text';
|
||||||
value: string;
|
value: string;
|
||||||
bold?: boolean;
|
bold?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Content =
|
||||||
|
| { type: 'paragraph'; children: Text[] }
|
||||||
|
| Text
|
||||||
|
| {
|
||||||
|
type: 'list';
|
||||||
|
listType: 'bullet' | 'ordered';
|
||||||
|
children: Array<{ type: 'listItem'; children: Text[] }>;
|
||||||
|
}
|
||||||
|
| { type: 'listItem'; children: Text[] };
|
||||||
|
|
||||||
const RichTextBlock = ({ block }: { block: Content }) => {
|
const RichTextBlock = ({ block }: { block: Content }) => {
|
||||||
if (block.type === 'text') {
|
if (block.type === 'text') {
|
||||||
@ -15,6 +23,22 @@ const RichTextBlock = ({ block }: { block: Content }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (block.type === 'listItem') {
|
||||||
|
return block.children.map((child, index) => <RichTextBlock key={index} block={child} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.type === 'list' && block.listType === 'ordered') {
|
||||||
|
return (
|
||||||
|
<ol className="ml-10 list-decimal">
|
||||||
|
{block.children.map((child, index) => (
|
||||||
|
<li key={index}>
|
||||||
|
<RichTextBlock block={child} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p className="text-gray-800">
|
<p className="text-gray-800">
|
||||||
{block.children.map((child, index) => (
|
{block.children.map((child, index) => (
|
||||||
|
@ -1,21 +1,18 @@
|
|||||||
import { PageContent } from 'lib/shopify/types';
|
import { Metaobject } from 'lib/shopify/types';
|
||||||
import RichTextDisplay from './rich-text-display';
|
import RichTextDisplay from './rich-text-display';
|
||||||
|
|
||||||
const TextBlock = ({ content }: { content: PageContent }) => {
|
const TextBlock = ({ block }: { block: Metaobject }) => {
|
||||||
if (!content.metaobjects.length) return null;
|
const content = JSON.parse(block.content || '{}');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-8">
|
||||||
{content.metaobjects.map((metaobject) => {
|
<div className="flex flex-col gap-5 px-4 md:px-0">
|
||||||
const contentBlocks = JSON.parse(metaobject.content || '{}');
|
{block.title && (
|
||||||
|
<h3 className="text-xl font-semibold leading-6 text-gray-900">{block.title}</h3>
|
||||||
|
)}
|
||||||
|
|
||||||
return (
|
<RichTextDisplay contentBlocks={content.children} />
|
||||||
<div className="flex flex-col gap-5 px-4 md:px-0" key={metaobject.id}>
|
|
||||||
<h3 className="text-xl font-semibold leading-6 text-gray-900">{metaobject.title}</h3>
|
|
||||||
<RichTextDisplay contentBlocks={contentBlocks.children} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -28,7 +28,7 @@ import {
|
|||||||
getCollectionsQuery
|
getCollectionsQuery
|
||||||
} from './queries/collection';
|
} from './queries/collection';
|
||||||
import { getMenuQuery } from './queries/menu';
|
import { getMenuQuery } from './queries/menu';
|
||||||
import { getMetaobjectsQuery } from './queries/metaobject';
|
import { getMetaobjectQuery, getMetaobjectsQuery } from './queries/metaobject';
|
||||||
import { getImageQuery, getMetaobjectsByIdsQuery } from './queries/node';
|
import { getImageQuery, getMetaobjectsByIdsQuery } from './queries/node';
|
||||||
import { getPageQuery, getPagesQuery } from './queries/page';
|
import { getPageQuery, getPagesQuery } from './queries/page';
|
||||||
import {
|
import {
|
||||||
@ -45,10 +45,8 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
Metaobject,
|
Metaobject,
|
||||||
Money,
|
Money,
|
||||||
PAGE_TYPES,
|
|
||||||
Page,
|
Page,
|
||||||
PageInfo,
|
PageInfo,
|
||||||
PageMetafield,
|
|
||||||
Product,
|
Product,
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
ShopifyAddToCartOperation,
|
ShopifyAddToCartOperation,
|
||||||
@ -63,8 +61,8 @@ import {
|
|||||||
ShopifyImageOperation,
|
ShopifyImageOperation,
|
||||||
ShopifyMenuOperation,
|
ShopifyMenuOperation,
|
||||||
ShopifyMetaobject,
|
ShopifyMetaobject,
|
||||||
ShopifyMetaobjectOperation,
|
|
||||||
ShopifyMetaobjectsOperation,
|
ShopifyMetaobjectsOperation,
|
||||||
|
ShopifyPage,
|
||||||
ShopifyPageOperation,
|
ShopifyPageOperation,
|
||||||
ShopifyPagesOperation,
|
ShopifyPagesOperation,
|
||||||
ShopifyProduct,
|
ShopifyProduct,
|
||||||
@ -238,7 +236,7 @@ const reshapeFilters = (filters: ShopifyFilter[]): Filter[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const reshapeMetaobjects = (metaobjects: ShopifyMetaobject[]): Metaobject[] => {
|
const reshapeMetaobjects = (metaobjects: ShopifyMetaobject[]): Metaobject[] => {
|
||||||
return metaobjects.map(({ fields, id }) => {
|
return metaobjects.map(({ fields, id, type }) => {
|
||||||
const groupedFieldsByKey = fields.reduce(
|
const groupedFieldsByKey = fields.reduce(
|
||||||
(acc, field) => {
|
(acc, field) => {
|
||||||
return {
|
return {
|
||||||
@ -256,7 +254,7 @@ const reshapeMetaobjects = (metaobjects: ShopifyMetaobject[]): Metaobject[] => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return { id, ...groupedFieldsByKey };
|
return { id, type, ...groupedFieldsByKey };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -498,7 +496,10 @@ export async function getMetaobjects(type: string) {
|
|||||||
export async function getMetaobjectsByIds(ids: string[]) {
|
export async function getMetaobjectsByIds(ids: string[]) {
|
||||||
if (!ids.length) return [];
|
if (!ids.length) return [];
|
||||||
|
|
||||||
const res = await shopifyFetch<ShopifyMetaobjectOperation>({
|
const res = await shopifyFetch<{
|
||||||
|
data: { nodes: ShopifyMetaobject[] };
|
||||||
|
variables: { ids: string[] };
|
||||||
|
}>({
|
||||||
query: getMetaobjectsByIdsQuery,
|
query: getMetaobjectsByIdsQuery,
|
||||||
variables: { ids }
|
variables: { ids }
|
||||||
});
|
});
|
||||||
@ -506,31 +507,39 @@ export async function getMetaobjectsByIds(ids: string[]) {
|
|||||||
return reshapeMetaobjects(res.body.data.nodes);
|
return reshapeMetaobjects(res.body.data.nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPageMetaObjects(metafield: PageMetafield) {
|
export async function getMetaobjectById(id: string) {
|
||||||
let metaobjectIds = parseMetaFieldValue<string | string[]>(metafield) || metafield.value;
|
const res = await shopifyFetch<{
|
||||||
|
data: { metaobject: ShopifyMetaobject };
|
||||||
|
variables: { id: string };
|
||||||
|
}>({
|
||||||
|
query: getMetaobjectQuery,
|
||||||
|
variables: { id }
|
||||||
|
});
|
||||||
|
|
||||||
if (!metaobjectIds) {
|
return res.body.data.metaobject ? reshapeMetaobjects([res.body.data.metaobject])[0] : null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
metaobjectIds = (Array.isArray(metaobjectIds) ? metaobjectIds : [metaobjectIds]) as string[];
|
|
||||||
|
|
||||||
const metaobjects = await getMetaobjectsByIds(metaobjectIds);
|
|
||||||
|
|
||||||
return { metaobjects, id: metafield.id, key: metafield.key };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPage(handle: string): Promise<Page> {
|
export async function getPage(handle: string): Promise<Page> {
|
||||||
const metafieldIdentifiers = PAGE_TYPES.map((key) => ({ key, namespace: 'custom' }));
|
|
||||||
const res = await shopifyFetch<ShopifyPageOperation>({
|
const res = await shopifyFetch<ShopifyPageOperation>({
|
||||||
query: getPageQuery,
|
query: getPageQuery,
|
||||||
variables: { handle, metafieldIdentifiers }
|
variables: { handle, key: 'page_content', namespace: 'custom' }
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.body.data.pageByHandle;
|
const page = res.body.data.pageByHandle;
|
||||||
|
|
||||||
|
if (page.metafield) {
|
||||||
|
const metaobjectIds = parseMetaFieldValue<string[]>(page.metafield) || [];
|
||||||
|
|
||||||
|
const metaobjects = await getMetaobjectsByIds(metaobjectIds);
|
||||||
|
|
||||||
|
const { metafield, ...restPage } = page;
|
||||||
|
return { ...restPage, metaobjects };
|
||||||
|
}
|
||||||
|
|
||||||
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPages(): Promise<Page[]> {
|
export async function getPages(): Promise<ShopifyPage[]> {
|
||||||
const res = await shopifyFetch<ShopifyPagesOperation>({
|
const res = await shopifyFetch<ShopifyPagesOperation>({
|
||||||
query: getPagesQuery
|
query: getPagesQuery
|
||||||
});
|
});
|
||||||
|
@ -18,3 +18,21 @@ export const getMetaobjectsQuery = /* GraphQL */ `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const getMetaobjectQuery = /* GraphQL */ `
|
||||||
|
query getMetaobject($id: ID!) {
|
||||||
|
metaobject(id: $id) {
|
||||||
|
id
|
||||||
|
type
|
||||||
|
fields {
|
||||||
|
reference {
|
||||||
|
... on Metaobject {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -18,6 +18,7 @@ export const getMetaobjectsByIdsQuery = /* GraphQL */ `
|
|||||||
nodes(ids: $ids) {
|
nodes(ids: $ids) {
|
||||||
... on Metaobject {
|
... on Metaobject {
|
||||||
id
|
id
|
||||||
|
type
|
||||||
fields {
|
fields {
|
||||||
reference {
|
reference {
|
||||||
... on Metaobject {
|
... on Metaobject {
|
||||||
|
@ -19,12 +19,11 @@ const pageFragment = /* GraphQL */ `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const getPageQuery = /* GraphQL */ `
|
export const getPageQuery = /* GraphQL */ `
|
||||||
query getPage($handle: String!, $metafieldIdentifiers: [HasMetafieldsIdentifier!]!) {
|
query getPage($handle: String!, $key: String!, $namespace: String) {
|
||||||
pageByHandle(handle: $handle) {
|
pageByHandle(handle: $handle) {
|
||||||
...page
|
...page
|
||||||
metafields(identifiers: $metafieldIdentifiers) {
|
metafield(key: $key, namespace: $namespace) {
|
||||||
value
|
value
|
||||||
key
|
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,14 +53,13 @@ export type Money = {
|
|||||||
|
|
||||||
export type PageMetafield = {
|
export type PageMetafield = {
|
||||||
id: string;
|
id: string;
|
||||||
key: PageMetafieldKey;
|
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PAGE_TYPES = ['page_icon_section', 'page_section', 'page_image_content'] as const;
|
export const PAGE_TYPES = ['image', 'icon_content_section', 'page_section', 'accordion'] as const;
|
||||||
export type PageMetafieldKey = (typeof PAGE_TYPES)[number];
|
export type PageType = (typeof PAGE_TYPES)[number];
|
||||||
|
|
||||||
export type Page = {
|
export type ShopifyPage = {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
handle: string;
|
handle: string;
|
||||||
@ -69,16 +68,15 @@ export type Page = {
|
|||||||
seo?: SEO;
|
seo?: SEO;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
metafields: PageMetafield[];
|
metafield: PageMetafield | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MetafieldIdentifier = {
|
export type Page = Omit<ShopifyPage, 'metafield'> & {
|
||||||
key: string;
|
metaobjects?: Metaobject[];
|
||||||
namespace: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShopifyMetaobject = {
|
export type ShopifyMetaobject = {
|
||||||
id: string;
|
id: string;
|
||||||
|
type: string;
|
||||||
fields: Array<{
|
fields: Array<{
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
@ -90,6 +88,7 @@ export type ShopifyMetaobject = {
|
|||||||
|
|
||||||
export type Metaobject = {
|
export type Metaobject = {
|
||||||
id: string;
|
id: string;
|
||||||
|
type: string;
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -296,8 +295,8 @@ export type ShopifyMenuOperation = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ShopifyPageOperation = {
|
export type ShopifyPageOperation = {
|
||||||
data: { pageByHandle: Page };
|
data: { pageByHandle: ShopifyPage };
|
||||||
variables: { handle: string; metafieldIdentifiers: MetafieldIdentifier[] };
|
variables: { handle: string; key: string; namespace: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShopifyImageOperation = {
|
export type ShopifyImageOperation = {
|
||||||
@ -312,7 +311,7 @@ export type ShopifyMetaobjectsOperation = {
|
|||||||
|
|
||||||
export type ShopifyPagesOperation = {
|
export type ShopifyPagesOperation = {
|
||||||
data: {
|
data: {
|
||||||
pages: Connection<Page>;
|
pages: Connection<ShopifyPage>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -389,12 +388,6 @@ export type Filter = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PageContent = {
|
|
||||||
id: string;
|
|
||||||
key: PageMetafieldKey;
|
|
||||||
metaobjects: Metaobject[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SCREEN_SIZES = ['small', 'medium', 'large', 'extra_large'] as const;
|
export const SCREEN_SIZES = ['small', 'medium', 'large', 'extra_large'] as const;
|
||||||
|
|
||||||
export type ScreenSize = (typeof SCREEN_SIZES)[number];
|
export type ScreenSize = (typeof SCREEN_SIZES)[number];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user