Skip to content

Commit

Permalink
feat: make product details visually editable
Browse files Browse the repository at this point in the history
  • Loading branch information
fikrikarim authored and agurtovoy committed Nov 11, 2024
1 parent dc96609 commit 5b1c0a6
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 4 deletions.
2 changes: 1 addition & 1 deletion core/app/[locale]/(default)/product/[slug]/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ const ProductSchemaFragment = graphql(`
fragment ProductSchemaFragment on Product {
name
path
plainTextDescription
sku
gtin
mpn
Expand Down Expand Up @@ -289,6 +288,7 @@ const DetailsFragment = graphql(
export const DescriptionFragment = graphql(`
fragment DescriptionFragment on Product {
description
plainTextDescription(characterLimit: 1200)
}
`);

Expand Down
4 changes: 3 additions & 1 deletion core/app/[locale]/(default)/product/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { notFound } from 'next/navigation';
import { getFormatter, getTranslations, setRequestLocale } from 'next-intl/server';
import { Suspense } from 'react';

import { ProductDetail } from '@/vibes/soul/sections/product-detail';
import { Field } from '@/vibes/soul/sections/product-detail/schema';
import { ReviewsSkeleton } from '@/vibes/soul/sections/reviews';
import { pricesTransformer } from '~/data-transformers/prices-transformer';
import { LocaleType } from '~/i18n/routing';
import { AccordionItem, ProductDescription } from '~/makeswift/components/product-description';
import { ProductDetail } from '~/makeswift/components/product-detail';

import { addToCart } from './_actions/add-to-cart';
import { ProductSchema } from './_components/product-schema';
Expand Down Expand Up @@ -148,6 +148,8 @@ export default async function Product({ params: { locale, slug }, searchParams }
const formattedProduct = {
id: product.entityId.toString(),
title: product.name,
description: product.description,
plainTextDescription: product.plainTextDescription,
href: product.path,
images: product.defaultImage
? [{ src: product.defaultImage.url, alt: product.defaultImage.altText }, ...images]
Expand Down
1 change: 1 addition & 0 deletions core/lib/makeswift/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '~/makeswift/components/button-link/button-link.makeswift';
import '~/makeswift/components/card/card.makeswift';
import '~/makeswift/components/carousel/carousel.makeswift';
import '~/makeswift/components/card-carousel/card-carousel.makeswift';
import '~/makeswift/components/product-detail/register';
import '~/makeswift/components/product-description/register';
import '~/makeswift/components/products-carousel/products-carousel.makeswift';
import '~/makeswift/components/products-list/products-list.makeswift';
Expand Down
84 changes: 84 additions & 0 deletions core/makeswift/components/product-detail/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use client';

import React, {
type ComponentPropsWithoutRef,
createContext,
forwardRef,
type PropsWithChildren,
type ReactNode,
type Ref,
useContext,
useMemo,
} from 'react';

import { ProductDetail } from '@/vibes/soul/sections/product-detail';

type VibesProductDetailProps = ComponentPropsWithoutRef<typeof ProductDetail>;

type Props = VibesProductDetailProps & {
product: VibesProductDetailProps['product'] & {
description: string;
plainTextDescription: string;
};
};

const PropsContext = createContext<Props | null>(null);

export const PropsContextProvider = ({ value, children }: PropsWithChildren<{ value: Props }>) => (
<PropsContext.Provider value={value}>{children}</PropsContext.Provider>
);

export const DescriptionType = {
PlainText: 'PlainText',
RichText: 'RichText',
Custom: 'Custom',
} as const;

type DescriptionType = (typeof DescriptionType)[keyof typeof DescriptionType];

export const MakeswiftProductDetail = forwardRef(
(
{
descriptionType,
slot,
}: {
descriptionType: DescriptionType;
slot: ReactNode;
},
ref: Ref<HTMLDivElement>,
) => {
const context = useContext(PropsContext);
const description = useMemo(() => {
const product = context?.product;
if (product == null) return null;

switch (descriptionType) {
case DescriptionType.PlainText:
return product.plainTextDescription;
case DescriptionType.RichText:
return (
<div className="prose" dangerouslySetInnerHTML={{ __html: product.description }} />
);
case DescriptionType.Custom:
return slot;
}
}, [descriptionType, context?.product, slot]);

if (context == null) {
console.error('No context provided for MakeswiftProductDetail');
return <p ref={ref}>There was an error rendering the product detail.</p>;
}

return (
<div className="flex flex-col" ref={ref}>
<ProductDetail
{...context}
product={{
...context.product,
description,
}}
/>
</div>
);
},
);
31 changes: 31 additions & 0 deletions core/makeswift/components/product-detail/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MakeswiftComponent } from '@makeswift/runtime/next';
import { type ComponentPropsWithoutRef } from 'react';

import { ProductDetail as VibesProductDetail } from '@/vibes/soul/sections/product-detail';
import { getComponentSnapshot } from '~/lib/makeswift/client';

import { PropsContextProvider } from './client';
import { COMPONENT_TYPE } from './register';

type VibesProductDetailProps = ComponentPropsWithoutRef<typeof VibesProductDetail>;

type Props = VibesProductDetailProps & {
product: VibesProductDetailProps['product'] & {
description: string;
plainTextDescription: string;
};
};

export const ProductDetail = async (props: Props) => {
const snapshot = await getComponentSnapshot(`product-detail-${props.product.id}`);

return (
<PropsContextProvider value={props}>
<MakeswiftComponent
label={`Detail for ${props.product.title}`}
snapshot={snapshot}
type={COMPONENT_TYPE}
/>
</PropsContextProvider>
);
};
25 changes: 25 additions & 0 deletions core/makeswift/components/product-detail/register.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Select, Slot } from '@makeswift/runtime/controls';

import { runtime } from '~/lib/makeswift/runtime';

import { DescriptionType, MakeswiftProductDetail } from './client';

export const COMPONENT_TYPE = 'catalyst-makeswift-product-detail-description';

runtime.registerComponent(MakeswiftProductDetail, {
type: COMPONENT_TYPE,
label: 'MakeswiftProductDetail (private)',
hidden: true,
props: {
descriptionType: Select({
label: 'Description',
options: [
{ label: 'Catalog (plain text)', value: DescriptionType.PlainText },
{ label: 'Catalog (rich text)', value: DescriptionType.RichText },
{ label: 'Custom', value: DescriptionType.Custom },
],
defaultValue: DescriptionType.PlainText,
}),
slot: Slot(),
},
});
6 changes: 4 additions & 2 deletions core/vibes/soul/sections/product-detail/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ReactNode } from 'react';

import { Breadcrumb, Breadcrumbs } from '@/vibes/soul/primitives/breadcrumbs';
import { Price, PriceLabel } from '@/vibes/soul/primitives/price-label';
import { Rating } from '@/vibes/soul/primitives/rating';
Expand All @@ -15,7 +17,7 @@ type ProductDetailProduct = {
subtitle?: string;
badge?: string;
rating?: number;
description?: string;
description?: string | ReactNode;
};

type Props<F extends Field> = {
Expand Down Expand Up @@ -55,7 +57,7 @@ export function ProductDetail<F extends Field>({ product, action, fields, breadc
</div>

{product.description != null && product.description !== '' && (
<p className="mb-6 text-contrast-500">{product.description}</p>
<div className="mb-6 text-contrast-500">{product.description}</div>
)}

<ProductDetailForm action={action} fields={fields} productId={product.id} />
Expand Down

0 comments on commit 5b1c0a6

Please sign in to comment.