Skip to content

Commit

Permalink
feat: sync template for recent react and next versions (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkucmus authored Nov 7, 2024
1 parent 08c3f3e commit f6c0e07
Show file tree
Hide file tree
Showing 61 changed files with 7,119 additions and 451 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ COMPANY_NAME="shopware AG"
TWITTER_CREATOR="@shopware"
TWITTER_SITE="https://www.shopware.com/en/solutions/shopware-composable-frontends/"
SITE_NAME="Next.js Shopware Starter with Composable Frontends"
SHOPWARE_STORE_DOMAIN=""
SHOPWARE_STORE_DOMAIN="https://demo-frontends.shopware.store"
SHOPWARE_API_TYPE="store-api"
SHOPWARE_ACCESS_TOKEN=""
SHOPWARE_ACCESS_TOKEN="SWSCBHFSNTVMAWNZDNFKSHLAYW"
SHOPWARE_USE_SEO_URLS="true"
SHOPWARE_REVALIDATION_SECRET=""
BASE_E2E_URL=""
23 changes: 0 additions & 23 deletions .eslintrc.js

This file was deleted.

3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
6 changes: 0 additions & 6 deletions .github/dependabot.yml

This file was deleted.

4 changes: 2 additions & 2 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 22

- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 22
cache: 'pnpm'
cache-dependency-path: '**/pnpm-lock.yaml'

Expand Down
35 changes: 0 additions & 35 deletions .github/workflows/test.yml

This file was deleted.

3 changes: 0 additions & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

pnpm run lint
npx lint-staged
1 change: 0 additions & 1 deletion .nvmrc

This file was deleted.

3 changes: 0 additions & 3 deletions .prettierignore

This file was deleted.

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Next.js Shopware Starter

A Next.js 14 and App Router-ready ecommerce template featuring:
A high-performance, server-rendered Next.js App Router ecommerce application.

- Next.js App Router
- Optimized for SEO using Next.js's Metadata
Expand All @@ -18,6 +18,9 @@ Next.js Shopware Starter requires a running [Shopware 6 Instance (Installation G

To get started, use this README and the example environment variable comments.

- [React Bricks](https://github.com/ReactBricks/nextjs-commerce-rb) ([Demo](https://nextjs-commerce.reactbricks.com/))
- Edit pages, product details, and footer content visually using [React Bricks](https://www.reactbricks.com) visual headless CMS.

## Running locally

You will need to use the environment variables [defined in `.env.example`](https://github.com/shopwareLabs/nextjs-shopware-starter/blob/main/.env.example) to run Next.js Shopware Starter. It's recommended you use [Vercel Environment Variables](https://vercel.com/docs/concepts/projects/environment-variables) for this, but a `.env` file is all that is necessary.
Expand Down
16 changes: 11 additions & 5 deletions app/(cms)/[...cms]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import Prose from 'components/prose';
import { getPage } from 'lib/shopware';
import { notFound } from 'next/navigation';

export async function generateMetadata({ params }: { params: { cms: string } }): Promise<Metadata> {
const page = await getPage(params.cms);
export async function generateMetadata({
params
}: {
params: Promise<{ cms: string }>;
}): Promise<Metadata> {
const { cms } = await params;
const page = await getPage(cms);

if (!page) return notFound();

Expand All @@ -20,8 +25,9 @@ export async function generateMetadata({ params }: { params: { cms: string } }):
};
}

export default async function Page({ params }: { params: { cms: string } }) {
const page = await getPage(params.cms);
export default async function Page({ params }: { params: Promise<{ cms: string }> }) {
const { cms } = await params;
const page = await getPage(cms);

if (!page) return notFound();
let date = page.createdAt;
Expand All @@ -32,7 +38,7 @@ export default async function Page({ params }: { params: { cms: string } }) {
return (
<>
<h1 className="mb-8 text-5xl font-bold">{page.title}</h1>
<Prose className="mb-8" html={page.body as string} />
<Prose className="mb-8" html={page.body} />
<p className="text-sm italic">
{`This document was last updated on ${new Intl.DateTimeFormat(undefined, {
year: 'numeric',
Expand Down
3 changes: 2 additions & 1 deletion app/(cms)/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { getPage } from 'lib/shopware';
export const runtime = 'edge';

export default async function Image({ params }: { params: { page: string } }) {
const page = await getPage(params.page);
const { page: pageParamName } = await params;
const page = await getPage(pageParamName);
const title = page ? page.seo?.title || page.title : '';

return await OpengraphImage({ title });
Expand Down
17 changes: 15 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { getCart } from 'components/cart/actions';
import { CartProvider } from 'components/cart/cart-context';
import Navbar from 'components/layout/navbar';
import { GeistSans } from 'geist/font/sans';
import { ensureStartsWith } from 'lib/utils';
import { cookies } from 'next/headers';
import { ReactNode } from 'react';
import './globals.css';

Expand Down Expand Up @@ -32,11 +35,21 @@ export const metadata = {
};

export default async function RootLayout({ children }: { children: ReactNode }) {
const cartId = (await cookies()).get('cartId')?.value;
// Don't await the fetch, pass the Promise to the context provider
const cart = getCart(cartId);

return (
<html lang="en" className={GeistSans.variable}>
<body className="bg-neutral-50 text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
<Navbar />
<main>{children}</main>
<CartProvider cartPromise={cart}>
<Navbar />
<main>
{children}
{/* <Toaster closeButton />
<WelcomeToast /> */}
</main>
</CartProvider>
</body>
</html>
);
Expand Down
2 changes: 1 addition & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const metadata = {
}
};

export default async function HomePage() {
export default function HomePage() {
return (
<>
<ThreeItemGrid />
Expand Down
28 changes: 18 additions & 10 deletions app/product/[...handle]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import { notFound } from 'next/navigation';
import { GridTileImage } from 'components/grid/tile';
import Footer from 'components/layout/footer';
import { Gallery } from 'components/product/gallery';
import { ProductProvider } from 'components/product/product-context';
import { ProductDescription } from 'components/product/product-description';
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
import { getProduct, getProductRecommendations } from 'lib/shopware';
import { Image } from 'lib/shopware/types';
import Link from 'next/link';
import { Suspense } from 'react';

export async function generateMetadata({
params
}: {
params: { handle: string };
export async function generateMetadata(props: {
params: Promise<{ handle: string }>;
}): Promise<Metadata> {
// @ToDo: create a simpler function and do not do the heavy options/variant stuff here
const params = await props.params;
const product = await getProduct(params.handle);

if (!product) return notFound();
Expand Down Expand Up @@ -50,7 +50,8 @@ export async function generateMetadata({
};
}

export default async function ProductPage({ params }: { params: { handle: string } }) {
export default async function ProductPage(props: { params: Promise<{ handle: string }> }) {
const params = await props.params;
const product = await getProduct(params.handle);

if (!product) return notFound();
Expand All @@ -73,7 +74,7 @@ export default async function ProductPage({ params }: { params: { handle: string
};

return (
<>
<ProductProvider>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
Expand All @@ -89,7 +90,7 @@ export default async function ProductPage({ params }: { params: { handle: string
}
>
<Gallery
images={product.images.map((image: Image) => ({
images={product.images.slice(0, 5).map((image: Image) => ({
src: image.url,
altText: image.altText
}))}
Expand All @@ -98,13 +99,15 @@ export default async function ProductPage({ params }: { params: { handle: string
</div>

<div className="basis-full lg:basis-2/6">
<ProductDescription product={product} />
<Suspense fallback={null}>
<ProductDescription product={product} />
</Suspense>
</div>
</div>
<RelatedProducts id={product.id} />
</div>
<Footer />
</>
</ProductProvider>
);
}

Expand All @@ -122,7 +125,12 @@ async function RelatedProducts({ id }: { id: string }) {
key={product.path}
className="aspect-square w-full flex-none min-[475px]:w-1/2 sm:w-1/3 md:w-1/4 lg:w-1/5"
>
<Link className="relative h-full w-full" href={`/product/${product.path}`}>
<Link
className="relative h-full w-full"
prefetch={true}
role="link"
href={`/product/${product.path}`}
>
<GridTileImage
alt={product.title}
label={{
Expand Down
34 changes: 23 additions & 11 deletions app/search/(collection)/[...collection]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import { Metadata } from 'next';
import { notFound } from 'next/navigation';

import Pagination from 'components/collection/pagination';
import Grid from 'components/grid';
import ProductGridItems from 'components/layout/product-grid-items';
import Collections from 'components/layout/search/collections';
import FilterList from 'components/layout/search/filter';
import ProductGridItems from 'components/layout/product-grid-items';
import Pagination from 'components/collection/pagination';

import { defaultSort, sorting } from 'lib/constants';
import { getCollection, getCollectionProducts } from 'lib/shopware';
import { transformHandle } from 'lib/shopware/transform';
import { defaultSort, sorting } from 'lib/constants';

export async function generateMetadata({
params
}: {
params: { collection: string };
params: Promise<{ collection: string }>;
}): Promise<Metadata> {
const { collection: collectionParamName } = await params;

// see https://github.com/facebook/react/issues/25994
const collectionName = decodeURIComponent(transformHandle(params?.collection ?? ''));
const collectionName = decodeURIComponent(transformHandle(collectionParamName ?? ''));
if (collectionName.includes('.js.map')) {
return {};
}
Expand All @@ -29,22 +31,32 @@ export async function generateMetadata({
return {
title: collection.seo?.title || collection.title,
description:
collection.seo?.description || collection.description || `${collection.title} products`
collection.seo?.description || collection.description || `${collection.title} products`,
openGraph: collection.featuredImage
? {
images: [
{
url: collection.featuredImage
}
]
}
: null
};
}

export default async function CategoryPage({
params,
searchParams
}: {
params: { collection: string };
searchParams?: { [key: string]: string | string[] | undefined };
params: Promise<{ collection: string }>;
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
const { sort, page } = searchParams as { [key: string]: string };
const { collection } = await params;
const { sort, page } = (await searchParams) as { [key: string]: string };
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;

// see https://github.com/facebook/react/issues/25994
const collectionName = decodeURIComponent(transformHandle(params?.collection ?? ''));
const collectionName = decodeURIComponent(transformHandle(collection ?? ''));
if (collectionName.includes('.js.map')) {
return null;
}
Expand All @@ -63,7 +75,7 @@ export default async function CategoryPage({
) : (
<div className="mx-auto flex max-w-screen-2xl flex-col gap-8 px-4 pb-4 text-black md:flex-row dark:text-white">
<div className="order-first w-full flex-none md:max-w-[125px]">
<Collections collection={params.collection} />
<Collections collection={collection} />
</div>
<div className="order-last min-h-screen w-full md:order-none">
<Grid className="grid-cols-2 lg:grid-cols-3">
Expand Down
3 changes: 2 additions & 1 deletion app/search/(collection)/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { getCollection } from 'lib/shopware';
export const runtime = 'edge';

export default async function Image({ params }: { params: { collection: string } }) {
const collection = await getCollection(params.collection);
const { collection: collectionParamName } = await params;
const collection = await getCollection(collectionParamName);
const title = collection?.seo?.title || collection?.title;

return await OpengraphImage({ title });
Expand Down
Loading

0 comments on commit f6c0e07

Please sign in to comment.