Skip to content

Commit

Permalink
feat: integrate makeswift with catalyst
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewvolk committed Jul 30, 2024
1 parent 5c77f41 commit 3c98dc3
Show file tree
Hide file tree
Showing 21 changed files with 1,514 additions and 41 deletions.
4 changes: 4 additions & 0 deletions core/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Makeswift Site API Key
# In the Makeswift builder, go to Settings > Host and copy the API key for the site.
MAKESWIFT_SITE_API_KEY=

# The hash visible in the subject store's URL when signed in to the store control panel.
# The control panel URL is of the form `https://store-{hash}.mybigcommerce.com`.
BIGCOMMERCE_STORE_HASH=
Expand Down
41 changes: 39 additions & 2 deletions core/app/[locale]/(default)/[...rest]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
import { Page as MakeswiftPage } from '@makeswift/runtime/next';
import { getSiteVersion } from '@makeswift/runtime/next/server';
import { notFound } from 'next/navigation';

export default function CatchAllPage() {
notFound();
import { locales } from '~/i18n';
import { client } from '~/lib/makeswift/client';
import { MakeswiftProvider } from '~/lib/makeswift/provider';

interface CatchAllParams {
locale: string;
rest: string[];
}

export async function generateStaticParams() {
const pages = await client.getPages().toArray();

return pages.flatMap((page) =>
locales.map((locale) => ({
rest: page.path.split('/').filter((segment) => segment !== ''),
locale,
})),
);
}

export default async function CatchAllPage({ params }: { params: CatchAllParams }) {
const path = `/${params.rest.join('/')}`;

const snapshot = await client.getPageSnapshot(path, {
siteVersion: getSiteVersion(),
locale: params.locale,
});

if (snapshot == null) return notFound();

return (
<MakeswiftProvider>
<MakeswiftPage snapshot={snapshot} />
</MakeswiftProvider>
);
}

export const runtime = 'nodejs';
4 changes: 4 additions & 0 deletions core/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DraftModeScript } from '@makeswift/runtime/next/server';
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/next';
import type { Metadata } from 'next';
Expand Down Expand Up @@ -66,6 +67,9 @@ export default function RootLayout({ children, params: { locale } }: RootLayoutP

return (
<html className={`${inter.variable} font-sans`} lang={locale}>
<head>
<DraftModeScript />
</head>
<body className="flex h-screen min-w-[375px] flex-col">
<Notifications />
<NextIntlClientProvider locale={locale} messages={{ Providers: messages.Providers ?? {} }}>
Expand Down
14 changes: 14 additions & 0 deletions core/app/api/makeswift/[...makeswift]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MakeswiftApiHandler } from '@makeswift/runtime/next/server';
import { strict } from 'assert';

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

strict(process.env.MAKESWIFT_SITE_API_KEY, 'MAKESWIFT_SITE_API_KEY is required');

const handler = MakeswiftApiHandler(process.env.MAKESWIFT_SITE_API_KEY, {
runtime,
apiOrigin: process.env.MAKESWIFT_API_ORIGIN,
appOrigin: process.env.MAKESWIFT_APP_ORIGIN,
});

export { handler as GET, handler as POST };
10 changes: 10 additions & 0 deletions core/app/api/makeswift/draft-mode/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { draftMode } from 'next/headers';
import { NextRequest } from 'next/server';

export const GET = (request: NextRequest) => {
if (request.headers.get('x-makeswift-api-key') === process.env.MAKESWIFT_SITE_API_KEY) {
draftMode().enable();
}

return new Response(null);
};
52 changes: 52 additions & 0 deletions core/app/api/product-card-carousel/[type]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';
import { NextRequest, NextResponse } from 'next/server';

import { getSessionCustomerId } from '~/auth';
import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { ProductCardCarouselFragment } from '~/components/product-card-carousel';

const GetProductCardCarousel = graphql(
`
query GetProductCardCarousel {
site {
newestProducts(first: 12) {
edges {
node {
...ProductCardCarouselFragment
}
}
}
featuredProducts(first: 12) {
edges {
node {
...ProductCardCarouselFragment
}
}
}
}
}
`,
[ProductCardCarouselFragment],
);

export const GET = async (
_request: NextRequest,
{ params }: { params: { type: 'newest' | 'featured' } },
) => {
const customerId = await getSessionCustomerId();
const { type } = params;

const { data } = await client.fetch({
document: GetProductCardCarousel,
customerId,
});

if (type === 'newest') {
return NextResponse.json(removeEdgesAndNodes(data.site.newestProducts));
}

return NextResponse.json(removeEdgesAndNodes(data.site.featuredProducts));
};

export const runtime = 'edge';
35 changes: 35 additions & 0 deletions core/components/ui/accordions/accordion.makeswift.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { List, Select, Shape, Slot, TextInput } from '@makeswift/runtime/controls';

import { Accordions } from '~/components/ui/accordions';
import { runtime } from '~/lib/makeswift/runtime';

runtime.registerComponent(Accordions, {
type: 'catalyst-accordion',
label: 'Catalyst / Accordion',
props: {
accordions: List({
label: 'Accordions',
type: Shape({
type: {
content: Slot(),
value: TextInput({
label: 'Value',
defaultValue: 'Lorem Ipsum?',
placeholder: 'Unique value',
}),
},
}),
getItemLabel() {
return 'Slot';
},
}),
type: Select({
label: 'Type',
options: [
{ label: 'Single', value: 'single' },
{ label: 'Multiple', value: 'multiple' },
],
defaultValue: 'single',
}),
},
});
67 changes: 67 additions & 0 deletions core/components/ui/carousel/carousel.makeswift.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Select, Style, TextInput } from '@makeswift/runtime/controls';
import { useEffect, useState } from 'react';

