diff --git a/.env.example b/.env.example
index 9ac741a18..3f31f2612 100644
--- a/.env.example
+++ b/.env.example
@@ -16,6 +16,16 @@ VERCEL_GIT_COMMIT_AUTHOR_LOGIN=""
VERCEL_GIT_COMMIT_AUTHOR_NAME=""
VERCEL_GIT_PULL_REQUEST_ID=""
+# Sanity
+NEXT_PUBLIC_SANITY_PROJECT_ID=""
+NEXT_PUBLIC_SANITY_DATASET=""
+NEXT_PUBLIC_SANITY_API_VERSION=""
+
+# Preview
+SANITY_API_READ_TOKEN=""
+SANITY_WEBHOOK_SECRET=""
+
+# Site
TWITTER_CREATOR="@kodamera"
TWITTER_SITE="https://kodamera.se"
SITE_NAME="KM Storefront"
diff --git a/app/[locale]/globals.css b/app/[locale]/globals.css
index d66461eab..2d7943a69 100644
--- a/app/[locale]/globals.css
+++ b/app/[locale]/globals.css
@@ -2,9 +2,76 @@
@tailwind components;
@tailwind utilities;
-@supports (font: -apple-system-body) and (-webkit-appearance: none) {
- img[loading='lazy'] {
- clip-path: inset(0.6px);
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 0 0% 3.9%;
+ --foreground: 0 0% 98%;
+
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
}
}
@@ -28,7 +95,7 @@
html,
body {
- @apply font-sans h-full bg-white text-high-contrast;
+ @apply h-full bg-white font-sans text-high-contrast;
box-sizing: border-box;
touch-action: manipulation;
@@ -74,11 +141,11 @@ body {
}
.glider-dots {
- @apply flex !space-x-[2px] !mt-8;
+ @apply !mt-8 flex !space-x-[2px];
}
.glider-dot {
- @apply !m-0 !rounded-none !w-12 !h-4 !bg-transparent after:content-[''] after:block after:w-12 after:h-[3px] after:bg-ui-border 2xl:!w-16 2xl:after:w-16;
+ @apply !m-0 !h-4 !w-12 !rounded-none !bg-transparent after:block after:h-[3px] after:w-12 after:bg-ui-border after:content-[''] 2xl:!w-16 2xl:after:w-16;
}
.glider-dot.active {
@@ -86,17 +153,17 @@ body {
}
.glider-prev {
- @apply text-high-contrast !right-12 !-top-10 !left-auto lg:!right-16 lg:!-top-12 2xl:!-top-16 2xl:!right-[100px] !transition-transform !duration-100 hover:!text-high-contrast hover:scale-110;
+ @apply !-top-10 !left-auto !right-12 text-high-contrast !transition-transform !duration-100 hover:scale-110 hover:!text-high-contrast lg:!-top-10 lg:!right-16 2xl:!-top-12 2xl:!right-[100px];
}
.glider-next {
- @apply text-high-contrast !right-4 !-top-10 lg:!right-8 lg:!-top-12 2xl:!-top-16 2xl:!right-16 !transition-transform !duration-100 hover:!text-high-contrast hover:scale-110;
+ @apply !-top-10 !right-4 text-high-contrast !transition-transform !duration-100 hover:scale-110 hover:!text-high-contrast lg:!-top-10 lg:!right-8 2xl:!-top-12 2xl:!right-16;
}
.pdp .glider-prev {
- @apply text-high-contrast absolute !left-4 !top-1/2 !transition-transform !duration-100 hover:!text-high-contrast hover:scale-100 lg:hidden;
+ @apply absolute !left-4 !top-1/2 text-high-contrast !transition-transform !duration-100 hover:scale-100 hover:!text-high-contrast lg:hidden;
}
.pdp .glider-next {
- @apply text-high-contrast absolute !right-4 !top-1/2 !transition-transform !duration-100 hover:!text-high-contrast hover:scale-100 lg:hidden;
-}
\ No newline at end of file
+ @apply absolute !right-4 !top-1/2 text-high-contrast !transition-transform !duration-100 hover:scale-100 hover:!text-high-contrast lg:hidden;
+}
diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx
index e8999055b..0546880c4 100644
--- a/app/[locale]/layout.tsx
+++ b/app/[locale]/layout.tsx
@@ -1,9 +1,10 @@
import Footer from 'components/layout/footer/footer';
import Header from 'components/layout/header/header';
-import { useLocale } from 'next-intl';
+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';
export const metadata = {
@@ -32,30 +33,34 @@ const inter = Inter({
variable: '--font-inter'
});
-// export function generateStaticParams() {
-// return supportedLanguages.locales.map((locale) => ({ locale: locale.id }));
-// }
+export function generateStaticParams() {
+ return supportedLanguages.locales.map((locale) => ({ locale: locale.id }));
+}
-export default function LocaleLayout({
- children,
- params
-}: {
+interface LocaleLayoutProps {
children: ReactNode;
- params: { locale: string };
-}) {
- const locale = useLocale();
+ params: {
+ locale: string;
+ };
+}
- // Show a 404 error if the user requests an unknown locale
- if (params.locale !== locale) {
+export default async function LocaleLayout({ children, params: { locale } }: LocaleLayoutProps) {
+ let messages;
+
+ try {
+ messages = (await import(`../../messages/${locale}.json`)).default;
+ } catch (error) {
notFound();
}
return (
-
- {children}
-
+
+
+ {children}
+
+
);
diff --git a/app/[locale]/product/[slug]/page.tsx b/app/[locale]/product/[slug]/page.tsx
index 19083ed90..db6ebef81 100644
--- a/app/[locale]/product/[slug]/page.tsx
+++ b/app/[locale]/product/[slug]/page.tsx
@@ -55,8 +55,6 @@ export async function generateMetadata({
export default async function ProductPage({ params }: ProductPageParams) {
const product = await clientFetch(productQuery, params);
- console.log(params);
-
if (!product) return notFound();
const productJsonLd = {
diff --git a/components.json b/components.json
new file mode 100644
index 000000000..f8b0c79bc
--- /dev/null
+++ b/components.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils"
+ }
+}
\ No newline at end of file
diff --git a/components/cart/add-to-cart.tsx b/components/cart/add-to-cart.tsx
index f9d21c0fd..704fb2a41 100644
--- a/components/cart/add-to-cart.tsx
+++ b/components/cart/add-to-cart.tsx
@@ -1,6 +1,6 @@
'use client';
-import { PlusIcon } from '@heroicons/react/24/outline';
+import { PlusIcon } from '@radix-ui/react-icons';
import clsx from 'clsx';
import { addItem } from 'components/cart/actions';
import LoadingDots from 'components/loading-dots';
@@ -39,7 +39,7 @@ export function AddToCart({
onClick={() => {
// Safeguard in case someone messes with `disabled` in devtools.
if (!availableForSale || !selectedVariantId) return;
-
+ // @ts-ignore
startTransition(async () => {
const error = await addItem(selectedVariantId);
@@ -52,7 +52,7 @@ export function AddToCart({
});
}}
className={clsx(
- 'relative flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90',
+ 'bg-blue-600 relative flex w-full items-center justify-center rounded-full p-4 tracking-wide text-white hover:opacity-90',
{
'cursor-not-allowed opacity-60 hover:opacity-60': !availableForSale || !selectedVariantId,
'cursor-not-allowed': isPending
diff --git a/components/cart/close-cart.tsx b/components/cart/close-cart.tsx
index 515b94843..9907201e8 100644
--- a/components/cart/close-cart.tsx
+++ b/components/cart/close-cart.tsx
@@ -1,10 +1,10 @@
-import { XMarkIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
+import CloseIcon from 'components/icons/close';
export default function CloseCart({ className }: { className?: string }) {
return (
-
+
);
}
diff --git a/components/cart/delete-item-button.tsx b/components/cart/delete-item-button.tsx
index 605adcf51..1005cb28a 100644
--- a/components/cart/delete-item-button.tsx
+++ b/components/cart/delete-item-button.tsx
@@ -1,4 +1,4 @@
-import { XMarkIcon } from '@heroicons/react/24/outline';
+import CloseIcon from 'components/icons/close';
import LoadingDots from 'components/loading-dots';
import { useRouter } from 'next/navigation';
@@ -15,6 +15,7 @@ export default function DeleteItemButton({ item }: { item: CartItem }) {
);
diff --git a/components/cart/edit-item-quantity-button.tsx b/components/cart/edit-item-quantity-button.tsx
index e846196ca..a09102315 100644
--- a/components/cart/edit-item-quantity-button.tsx
+++ b/components/cart/edit-item-quantity-button.tsx
@@ -1,9 +1,10 @@
import { useRouter } from 'next/navigation';
import { useTransition } from 'react';
-import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
import { removeItem, updateItemQuantity } from 'components/cart/actions';
+import MinusIcon from 'components/icons/minus';
+import PlusIcon from 'components/icons/plus';
import LoadingDots from 'components/loading-dots';
import type { CartItem } from 'lib/shopify/types';
@@ -21,6 +22,7 @@ export default function EditItemQuantityButton({