Structural changes

This commit is contained in:
Henrik Larsson
2023-08-07 15:06:13 +02:00
parent 1857278dda
commit a75a2e7feb
25 changed files with 359 additions and 180 deletions

View File

@@ -1,26 +0,0 @@
'use client'
import PreviewBanner from 'components/ui/preview-banner'
import { usePreview } from 'lib/sanity/sanity.preview'
import CategoryPage from './category-page'
export default function CategoryPagePreview({
query,
queryParams,
}: {
query: string
queryParams: {
[key: string]: any
}
}) {
const data = usePreview(null, query, queryParams)
const { title, _type } = data
return (
<>
<CategoryPage data={data} />
<PreviewBanner title={`${title} (${_type})`} />
</>
)
}

View File

@@ -1,19 +0,0 @@
import { notFound } from "next/navigation";
interface CategoryPageProps {
data: object | any
}
// This is a Client Component. It receives data as props and
// has access to state and effects just like Page components
// in the `pages` directory.
export default function CategoryPage({data }: CategoryPageProps) {
if (!data) {
return notFound();
}
return (
<div>Category: {data?.title}</div>
)
}

View File

@@ -1,26 +0,0 @@
'use client'
import PreviewBanner from 'components/ui/preview-banner'
import { usePreview } from 'lib/sanity/sanity.preview'
import HomePage from './home-page'
export default function HomePagePreview({
query,
queryParams,
}: {
query: string
queryParams: {
[key: string]: any
}
}) {
const data = usePreview(null, query, queryParams)
const { title, _type } = data
return (
<>
<HomePage data={data} />
<PreviewBanner title={`${title} (${_type})`} />
</>
)
}

View File

@@ -1,14 +0,0 @@
import DynamicContentManager from 'components/layout/dynamic-content-manager'
interface HomePageProps {
data: object | any
}
// This is a Client Component. It receives data as props and
// has access to state and effects just like Page components
// in the `pages` directory.
export default function HomePage({ data }: HomePageProps) {
return (
<DynamicContentManager content={data?.content} />
)
}

View File

@@ -1,137 +0,0 @@
// Next
import type { Metadata } from 'next';
import { draftMode } from 'next/headers';
// Sanity
import PreviewSuspense from 'components/preview-suspense';
import getQueryFromSlug from 'helpers/getQueryFromSlug';
import { docQuery } from 'lib/sanity/queries';
import { clientFetch } from 'lib/sanity/sanity.client';
// Pages.
import CategoryPage from './category-page';
import CategoryPagePreview from './category-page-preview';
import HomePage from './home-page';
import HomePagePreview from './home-page-preview';
import ProductPage from './product-page';
import ProductPagePreview from './product-page-preview';
import SinglePage from './single-page';
import SinglePagePreview from './single-page-preview';
// Chrome
import Footer from 'components/layout/footer';
import Header from 'components/layout/header';
/**
* Render pages depending on type.
*/
export default async function Page({ params }: { params: { slug: string[]; locale: string } }) {
const { isEnabled } = draftMode();
const { slug, locale } = params;
const { query = '', queryParams, docType } = getQueryFromSlug(slug, locale);
const pageData = await clientFetch(query, queryParams);
const data = filterDataToSingleItem(pageData, isEnabled);
const localeData = {
type: data._type,
locale: data.locale,
translations: data.translations
};
return (
<div className="flex flex-col">
<Header localeData={localeData} />
<main className="flex-1">
<article>
{isEnabled ? (
<PreviewSuspense fallback="Loading...">
{docType === 'home' && <HomePagePreview query={query} queryParams={queryParams} />}
{docType === 'page' && <SinglePagePreview query={query} queryParams={queryParams} />}
{docType === 'product' && (
<ProductPagePreview query={query} queryParams={queryParams} />
)}
{docType === 'category' && (
<CategoryPagePreview query={query} queryParams={queryParams} />
)}
</PreviewSuspense>
) : (
<>
{docType === 'home' && <HomePage data={data} />}
{docType === 'product' && <ProductPage data={data} />}
{docType === 'category' && <CategoryPage data={data} />}
{docType === 'page' && <SinglePage data={data} />}
</>
)}
</article>
</main>
<Footer />
</div>
);
}
/**
* Get paths for each page.
*/
export async function generateStaticParams() {
const paths = await clientFetch(docQuery);
return paths.map((path: { slug: string; locale: string }) => ({
slug: path.slug.split('/').filter((p) => p),
locale: path.locale
}));
}
/**
* Helper function to return the correct version of the document
* If we're in "preview mode" and have multiple documents, return the draft
*/
function filterDataToSingleItem(data: any, preview = false) {
if (!Array.isArray(data)) {
return data;
}
if (data.length === 1) {
return data[0];
}
if (preview) {
return data.find((item) => item._id.startsWith(`drafts.`)) || data[0];
}
return data[0];
}
/**
* Generate metadata for each page.
*/
export async function generateMetadata({
params
}: {
params: { slug: string[]; locale: string };
}): Promise<Metadata> {
const { slug, locale } = params;
const { query = '', queryParams } = getQueryFromSlug(slug, locale);
const pageData = await clientFetch(query, queryParams);
const data = filterDataToSingleItem(pageData, false);
const { seo } = data ?? {};
return {
title: seo?.title ? seo?.title : data?.title,
description: seo?.description ? seo.description : 'Webb och digitalbyrå från Göteborg',
openGraph: {
images: [
{
url: seo?.image?.asset?.url ? seo.image.asset.url : '/og-image.jpg',
width: 1200,
height: 630,
alt: seo?.coverImage?.alt ? seo.coverImage.alt : 'Kodamera AB'
}
]
}
};
}

