feat: implement text/image-with-text/icon-with-text content block

Signed-off-by: Chloe <pinkcloudvnn@gmail.com>
This commit is contained in:
Chloe
2024-05-23 14:17:55 +07:00
parent 4684d54ac3
commit a1d65a54c1
15 changed files with 431 additions and 24 deletions

17
components/hero-icon.tsx Normal file
View File

@@ -0,0 +1,17 @@
import * as HeroIcons from '@heroicons/react/24/outline';
import startcase from 'lodash.startcase';
type IconName = keyof typeof HeroIcons;
interface IconProps {
icon: string;
className?: string;
}
const DynamicHeroIcon = ({ icon, className }: IconProps) => {
const _icon = startcase(icon).replace(/\s/g, '');
const SingleIcon = HeroIcons[`${_icon}Icon` as IconName];
return SingleIcon ? <SingleIcon className={className} /> : null;
};
export default DynamicHeroIcon;

View File

@@ -53,13 +53,26 @@ export default async function Footer() {
{copyrightName.length && !copyrightName.endsWith('.') ? '.' : ''} All rights reserved.
</p>
<div className="ml-0 flex flex-row items-center gap-2 md:ml-auto">
<Image alt="visa" src="/icons/visa.png" width={30} height={20} />
<Image alt="mastercard" src="/icons/mastercard.png" width={30} height={20} />
<Image
alt="visa"
src="/icons/visa.png"
width={30}
height={30}
className="h-auto w-[30px]"
/>
<Image
alt="mastercard"
src="/icons/mastercard.png"
width={30}
height={30}
className="h-auto w-[30px]"
/>
<Image
alt="american-express"
src="/icons/american-express.png"
width={30}
height={20}
height={30}
className="h-auto w-[30px]"
/>
</div>
</div>

View File

@@ -0,0 +1,94 @@
import Grid from 'components/grid';
import DynamicHeroIcon from 'components/hero-icon';
import { getMetaobjects, getMetaobjectsByIds } from 'lib/shopify';
import { PageContent, ScreenSize } from 'lib/shopify/types';
export const IconBlockPlaceholder = () => {
return (
<div className="flex animate-pulse flex-col gap-5 px-4 md:px-0">
<div className="h-10 w-1/2 rounded bg-gray-200" />
<div className="h-40 w-full rounded bg-gray-200" />
<div className="h-40 w-full rounded bg-gray-200" />
</div>
);
};
const IconWithTextBlock = async ({ content }: { content: PageContent }) => {
// 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([
getMetaobjectsByIds(metaobject.content ? JSON.parse(metaobject.content) : []),
getMetaobjectsByIds(metaobject.layouts ? JSON.parse(metaobject.layouts) : []),
getMetaobjects('screen_sizes')
]);
const availableLayouts = layouts.reduce(
(acc, layout) => {
const screenSize = screenSizes.find((screen) => screen.id === layout.screen_size);
if (screenSize?.size) {
acc[screenSize.size.toLowerCase() as ScreenSize] = Number(layout.number_of_columns);
}
return acc;
},
{} as Record<ScreenSize, number>
);
let classnames = {} as { [key: string]: boolean };
if (availableLayouts.small) {
classnames = {
...classnames,
'sm:grid-cols-1': availableLayouts.small === 1,
'sm:grid-cols-2': availableLayouts.small === 2,
'sm:grid-cols-3': availableLayouts.small === 3,
'sm:grid-cols-4': availableLayouts.small === 4
};
}
if (availableLayouts.medium) {
classnames = {
...classnames,
'md:grid-cols-1': availableLayouts.medium === 1,
'md:grid-cols-2': availableLayouts.medium === 2,
'md:grid-cols-3': availableLayouts.medium === 3,
'md:grid-cols-4': availableLayouts.medium === 4
};
}
if (availableLayouts.large) {
classnames = {
...classnames,
'lg:grid-cols-1': availableLayouts.large === 1,
'lg:grid-cols-2': availableLayouts.large === 2,
'lg:grid-cols-3': availableLayouts.large === 3,
'lg:grid-cols-4': availableLayouts.large === 4
};
}
const validClassnames = Object.keys(classnames)
.filter((key) => classnames[key])
.join(' ');
return (
<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>
<Grid className={validClassnames}>
{contentBlocks.map((block) => (
<Grid.Item key={block.id} className="flex flex-col gap-2">
{block.icon_name && (
<DynamicHeroIcon icon={block.icon_name} className="w-16 text-secondary" />
)}
<div className="text-lg font-medium">{block.title}</div>
<p className="text-base text-gray-800">{block.content}</p>
</Grid.Item>
))}
</Grid>
</div>
);
};
export default IconWithTextBlock;

View File

@@ -0,0 +1,17 @@
import { getImage } from 'lib/shopify';
import Image from 'next/image';
const ImageDisplay = async ({ fileId, title }: { fileId: string; title: string }) => {
const image = await getImage(fileId);
return (
<Image
src={image.url}
alt={image.altText || `Display Image for ${title} section`}
width={image.width}
height={image.height}
className="h-full w-full object-contain"
/>
);
};
export default ImageDisplay;

View File

@@ -0,0 +1,37 @@
import { PageContent } from 'lib/shopify/types';
import { Suspense } from 'react';
import ImageDisplay from './image-display';
import RichTextDisplay from './rich-text-display';
const ImageWithTextBlock = ({ content }: { content: PageContent }) => {
if (!content.metaobjects.length) return null;
return (
<div className="flex flex-col gap-10">
{content.metaobjects.map((metaobject) => {
const contentBlocks = JSON.parse(metaobject.description || '{}');
return (
<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="relative col-span-1">
<Suspense>
<ImageDisplay
title={metaobject.title as string}
fileId={metaobject.file as string}
/>
</Suspense>
</div>
<div className="col-span-2">
<RichTextDisplay contentBlocks={contentBlocks.children} />
</div>
</div>
</div>
);
})}
</div>
);
};
export default ImageWithTextBlock;

View File

@@ -0,0 +1,37 @@
type Content =
| { type: 'paragraph'; children: Array<{ type: 'text'; value: string; bold?: boolean }> }
| {
type: 'text';
value: string;
bold?: boolean;
};
const RichTextBlock = ({ block }: { block: Content }) => {
if (block.type === 'text') {
return block.bold ? (
<strong className="font-semibold">{block.value}</strong>
) : (
<span>{block.value}</span>
);
}
return (
<p className="text-gray-800">
{block.children.map((child, index) => (
<RichTextBlock key={index} block={child} />
))}
</p>
);
};
const RichTextDisplay = ({ contentBlocks }: { contentBlocks: Content[] }) => {
return (
<div className="flex w-full flex-col gap-2">
{contentBlocks.map((block, index) => (
<RichTextBlock key={index} block={block} />
))}
</div>
);
};
export default RichTextDisplay;

View File

@@ -0,0 +1,23 @@
import { PageContent } from 'lib/shopify/types';
import RichTextDisplay from './rich-text-display';
const TextBlock = ({ content }: { content: PageContent }) => {
if (!content.metaobjects.length) return null;
return (
<div className="flex flex-col gap-8">
{content.metaobjects.map((metaobject) => {
const contentBlocks = JSON.parse(metaobject.content || '{}');
return (
<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>
);
};
export default TextBlock;