import { ResultOf } from '~/client/graphql';
import { ProductCard } from '~/components/product-card';
import { ProductCardCarouselFragment } from '~/components/product-card-carousel';
import { runtime } from '~/lib/makeswift/runtime';

import { Carousel } from './carousel';

interface Props {
title: string;
type: 'newest' | 'featured';
className?: string;
}

type Product = ResultOf<typeof ProductCardCarouselFragment>;

runtime.registerComponent(
function MakeswiftCarousel({ title, type, className }: Props) {
const [products, setProducts] = useState<Product[]>([]);

useEffect(() => {
const fetchProducts = async () => {
const response = await fetch(`/api/product-card-carousel/${type}`);
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const data = (await response.json()) as Product[];

setProducts(data);
};

void fetchProducts();
}, [type]);

const items = products.map((product) => (
<ProductCard
imageSize="tall"
key={product.entityId}
product={product}
showCart={false}
showCompare={false}
/>
));

return (
<div className={className}>
<Carousel className="mb-14" items={items} title={title} />
</div>
);
},
{
type: 'catalyst-carousel',
label: 'Catalyst / Carousel',
props: {
className: Style(),
title: TextInput({ label: 'Title', defaultValue: 'Carousel' }),
type: Select({
label: 'Type',
options: [
{ label: 'Newest', value: 'newest' },
{ label: 'Featured', value: 'featured' },
],
defaultValue: 'newest',
}),
},
},
);
42 changes: 42 additions & 0 deletions core/components/ui/slideshow/slide.makeswift.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Style, TextInput } from '@makeswift/runtime/controls';

import { Button } from '~/components/ui/button';
import { runtime } from '~/lib/makeswift/runtime';
import { cn } from '~/lib/utils';

interface Props {
className?: string;
title: string;
description: string;
ctaText: string;
ctaUrl: string;
}