View File

@@ -1,26 +0,0 @@
'use client'
import PreviewBanner from 'components/ui/preview-banner'
import { usePreview } from 'lib/sanity/sanity.preview'
import ProductPage from './product-page'
export default function ProductPagePreview({
query,
queryParams,
}: {
query: string
queryParams: {
[key: string]: any
}
}) {
const data = usePreview(null, query, queryParams)
const { title, _type } = data
return (
<>
<ProductPage data={data} />
<PreviewBanner title={`${title} (${_type})`} />
</>
)
}

View File

@@ -1,21 +0,0 @@
import ProductView from "components/product/product-view";
import { notFound } from "next/navigation";
interface ProductPageProps {
data: object | any
}
// This is a Client Component. It receives data as props and
// has access to state and effects just like Page components
// in the `pages` directory.
export default function ProductPage({data }: ProductPageProps) {
if (!data) {
return notFound();
}
const { product } = data;
return (
<ProductView product={product} relatedProducts={[]} />
)
}

View File

@@ -1,26 +0,0 @@
'use client'
import PreviewBanner from 'components/ui/preview-banner'
import { usePreview } from 'lib/sanity/sanity.preview'
import SinglePage from './single-page'
export default function SinglePagePreview({
query,
queryParams,
}: {
query: string
queryParams: {
[key: string]: any
}
}) {
const data = usePreview(null, query, queryParams)
const { title, _type } = data
return (
<>
<SinglePage data={data} />
<PreviewBanner title={`${title} (${_type})`} />
</>
)
}

View File

@@ -1,20 +0,0 @@
import DynamicContentManager from 'components/layout/dynamic-content-manager';
import { notFound } from "next/navigation";
interface SinglePageProps {
data: object | any
}
// This is a Client Component. It receives data as props and
// has access to state and effects just like Page components
// in the `pages` directory.
export default function SinglePage({data }: SinglePageProps) {
if (!data) {
return notFound();
}
return (
<DynamicContentManager content={data?.content} />
)
}

View File

@@ -0,0 +1,44 @@
import DynamicContentManager from 'components/layout/dynamic-content-manager';
import { pageQuery } from 'lib/sanity/queries';
import { clientFetch } from 'lib/sanity/sanity.client';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
export const runtime = 'edge';
export const revalidate = 43200; // 12 hours in seconds
export async function generateMetadata({
params
}: {
params: { locale: string; slug: string };
}): Promise<Metadata> {
const page = await clientFetch(pageQuery, params);
if (!page) return notFound();
return {
title: page.seo?.title || page.title,
description: page.seo?.description || page.bodySummary,
openGraph: {
publishedTime: page.createdAt,
modifiedTime: page.updatedAt,
type: 'article'
}
};
}
interface PageParams {
params: {
locale: string;
slug: string;
};
}
export default async function Page({ params }: PageParams) {
const page = await clientFetch(pageQuery, params);
if (!page) return notFound();
return <DynamicContentManager content={page?.content} />;
}

View File

