Iterations and TS error fixes

This commit is contained in:
Henrik Larsson 2023-05-05 10:12:15 +02:00
parent c68f95e454
commit 4bf59a61f6
26 changed files with 111 additions and 621 deletions

View File

@ -7,7 +7,6 @@ interface CategoryPageProps {
// has access to state and effects just like Page components // has access to state and effects just like Page components
// in the `pages` directory. // in the `pages` directory.
export default function ProductPage({data }: CategoryPageProps) { export default function ProductPage({data }: CategoryPageProps) {
console.log(data);
return ( return (
<>Category page</> <>Category page</>

View File

@ -14,8 +14,6 @@ export async function generateStaticParams() {
next: { revalidate: 10 }, next: { revalidate: 10 },
}) })
// console.log(paths)
return paths.map((path: { return paths.map((path: {
slug: string, slug: string,
locale: string locale: string
@ -52,7 +50,7 @@ export default async function Page({
}) { }) {
const { slug, locale } = params; const { slug, locale } = params;
const { query, queryParams, docType } = getQueryFromSlug(slug, locale) const { query = '', queryParams, docType } = getQueryFromSlug(slug, locale)
const pageData = await client.fetch(query, queryParams) const pageData = await client.fetch(query, queryParams)

View File

@ -1,4 +1,5 @@
import ProductView from "components/product/product-view"; import ProductView from "components/product/product-view";
import { notFound } from "next/navigation";
interface ProductPageProps { interface ProductPageProps {
data: object | any data: object | any
@ -8,6 +9,10 @@ interface ProductPageProps {
// has access to state and effects just like Page components // has access to state and effects just like Page components
// in the `pages` directory. // in the `pages` directory.
export default function ProductPage({data }: ProductPageProps) { export default function ProductPage({data }: ProductPageProps) {
if (!data) {
return notFound();
}
const { product } = data; const { product } = data;
return ( return (

View File

@ -39,6 +39,15 @@ body {
} }
/* COMPONENTS */ /* COMPONENTS */
.glider {
scrollbar-width: none;
-ms-overflow-style: none;
}
.glider::-webkit-scrollbar {
display: none;
}
.glider-dots { .glider-dots {
@apply flex !space-x-[2px] !mt-8; @apply flex !space-x-[2px] !mt-8;
} }

View File

@ -57,7 +57,9 @@ export default async function LocaleLayout({children, params: {locale}}: LocaleL
<body className="flex flex-col min-h-screen"> <body className="flex flex-col min-h-screen">
<NextIntlClientProvider locale={locale} messages={messages}> <NextIntlClientProvider locale={locale} messages={messages}>
<Header /> <Header />
<main className="flex-1">{children}</main> <main className="flex-1">
{children}
</main>
<Footer /> <Footer />
</NextIntlClientProvider> </NextIntlClientProvider>
</body> </body>

View File

@ -1,112 +0,0 @@
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { Suspense } from 'react';
import Grid from 'components/grid';
import Footer from 'components/layout/footer';
import ProductGridItems from 'components/layout/product-grid-items';
import { AddToCart } from 'components/product/add-to-cart';
import { Gallery } from 'components/product/gallery';
import { VariantSelector } from 'components/product/variant-selector';
import Prose from 'components/prose';
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
import { getProduct, getProductRecommendations } from 'lib/shopify';
import { Image } from 'lib/shopify/types';
export const runtime = 'edge';
export async function generateMetadata({
params
}: {
params: { handle: string };
}): Promise<Metadata> {
const product = await getProduct(params.handle);
if (!product) return notFound();
const { url, width, height, altText: alt } = product.featuredImage || {};
const hide = !product.tags.includes(HIDDEN_PRODUCT_TAG);
return {
title: product.seo.title || product.title,
description: product.seo.description || product.description,
robots: {
index: hide,
follow: hide,
googleBot: {
index: hide,
follow: hide
}
},
openGraph: url
? {
images: [
{
url,
width,
height,
alt
}
]
}
: null
};
}
export default async function ProductPage({ params }: { params: { handle: string } }) {
const product = await getProduct(params.handle);
if (!product) return notFound();
return (
<div>
<div className="lg:grid lg:grid-cols-6">
<div className="lg:col-span-4">
<Gallery
title={product.title}
amount={product.priceRange.maxVariantPrice.amount}
currencyCode={product.priceRange.maxVariantPrice.currencyCode}
images={product.images.map((image: Image) => ({
src: image.url,
altText: image.altText
}))}
/>
</div>
<div className="p-6 lg:col-span-2">
{/* @ts-expect-error Server Component */}
<VariantSelector options={product.options} variants={product.variants} />
{product.descriptionHtml ? (
<Prose className="mb-6 text-sm leading-tight" html={product.descriptionHtml} />
) : null}
<AddToCart variants={product.variants} availableForSale={product.availableForSale} />
</div>
</div>
<Suspense>
{/* @ts-expect-error Server Component */}
<RelatedProducts id={product.id} />
<Suspense>
{/* @ts-expect-error Server Component */}
<Footer />
</Suspense>
</Suspense>
</div>
);
}
async function RelatedProducts({ id }: { id: string }) {
const relatedProducts = await getProductRecommendations(id);
if (!relatedProducts.length) return null;
return (
<div className="px-4 py-8">
<div className="mb-4 text-3xl font-bold">Related Products</div>
<Grid className="grid-cols-2 lg:grid-cols-5">
<ProductGridItems products={relatedProducts} />
</Grid>
</div>
);
}

View File

@ -1,4 +1,3 @@
import { getCollections, getPages, getProducts } from 'lib/shopify';
import { MetadataRoute } from 'next'; import { MetadataRoute } from 'next';
const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
@ -11,23 +10,5 @@ export default async function sitemap(): Promise<Promise<Promise<MetadataRoute.S
lastModified: new Date().toISOString() lastModified: new Date().toISOString()
})); }));
const collections = await getCollections(); return [...routesMap];
const collectionsMap = collections.map((collection) => ({
url: `${baseUrl}${collection.path}`,
lastModified: collection.updatedAt
}));
const products = await getProducts({});
const productsMap = products.map((product) => ({
url: `${baseUrl}/product/${product.handle}`,
lastModified: product.updatedAt
}));
const pages = await getPages();
const pagesMap = pages.map((page) => ({
url: `${baseUrl}/${page.handle}`,
lastModified: page.updatedAt
}));
return [...routesMap, ...collectionsMap, ...productsMap, ...pagesMap];
} }

View File

@ -1,5 +1,5 @@
import CloseIcon from 'components/icons/close'; import CloseIcon from 'components/icons/close';
import LoadingDots from 'components/loading-dots'; import LoadingDots from 'components/ui/loading-dots';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { startTransition, useState } from 'react'; import { startTransition, useState } from 'react';

View File

@ -1,50 +0,0 @@
import Link from 'next/link';
import LogoIcon from 'components/icons/logo';
// import { getMenu } from 'lib/shopify';
// import { Menu } from 'lib/shopify/types';
// import MobileMenu from './mobile-menu';
// import Search from './search';
export default async function Navbar() {
// const menu = await getMenu('next-js-frontend-header-menu');
return (
<nav className="relative flex items-center justify-between bg-white p-4 dark:bg-black lg:px-6">
<div className="block w-1/3 md:hidden">
{/* <MobileMenu menu={menu} /> */}
</div>
<div className="flex justify-self-center md:w-1/3 md:justify-self-start">
<div className="md:mr-4">
<Link href="/" aria-label="Go back home">
<LogoIcon className="h-8 transition-transform hover:scale-110" />
</Link>
</div>
{/* {menu.length ? (
<ul className="hidden md:flex">
{menu.map((item: Menu) => (
<li key={item.title}>
<Link
href={item.path}
className="rounded-lg px-2 py-1 text-gray-800 hover:text-gray-500 dark:text-gray-200 dark:hover:text-gray-400"
>
{item.title}
</Link>
</li>
))}
</ul>
) : null} */}
</div>
<div className="hidden w-1/3 md:block">
{/* <Search /> */}
</div>
<div className="flex w-1/3 justify-end">
{/* <Suspense fallback={<CartIcon className="h-6" />}> */}
{/* @ts-expect-error Server Component */}
{/* <Cart /> */}
{/* </Suspense> */}
</div>
</nav>
);
}

View File

@ -1,98 +0,0 @@
'use client';
import { Dialog } from '@headlessui/react';
import { motion } from 'framer-motion';
import Link from 'next/link';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import CloseIcon from 'components/icons/close';
import MenuIcon from 'components/icons/menu';
import { Menu } from 'lib/shopify/types';
import Search from './search';
export default function MobileMenu({ menu }: { menu: Menu[] }) {
const pathname = usePathname();
const searchParams = useSearchParams();
const [mobileMenuIsOpen, setMobileMenuIsOpen] = useState(false);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth > 768) {
setMobileMenuIsOpen(false);
}
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [mobileMenuIsOpen]);
useEffect(() => {
setMobileMenuIsOpen(false);
}, [pathname, searchParams]);
return (
<>
<button
onClick={() => {
setMobileMenuIsOpen(!mobileMenuIsOpen);
}}
aria-label="Open mobile menu"
className="md:hidden"
data-testid="open-mobile-menu"
>
<MenuIcon className="h-6" />
</button>
<Dialog
open={mobileMenuIsOpen}
onClose={() => {
setMobileMenuIsOpen(false);
}}
className="relative z-50"
>
<div className="fixed inset-0 flex justify-end" data-testid="mobile-menu">
<Dialog.Panel
as={motion.div}
variants={{
open: { opacity: 1 }
}}
className="flex w-full flex-col bg-white pb-6 dark:bg-black"
>
<div className="p-4">
<button
className="mb-4"
onClick={() => {
setMobileMenuIsOpen(false);
}}
aria-label="Close mobile menu"
data-testid="close-mobile-menu"
>
<CloseIcon className="h-6" />
</button>
<div className="mb-4 w-full">
<Search />
</div>
{menu.length ? (
<ul className="flex flex-col">
{menu.map((item: Menu) => (
<li key={item.title}>
<Link
href={item.path}
className="rounded-lg py-1 text-xl text-black transition-colors hover:text-gray-500 dark:text-white"
onClick={() => {
setMobileMenuIsOpen(false);
}}
>
{item.title}
</Link>
</li>
))}
</ul>
) : null}
</div>
</Dialog.Panel>
</div>
</Dialog>
</>
);
}

View File

@ -1,42 +0,0 @@
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import SearchIcon from 'components/icons/search';
export default function Search() {
const router = useRouter();
const searchParams = useSearchParams();
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const val = e.target as HTMLFormElement;
const search = val.search as HTMLInputElement;
if (search.value) {
router.push(`/search?q=${search.value}`);
} else {
router.push(`/search`);
}
}
return (
<form
onSubmit={onSubmit}
className="relative m-0 flex w-full items-center border border-gray-200 bg-transparent p-0 dark:border-gray-500"
>
<input
type="text"
name="search"
placeholder="Search for products..."
autoComplete="off"
defaultValue={searchParams?.get('q') || ''}
className="w-full px-4 py-2 text-black dark:bg-black dark:text-gray-100"
/>
<div className="absolute right-0 top-0 mr-3 flex h-full items-center">
<SearchIcon className="h-5" />
</div>
</form>
);
}

View File

@ -1,38 +0,0 @@
import clsx from 'clsx';
import { Suspense } from 'react';
import { getCollections } from 'lib/shopify';
import FilterList from './filter';
async function CollectionList() {
const collections = await getCollections();
return <FilterList list={collections} title="Collections" />;
}
const skeleton = 'mb-3 h-4 w-5/6 animate-pulse rounded';
const activeAndTitles = 'bg-gray-800 dark:bg-gray-300';
const items = 'bg-gray-400 dark:bg-gray-700';
export default function Collections() {
return (
<Suspense
fallback={
<div className="col-span-2 hidden h-[400px] w-full flex-none py-4 pl-10 lg:block">
<div className={clsx(skeleton, activeAndTitles)} />
<div className={clsx(skeleton, activeAndTitles)} />
<div className={clsx(skeleton, items)} />
<div className={clsx(skeleton, items)} />
<div className={clsx(skeleton, items)} />
<div className={clsx(skeleton, items)} />
<div className={clsx(skeleton, items)} />
<div className={clsx(skeleton, items)} />
<div className={clsx(skeleton, items)} />
<div className={clsx(skeleton, items)} />
</div>
}
>
{/* @ts-expect-error Server Component */}
<CollectionList />
</Suspense>
);
}

View File

@ -1,64 +0,0 @@
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, useRef, useState } from 'react';
import Caret from 'components/icons/caret-right';
import type { ListItem } from '.';
import { FilterItem } from './item';
export default function FilterItemDropdown({ list }: { list: ListItem[] }) {
const pathname = usePathname();
const searchParams = useSearchParams();
const [active, setActive] = useState('');
const [openSelect, setOpenSelect] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
setOpenSelect(false);
}
};
window.addEventListener('click', handleClickOutside);
return () => window.removeEventListener('click', handleClickOutside);
}, []);
useEffect(() => {
list.forEach((listItem: ListItem) => {
if (
('path' in listItem && pathname === listItem.path) ||
('slug' in listItem && searchParams.get('sort') === listItem.slug)
) {
setActive(listItem.title);
}
});
}, [pathname, list, searchParams]);
return (
<div className="relative" ref={ref}>
<div
onClick={() => {
setOpenSelect(!openSelect);
}}
className="flex w-full items-center justify-between rounded border border-black/30 px-4 py-2 text-sm dark:border-white/30"
>
<div>{active}</div>
<Caret className="h-4 rotate-90" />
</div>
{openSelect && (
<div
onClick={() => {
setOpenSelect(false);
}}
className="absolute z-40 w-full rounded-b-md bg-white p-4 shadow-md dark:bg-black"
>
{list.map((item: ListItem, i) => (
<FilterItem key={i} item={item} />
))}
</div>
)}
</div>
);
}