runtime.registerComponent(
function Slide({ className, title, description, ctaText, ctaUrl }: Props) {
return (
<div className={cn('flex flex-col gap-4 bg-gray-100 px-12 pb-48 pt-36', className)} key={2}>
<h2 className="text-5xl font-black lg:text-6xl">{title}</h2>
<p className="max-w-xl">{description}</p>
<Button asChild className="w-fit">
<a href={ctaUrl}>{ctaText}</a>
</Button>
</div>
);
},
{
type: 'catalyst-slide',
label: 'Catalyst / Slideshow / Slide',
props: {
className: Style(),
title: TextInput({ label: 'Title', defaultValue: 'Great Deals' }),
description: TextInput({
label: 'Description',
defaultValue:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.',
}),
ctaText: TextInput({ label: 'CTA Text', defaultValue: 'Shop now' }),
ctaUrl: TextInput({ label: 'CTA URL', defaultValue: '/#' }),
},
},
);
17 changes: 17 additions & 0 deletions core/components/ui/slideshow/slideshow.makeswift.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { List, Slot, Style } from '@makeswift/runtime/controls';

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

import { Slideshow } from './slideshow';

runtime.registerComponent(Slideshow, {
type: 'catalyst-slideshow',
label: 'Catalyst / Slideshow / Slideshow',
props: {
className: Style(),
slides: List({
label: 'Slides',
type: Slot(),
}),
},
});
4 changes: 2 additions & 2 deletions core/components/ui/slideshow/slideshow.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import useEmblaCarousel from 'embla-carousel-react';
import { ArrowLeft, ArrowRight, Pause, Play } from 'lucide-react';
import { ComponentPropsWithRef, ReactNode, useEffect, useReducer, useState } from 'react';
import { ComponentPropsWithoutRef, ReactNode, useEffect, useReducer, useState } from 'react';

interface Props extends ComponentPropsWithRef<'section'> {
interface Props extends ComponentPropsWithoutRef<'section'> {
slides: ReactNode[];
interval?: number;
}
Expand Down
32 changes: 32 additions & 0 deletions core/components/ui/tabs/tabs.makeswift.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { List, Shape, Slot, Style, TextInput } from '@makeswift/runtime/controls';

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

import { Tabs } from './tabs';

runtime.registerComponent(Tabs, {
type: 'catalyst-tabs',
label: 'Catalyst / Tabs',
props: {
className: Style(),
tabs: List({
label: 'Tabs',
type: Shape({
type: {
content: Slot(),
title: Slot(),
value: TextInput({
label: 'Unique value',
defaultValue: Math.random().toString(36).substring(7),
placeholder: 'Unique value',
}),
},
}),
}),
label: TextInput({
label: 'Label',
defaultValue: 'Tabs',
placeholder: 'Label',
}),
},
});
11 changes: 11 additions & 0 deletions core/lib/makeswift/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Makeswift } from '@makeswift/runtime/next';
import { strict } from 'assert';

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

strict(process.env.MAKESWIFT_SITE_API_KEY, 'MAKESWIFT_SITE_API_KEY is required');

export const client = new Makeswift(process.env.MAKESWIFT_SITE_API_KEY, {
runtime,
apiOrigin: process.env.MAKESWIFT_API_ORIGIN,
});
5 changes: 5 additions & 0 deletions core/lib/makeswift/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import '~/components/ui/accordions/accordion.makeswift';
import '~/components/ui/carousel/carousel.makeswift';
import '~/components/ui/slideshow/slide.makeswift';
import '~/components/ui/slideshow/slideshow.makeswift';
import '~/components/ui/tabs/tabs.makeswift';
14 changes: 14 additions & 0 deletions core/lib/makeswift/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use client';

import { ReactRuntimeProvider, RootStyleRegistry } from '@makeswift/runtime/next';

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

export function MakeswiftProvider({ children }: { children: React.ReactNode }) {
return (
<ReactRuntimeProvider runtime={runtime}>
<RootStyleRegistry>{children}</RootStyleRegistry>
</ReactRuntimeProvider>
);
}
3 changes: 3 additions & 0 deletions core/lib/makeswift/runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ReactRuntime } from '@makeswift/runtime/react';

export const runtime = new ReactRuntime();
Loading

0 comments on commit 3c98dc3

Please sign in to comment.