@@ -0,0 +1,41 @@
import Text from 'components/ui/text/text';
import { categoryQuery } from 'lib/sanity/queries';
import { clientFetch } from 'lib/sanity/sanity.client';
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
export async function generateMetadata({
params
}: {
params: { slug: string; locale: string };
}): Promise<Metadata> {
const category = await clientFetch(categoryQuery, params);
if (!category) return notFound();
return {
title: category.seo.title || category.title,
description: category.seo.description || category.description
};
}
interface CategoryPageParams {
params: {
locale: string;
slug: string;
};
}
export default async function ProductPage({ params }: CategoryPageParams) {
const category = await clientFetch(categoryQuery, params);
if (!category) return notFound();
const { title } = category;
return (
<div className="mb-8 flex w-full flex-col px-4 lg:my-16 lg:px-8 2xl:px-16">
<Text variant={'pageHeading'}>{title}</Text>
</div>
);
}

View File

@@ -1,7 +1,10 @@
import Footer from 'components/layout/footer/footer';
import Header from 'components/layout/header/header';
import { NextIntlClientProvider } from 'next-intl';
import { Inter } from 'next/font/google';
import { notFound } from 'next/navigation';
import { ReactNode } from 'react';
import { supportedLanguages } from '../../i18n-config';
import './globals.css';
const SITE_NAME = 'KM Storefront';
@@ -36,7 +39,7 @@ const inter = Inter({
});
export function generateStaticParams() {
return [{ locale: 'sv' }, { locale: 'en' }];
return supportedLanguages.locales.map((locale) => ({ locale: locale.id }));
}
interface LocaleLayoutProps {
@@ -59,7 +62,9 @@ export default async function LocaleLayout({ children, params: { locale } }: Loc
<html lang={locale} className={inter.variable}>
<body className="flex min-h-screen flex-col">
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
<Header />
<main className="flex-1">{children}</main>
<Footer />
</NextIntlClientProvider>
</body>
</html>

27
app/[locale]/page.tsx Normal file
View File

@@ -0,0 +1,27 @@
import DynamicContentManager from 'components/layout/dynamic-content-manager/dynamic-content-manager';
import { homePageQuery } from 'lib/sanity/queries';
import { clientFetch } from 'lib/sanity/sanity.client';
export const runtime = 'edge';
export const metadata = {
description: 'High-performance ecommerce store built with Next.js, Vercel, Sanity and Storm.',
openGraph: {
type: 'website'
}
};
interface HomePageParams {
params: {
locale: string;
};
}
export default async function HomePage({ params }: HomePageParams) {
const data = await clientFetch(homePageQuery, params);
return (
<>
<DynamicContentManager content={data?.content} />
</>
);
}

View File

@@ -0,0 +1,89 @@
import ProductView from 'components/product/product-view';
import { productQuery } from 'lib/sanity/queries';
import { clientFetch } from 'lib/sanity/sanity.client';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
interface ProductPageParams {
params: {
locale: string;
slug: string;
};
}
export async function generateMetadata({
params
}: {
params: { slug: string; locale: string };
}): Promise<Metadata> {
const product = await clientFetch(productQuery, params);
if (!product) return notFound();
const { alt } = product.images[0] || '';
const { url } = product.images[0].asset || {};
const { width, height } = product.images[0].asset.metadata.dimensions;
// const indexable = !product.tags.includes(HIDDEN_PRODUCT_TAG);
return {
title: product.seo.title || product.title,
description: product.seo.description || product.description,
// @TODO ROBOTS SETTINGS???
// robots: {
// index: indexable,
// follow: indexable,
// googleBot: {
// index: indexable,
// follow: indexable
// }
// },
openGraph: url
? {
images: [
{
url,
width,
height,
alt
}
]
}
: null
};
}
export default async function ProductPage({ params }: ProductPageParams) {
const product = await clientFetch(productQuery, params);
if (!product) return notFound();
const productJsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
description: product.description,
// @TODO UPDATE TO STORM URL???
image: product.images[0].asset.url
// offers: {
// '@type': 'AggregateOffer',
// availability: product.availableForSale
// ? 'https://schema.org/InStock'
// : 'https://schema.org/OutOfStock',
// priceCurrency: product.priceRange.minVariantPrice.currencyCode,
// highPrice: product.priceRange.maxVariantPrice.amount,
// lowPrice: product.priceRange.minVariantPrice.amount
// }
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(productJsonLd)
}}
/>
<ProductView product={product} relatedProducts={[]} />;
</>
);
}