View File

@ -1,34 +0,0 @@
import { SortFilterItem } from 'lib/constants';
import FilterItemDropdown from './dropdown';
import { FilterItem } from './item';
export type ListItem = SortFilterItem | PathFilterItem;
export type PathFilterItem = { title: string; path: string };
function FilterItemList({ list }: { list: ListItem[] }) {
return (
<div className="hidden md:block">
{list.map((item: ListItem, i) => (
<FilterItem key={i} item={item} />
))}
</div>
);
}
export default function FilterList({ list, title }: { list: ListItem[]; title?: string }) {
return (
<>
<nav className="col-span-2 w-full flex-none px-6 py-2 md:py-4 md:pl-10">
{title ? (
<h3 className="hidden font-semibold text-black dark:text-white md:block">{title}</h3>
) : null}
<ul className="hidden md:block">
<FilterItemList list={list} />
</ul>
<ul className="md:hidden">
<FilterItemDropdown list={list} />
</ul>
</nav>
</>
);
}

View File

@ -1,67 +0,0 @@
'use client';
import clsx from 'clsx';
import { SortFilterItem } from 'lib/constants';
import { createUrl } from 'lib/utils';
import Link from 'next/link';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect, useState } from 'react';
import type { ListItem, PathFilterItem } from '.';
function PathFilterItem({ item }: { item: PathFilterItem }) {
const pathname = usePathname();
const searchParams = useSearchParams();
const [active, setActive] = useState(pathname === item.path);
useEffect(() => {
setActive(pathname === item.path);
}, [pathname, item.path]);
return (
<li className="mt-2 flex text-sm text-gray-400" key={item.title}>
<Link
href={createUrl(item.path, searchParams)}
className={clsx('w-full hover:text-gray-800 dark:hover:text-gray-100', {
'text-gray-600 dark:text-gray-400': !active,
'font-semibold text-black dark:text-white': active
})}
>
{item.title}
</Link>
</li>
);
}
function SortFilterItem({ item }: { item: SortFilterItem }) {
const pathname = usePathname();
const searchParams = useSearchParams();
const [active, setActive] = useState(searchParams.get('sort') === item.slug);
useEffect(() => {
setActive(searchParams.get('sort') === item.slug);
}, [searchParams, item.slug]);
const href =
item.slug && item.slug.length
? createUrl(pathname, new URLSearchParams({ sort: item.slug }))
: pathname;
return (
<li className="mt-2 flex text-sm text-gray-400" key={item.title}>
<Link
prefetch={false}
href={href}
className={clsx('w-full hover:text-gray-800 dark:hover:text-gray-100', {
'text-gray-600 dark:text-gray-400': !active,
'font-semibold text-black dark:text-white': active
})}
>
{item.title}
</Link>
</li>
);
}
export function FilterItem({ item }: { item: ListItem }) {
return 'path' in item ? <PathFilterItem item={item} /> : <SortFilterItem item={item} />;
}

View File

@ -1,14 +1,6 @@
import {
CarouselItemProps as ItemProps,
CarouselProps as Props,
} from 'components/modules/carousel/carousel'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
const Carousel = dynamic<Props>(() =>
import('components/modules/carousel/carousel').then((mod) => mod.Carousel) import { Carousel, CarouselItem } from 'components/modules/carousel/carousel'
)
const CarouselItem = dynamic<ItemProps>(() =>
import('components/modules/carousel/carousel').then((mod) => mod.CarouselItem)
)
const Card = dynamic(() => import('components/ui/card')) const Card = dynamic(() => import('components/ui/card'))
import Text from 'components/ui/text' import Text from 'components/ui/text'
@ -83,7 +75,6 @@ const BlurbSection = ({
{blurbs && ( {blurbs && (
<Carousel <Carousel
gliderClasses={'px-4 lg:px-8 2xl:px-16'} gliderClasses={'px-4 lg:px-8 2xl:px-16'}
gliderItemWrapperClasses={''}
hasDots={true} hasDots={true}
slidesToShow={2.2} slidesToShow={2.2}
responsive={{ responsive={{

View File

@ -1,16 +1,9 @@
import {
CarouselItemProps as ItemProps,
CarouselProps as Props,
} from 'components/modules/carousel/carousel'
import Text from 'components/ui/text' import Text from 'components/ui/text'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
const Carousel = dynamic<Props>(() =>
import('components/modules/carousel/carousel').then((mod) => mod.Carousel) import { Carousel, CarouselItem } from 'components/modules/carousel/carousel'
)
const CarouselItem = dynamic<ItemProps>(() =>
import('components/modules/carousel/carousel').then((mod) => mod.CarouselItem)
)
const ProductCard = dynamic(() => import('components/ui/product-card')) const ProductCard = dynamic(() => import('components/ui/product-card'))
const CategoryCard = dynamic(() => import('components/ui/category-card')) const CategoryCard = dynamic(() => import('components/ui/category-card'))

View File

@ -1,21 +1,12 @@
'use client' 'use client'
import { import { Carousel, CarouselItem } from 'components/modules/carousel/carousel'
CarouselItemProps as ItemProps,
CarouselProps as Props,
} from 'components/modules/carousel/carousel'
import SanityImage from 'components/ui/sanity-image' import SanityImage from 'components/ui/sanity-image'
import { Product } from "lib/storm/types/product" import { Product } from "lib/storm/types/product"
import { cn } from 'lib/utils' import { cn } from 'lib/utils'
import { useTranslations } from 'next-intl' import { useTranslations } from 'next-intl'
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
const ProductCard = dynamic(() => import('components/ui/product-card')) const ProductCard = dynamic(() => import('components/ui/product-card'))
const Carousel = dynamic<Props>(() =>
import('components/modules/carousel/carousel').then((mod) => mod.Carousel)
)
const CarouselItem = dynamic<ItemProps>(() =>
import('components/modules/carousel/carousel').then((mod) => mod.CarouselItem)
)
const Text = dynamic(() => import('components/ui/text')) const Text = dynamic(() => import('components/ui/text'))
interface ProductViewProps { interface ProductViewProps {
product: Product product: Product
@ -78,7 +69,9 @@ export default function ProductView({product, relatedProducts }: ProductViewProp
</div> </div>
<div className="flex flex-col col-span-1 mx-auto px-4 py-6 w-full h-auto lg:col-span-4 lg:py-0 lg:px-8 lg:pr-0 2xl:px-16 2xl:pr-0 lg:sticky lg:top-8 2xl:top-16"> <div className="flex flex-col col-span-1 mx-auto px-4 py-6 w-full h-auto lg:col-span-4 lg:py-0 lg:px-8 lg:pr-0 2xl:px-16 2xl:pr-0 lg:sticky lg:top-8 2xl:top-16">
<Text variant={'productHeading'}>
{product.name} {product.name}
</Text>
</div> </div>
</div> </div>

View File

@ -1 +1,2 @@
export { default } from './I18nWidget' export { default } from './locale-switcher';

View File

@ -20,6 +20,7 @@ type Variant =
| 'heading' | 'heading'
| 'body' | 'body'
| 'pageHeading' | 'pageHeading'
| 'productHeading'
| 'sectionHeading' | 'sectionHeading'
| 'label' | 'label'
| 'paragraph' | 'paragraph'
@ -39,6 +40,7 @@ const Text: FunctionComponent<TextProps> = ({
body: 'div', body: 'div',
heading: 'h1', heading: 'h1',
pageHeading: 'h1', pageHeading: 'h1',
productHeading: 'h1',
sectionHeading: 'h2', sectionHeading: 'h2',
listChildHeading: 'h3', listChildHeading: 'h3',
label: 'div', label: 'div',
@ -67,6 +69,8 @@ const Text: FunctionComponent<TextProps> = ({
variant === 'heading', variant === 'heading',
['max-w-prose text-3xl font-display font-bold leading-none md:text-4xl md:leading-none lg:leading-none lg:text-5xl']: ['max-w-prose text-3xl font-display font-bold leading-none md:text-4xl md:leading-none lg:leading-none lg:text-5xl']:
variant === 'pageHeading', variant === 'pageHeading',
['max-w-prose text-2xl font-display leading-none md:text-3xl md:leading-none lg:leading-none lg:text-4xl']:
variant === 'productHeading',
['max-w-prose text-2xl font-display font-bold leading-none md:text-3xl md:leading-none lg:leading-none lg:text-4xl']: ['max-w-prose text-2xl font-display font-bold leading-none md:text-3xl md:leading-none lg:leading-none lg:text-4xl']:
variant === 'sectionHeading', variant === 'sectionHeading',
['text-sm font-semibold leading-tight lg:text-base']: ['text-sm font-semibold leading-tight lg:text-base']:

View File

@ -7,11 +7,11 @@ import {
} from '../lib/sanity/queries' } from '../lib/sanity/queries'
const getQueryFromSlug = (slugArray: string[], locale: string) => { const getQueryFromSlug = (slugArray: string[], locale: string) => {
const docQuery = { const docQuery: { [index: string]: string } = {
homePage: groq`${homePageQuery}`, 'homePage': groq`${homePageQuery}`,
product: groq`${productQuery}`, 'product': groq`${productQuery}`,
category: groq`${categoryQuery}`, 'category': groq`${categoryQuery}`,
page: groq`${pageQuery}`, 'page': groq`${pageQuery}`,
} }
let docType = '' let docType = ''

View File

@ -5,7 +5,10 @@ export default createMiddleware({
locales: ['sv', 'en', 'nn'], locales: ['sv', 'en', 'nn'],
// If this locale is matched, pathnames work without a prefix (e.g. `/about`) // If this locale is matched, pathnames work without a prefix (e.g. `/about`)
defaultLocale: 'sv' defaultLocale: 'sv',
// Don't automatically detect locale, sv is default.
localeDetection: false
}); });
export const config = { export const config = {

View File

@ -10,7 +10,6 @@ module.exports = withBundleAnalyzer(
ignoreDuringBuilds: true ignoreDuringBuilds: true
}, },
experimental: { experimental: {
appDir: true,
scrollRestoration: true, scrollRestoration: true,
}, },
images: { images: {

View File

@ -40,7 +40,7 @@
"framer-motion": "^8.4.0", "framer-motion": "^8.4.0",
"is-empty-iterable": "^3.0.0", "is-empty-iterable": "^3.0.0",
"lucide-react": "^0.194.0", "lucide-react": "^0.194.0",
"next": "13.3.4", "next": "13.4.0",
"next-intl": "^2.13.1", "next-intl": "^2.13.1",
"next-sanity": "^4.2.0", "next-sanity": "^4.2.0",
"react": "18.2.0", "react": "18.2.0",
@ -62,7 +62,7 @@
"@vercel/git-hooks": "^1.0.0", "@vercel/git-hooks": "^1.0.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"eslint": "^8.35.0", "eslint": "^8.35.0",
"eslint-config-next": "^13.3.4", "eslint-config-next": "^13.4.0",
"eslint-config-prettier": "^8.6.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-unicorn": "^45.0.2", "eslint-plugin-unicorn": "^45.0.2",
"lint-staged": "^13.1.1", "lint-staged": "^13.1.1",

99
pnpm-lock.yaml generated
View File

@ -24,14 +24,14 @@ specifiers:
class-variance-authority: ^0.6.0 class-variance-authority: ^0.6.0
clsx: ^1.2.1 clsx: ^1.2.1
eslint: ^8.35.0 eslint: ^8.35.0
eslint-config-next: ^13.3.4 eslint-config-next: ^13.4.0
eslint-config-prettier: ^8.6.0 eslint-config-prettier: ^8.6.0
eslint-plugin-unicorn: ^45.0.2 eslint-plugin-unicorn: ^45.0.2
framer-motion: ^8.4.0 framer-motion: ^8.4.0
is-empty-iterable: ^3.0.0 is-empty-iterable: ^3.0.0
lint-staged: ^13.1.1 lint-staged: ^13.1.1
lucide-react: ^0.194.0 lucide-react: ^0.194.0
next: 13.3.4 next: 13.4.0
next-intl: ^2.13.1 next-intl: ^2.13.1
next-sanity: ^4.2.0 next-sanity: ^4.2.0
postcss: ^8.4.21 postcss: ^8.4.21
@ -66,9 +66,9 @@ dependencies:
framer-motion: 8.5.5_biqbaboplfbrettd7655fr4n2y framer-motion: 8.5.5_biqbaboplfbrettd7655fr4n2y
is-empty-iterable: 3.0.0 is-empty-iterable: 3.0.0
lucide-react: 0.194.0_react@18.2.0 lucide-react: 0.194.0_react@18.2.0
next: 13.3.4_5lnmxaau2bs7vcvowl2iwadrxa next: 13.4.0_5lnmxaau2bs7vcvowl2iwadrxa
next-intl: 2.13.1_next@13.3.4+react@18.2.0 next-intl: 2.13.1_next@13.4.0+react@18.2.0
next-sanity: 4.2.0_ng6tx5vtrrzblbayibx5wevyra next-sanity: 4.2.0_aehsfoaj4shzjn3qi6ffpl7sie
react: 18.2.0 react: 18.2.0
react-cookie: 4.1.1_react@18.2.0 react-cookie: 4.1.1_react@18.2.0
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
@ -88,7 +88,7 @@ devDependencies:
'@vercel/git-hooks': 1.0.0 '@vercel/git-hooks': 1.0.0
autoprefixer: 10.4.14_postcss@8.4.22 autoprefixer: 10.4.14_postcss@8.4.22
eslint: 8.38.0 eslint: 8.38.0
eslint-config-next: 13.3.4_ze6bmax3gcsfve3yrzu6npguhe eslint-config-next: 13.4.0_ze6bmax3gcsfve3yrzu6npguhe
eslint-config-prettier: 8.8.0_eslint@8.38.0 eslint-config-prettier: 8.8.0_eslint@8.38.0
eslint-plugin-unicorn: 45.0.2_eslint@8.38.0 eslint-plugin-unicorn: 45.0.2_eslint@8.38.0
lint-staged: 13.2.1 lint-staged: 13.2.1
@ -1091,18 +1091,18 @@ packages:
- utf-8-validate - utf-8-validate
dev: false dev: false
/@next/env/13.3.4: /@next/env/13.4.0:
resolution: {integrity: sha512-oTK/wRV2qga86m/4VdrR1+/56UA6U1Qv3sIgowB+bZjahniZLEG5BmmQjfoGv7ZuLXBZ8Eec6hkL9BqJcrEL2g==} resolution: {integrity: sha512-LKofmUuxwGXk2OZJSSJ2SlJE62s6z+56aRsze7chc5TPoVouLR9liTiSWxzYuVzuxk0ui2wtIjyR2tcgS1dIyw==}
dev: false dev: false
/@next/eslint-plugin-next/13.3.4: /@next/eslint-plugin-next/13.4.0:
resolution: {integrity: sha512-mvS+HafOPy31oJbAi920WJXMdjbyb4v5FAMr9PeGZfRIdEcsLkA3mU/ZvmwzovJgP3nAWw2e2yM8iIFW8VpvIA==} resolution: {integrity: sha512-ZqQi1slguDavpuNUcl9va8+WtHHpgymIW2g+4Gs9FdI+5rjAvrUqqjfCec2hi3Cjbbp7zULFQuAiPwASKHbrxw==}
dependencies: dependencies:
glob: 7.1.7 glob: 7.1.7
dev: true dev: true
/@next/swc-darwin-arm64/13.3.4: /@next/swc-darwin-arm64/13.4.0:
resolution: {integrity: sha512-vux7RWfzxy1lD21CMwZsy9Ej+0+LZdIIj1gEhVmzOQqQZ5N56h8JamrjIVCfDL+Lpj8KwOmFZbPHE8qaYnL2pg==} resolution: {integrity: sha512-C39AGL3ANXA+P3cFclQjFZaJ4RHPmuBhskmyy0N3VyCntDmRrNkS4aXeNY4Xwure9IL1nuw02D8bM55I+FsbuQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@ -1110,8 +1110,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-x64/13.3.4: /@next/swc-darwin-x64/13.4.0:
resolution: {integrity: sha512-1tb+6JT98+t7UIhVQpKL7zegKnCs9RKU6cKNyj+DYKuC/NVl49/JaIlmwCwK8Ibl+RXxJrK7uSXSIO71feXsgw==} resolution: {integrity: sha512-nj6nx6o7rnBXjo1woZFWLk7OUs7y0SQ0k65SX62kc88GqXtYi3BCqbBznjOX8qtrO//NmtAde/Jd5qkjSgINUQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@ -1119,8 +1119,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-gnu/13.3.4: /@next/swc-linux-arm64-gnu/13.4.0:
resolution: {integrity: sha512-UqcKkYTKslf5YAJNtZ5XV1D5MQJIkVtDHL8OehDZERHzqOe7jvy41HFto33IDPPU8gJiP5eJb3V9U26uifqHjw==} resolution: {integrity: sha512-FBYL7kpzI2KG3lv8gO4xVYmWcFohptjzD9RCLdXsAz+Kqz5VCJILF21DoRcz4Nwj/jMe0SO7l5kBVW4POl4EaQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -1128,8 +1128,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-musl/13.3.4: /@next/swc-linux-arm64-musl/13.4.0:
resolution: {integrity: sha512-HE/FmE8VvstAfehyo/XsrhGgz97cEr7uf9IfkgJ/unqSXE0CDshDn/4as6rRid74eDR8/exi7c2tdo49Tuqxrw==} resolution: {integrity: sha512-S3htBbcovnLMgVn0z1ThrP1iAeEM43fw8B7S3KyHTAGe0I21ww4rvUkLdgPCqLNvMpv899lmG7NU5rs6rTkGvg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -1137,8 +1137,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-gnu/13.3.4: /@next/swc-linux-x64-gnu/13.4.0:
resolution: {integrity: sha512-xU+ugaupGA4SL5aK1ZYEqVHrW3TPOhxVcpaJLfpANm2443J4GfxCmOacu9XcSgy5c51Mq7C9uZ1LODKHfZosRQ==} resolution: {integrity: sha512-H8GhCgQwFl6iWJ6azQ2tG/GY8BUg1nhPtg4Tp2kIPljdyQypTGJe8oRnPDx0N48WWvB2fNeA7LNEwzVuSidH2w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -1146,8 +1146,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-musl/13.3.4: /@next/swc-linux-x64-musl/13.4.0:
resolution: {integrity: sha512-cZvmf5KcYeTfIK6bCypfmxGUjme53Ep7hx94JJtGrYgCA1VwEuYdh+KouubJaQCH3aqnNE7+zGnVEupEKfoaaA==} resolution: {integrity: sha512-mztVybRPY39NfPOA3QrRQKzYuw7A/D8ElR6ruvM1cBsXMEfF5xTzdZqfTtrE/gNTPUFug9FJPpiRpkZ4mDUl8w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -1155,8 +1155,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-arm64-msvc/13.3.4: /@next/swc-win32-arm64-msvc/13.4.0:
resolution: {integrity: sha512-7dL+CAUAjmgnVbjXPIpdj7/AQKFqEUL3bKtaOIE1JzJ5UMHHAXCPwzQtibrsvQpf9MwcAmiv8aburD3xH1xf8w==} resolution: {integrity: sha512-mdVh/n0QqT2uXqn8kaTywUoLxY1OYqTpiKbt5b51pDwOStqgbIbqBqG0A7XDaiqWa7RKwllOYxPlPm16EDfWUA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@ -1164,8 +1164,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-ia32-msvc/13.3.4: /@next/swc-win32-ia32-msvc/13.4.0:
resolution: {integrity: sha512-qplTyzEl1vPkS+/DRK3pKSL0HeXrPHkYsV7U6gboHYpfqoHY+bcLUj3gwVUa9PEHRIoq4vXvPzx/WtzE6q52ng==} resolution: {integrity: sha512-GNRqT2mqxxH0x9VthFqziBj8X8HsoBUougmLe3kOouRq/jF73LpKXG0Qs2MYkylqvv/Wg31EYjFNcJnBi1Nimg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@ -1173,8 +1173,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-x64-msvc/13.3.4: /@next/swc-win32-x64-msvc/13.4.0:
resolution: {integrity: sha512-usdvZT7JHrTuXC+4OKN5mCzUkviFkCyJJTkEz8jhBpucg+T7s83e7owm3oNFzmj5iKfvxU2St6VkcnSgpFvEYA==} resolution: {integrity: sha512-0AkvhUBUqeb4WKN75IW1KjPkN3HazQFA0OpMuTK+6ptJUXMaMwDDcF3sIPCI741BJ2fpODB7BPM4C63hXWEypg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -3419,8 +3419,8 @@ packages:
source-map: 0.6.1 source-map: 0.6.1
dev: false dev: false
/eslint-config-next/13.3.4_ze6bmax3gcsfve3yrzu6npguhe: /eslint-config-next/13.4.0_ze6bmax3gcsfve3yrzu6npguhe:
resolution: {integrity: sha512-TknEcP+EdTqLvJ2zMY1KnWqcx8ZHl1C2Tjjbq3qmtWcHRU5oxe1PAsz3vrKG3NOzonSaPcB2SpCSfYqcgj6nfA==} resolution: {integrity: sha512-FkO3QRyUEKAHM4ie0xAcxo7fQ8gWevuLqgf6/g1Y6zWybqSa4FNeJr4hqqTbP25xIRgUUIPILBlx9RSH4C6+gQ==}
peerDependencies: peerDependencies:
eslint: ^7.23.0 || ^8.0.0 eslint: ^7.23.0 || ^8.0.0
typescript: '>=3.3.1' typescript: '>=3.3.1'
@ -3428,7 +3428,7 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@next/eslint-plugin-next': 13.3.4 '@next/eslint-plugin-next': 13.4.0
'@rushstack/eslint-patch': 1.2.0 '@rushstack/eslint-patch': 1.2.0
'@typescript-eslint/parser': 5.59.2_ze6bmax3gcsfve3yrzu6npguhe '@typescript-eslint/parser': 5.59.2_ze6bmax3gcsfve3yrzu6npguhe
eslint: 8.38.0 eslint: 8.38.0
@ -5201,7 +5201,7 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
/next-intl/2.13.1_next@13.3.4+react@18.2.0: /next-intl/2.13.1_next@13.4.0+react@18.2.0:
resolution: {integrity: sha512-3XUZ7c123QHgQGcz5UUkTtakJdLETBlcHcdHop43iVToOpsezxvMZW6jxWwuHTRvkElfNPy1fhHwzBo/mhVVvQ==} resolution: {integrity: sha512-3XUZ7c123QHgQGcz5UUkTtakJdLETBlcHcdHop43iVToOpsezxvMZW6jxWwuHTRvkElfNPy1fhHwzBo/mhVVvQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
peerDependencies: peerDependencies:
@ -5210,12 +5210,12 @@ packages:
dependencies: dependencies:
'@formatjs/intl-localematcher': 0.2.32 '@formatjs/intl-localematcher': 0.2.32
negotiator: 0.6.3 negotiator: 0.6.3
next: 13.3.4_5lnmxaau2bs7vcvowl2iwadrxa next: 13.4.0_5lnmxaau2bs7vcvowl2iwadrxa
react: 18.2.0 react: 18.2.0
use-intl: 2.13.1_react@18.2.0 use-intl: 2.13.1_react@18.2.0
dev: false dev: false
/next-sanity/4.2.0_ng6tx5vtrrzblbayibx5wevyra: /next-sanity/4.2.0_aehsfoaj4shzjn3qi6ffpl7sie:
resolution: {integrity: sha512-4GNEgXXDWPlvXqdJaAfKBR8BNvwQqUCynJ9GCgL6tVGcfZvcAImyZkzLTXj75PTZDPDcc7OfKHXg+XbmbUp7hA==} resolution: {integrity: sha512-4GNEgXXDWPlvXqdJaAfKBR8BNvwQqUCynJ9GCgL6tVGcfZvcAImyZkzLTXj75PTZDPDcc7OfKHXg+XbmbUp7hA==}
engines: {node: '>=16'} engines: {node: '>=16'}
peerDependencies: peerDependencies:
@ -5236,7 +5236,7 @@ packages:
'@sanity/webhook': 2.0.0 '@sanity/webhook': 2.0.0
'@types/styled-components': 5.1.26 '@types/styled-components': 5.1.26
groq: 3.9.1 groq: 3.9.1
next: 13.3.4_5lnmxaau2bs7vcvowl2iwadrxa next: 13.4.0_5lnmxaau2bs7vcvowl2iwadrxa
react: 18.2.0 react: 18.2.0
sanity: 3.9.1_inskn5v7aqlrr54h6fubgcms5y sanity: 3.9.1_inskn5v7aqlrr54h6fubgcms5y
styled-components: 5.3.10_7i5myeigehqah43i5u7wbekgba styled-components: 5.3.10_7i5myeigehqah43i5u7wbekgba
@ -5244,8 +5244,8 @@ packages:
- supports-color - supports-color
dev: false dev: false
/next/13.3.4_5lnmxaau2bs7vcvowl2iwadrxa: /next/13.4.0_5lnmxaau2bs7vcvowl2iwadrxa:
resolution: {integrity: sha512-sod7HeokBSvH5QV0KB+pXeLfcXUlLrGnVUXxHpmhilQ+nQYT3Im2O8DswD5e4uqbR8Pvdu9pcWgb1CbXZQZlmQ==} resolution: {integrity: sha512-y3E+2ZjiVrphkz7zcJvd2rEG6miOekI8krPfWV4AZZ9TaF0LDuFdP/f+RQ5M9wRvsz6GWw8k8+7jsO860GxSqg==}
engines: {node: '>=16.8.0'} engines: {node: '>=16.8.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -5265,7 +5265,7 @@ packages:
sass: sass:
optional: true optional: true
dependencies: dependencies:
'@next/env': 13.3.4 '@next/env': 13.4.0
'@swc/helpers': 0.5.1 '@swc/helpers': 0.5.1
busboy: 1.6.0 busboy: 1.6.0
caniuse-lite: 1.0.30001482 caniuse-lite: 1.0.30001482
@ -5273,16 +5273,17 @@ packages:
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
styled-jsx: 5.1.1_yxvpwo57iqrkjg2xxfiwjdgilu styled-jsx: 5.1.1_yxvpwo57iqrkjg2xxfiwjdgilu
zod: 3.21.4
optionalDependencies: optionalDependencies:
'@next/swc-darwin-arm64': 13.3.4 '@next/swc-darwin-arm64': 13.4.0
'@next/swc-darwin-x64': 13.3.4 '@next/swc-darwin-x64': 13.4.0
'@next/swc-linux-arm64-gnu': 13.3.4 '@next/swc-linux-arm64-gnu': 13.4.0
'@next/swc-linux-arm64-musl': 13.3.4 '@next/swc-linux-arm64-musl': 13.4.0
'@next/swc-linux-x64-gnu': 13.3.4 '@next/swc-linux-x64-gnu': 13.4.0
'@next/swc-linux-x64-musl': 13.3.4 '@next/swc-linux-x64-musl': 13.4.0
'@next/swc-win32-arm64-msvc': 13.3.4 '@next/swc-win32-arm64-msvc': 13.4.0
'@next/swc-win32-ia32-msvc': 13.3.4 '@next/swc-win32-ia32-msvc': 13.4.0
'@next/swc-win32-x64-msvc': 13.3.4 '@next/swc-win32-x64-msvc': 13.4.0
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
@ -7538,3 +7539,7 @@ packages:
compress-commons: 4.1.1 compress-commons: 4.1.1
readable-stream: 3.6.2 readable-stream: 3.6.2
dev: false dev: false
/zod/3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
dev: false

View File

@ -11,6 +11,18 @@ module.exports = {
], ],
safelist: ['outline-none'], safelist: ['outline-none'],
theme: { theme: {
fontSize: {
xs: '0.75rem',
sm: '0.875rem',
base: '1rem',
lg: '1.125rem',
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '1.75rem',
'4xl': '2rem',
'5xl': '3rem',
'6xl': '4rem',
},
extend: { extend: {
colors: { colors: {
app: '#ffffff', app: '#ffffff',