mirror of
https://github.com/vercel/commerce.git
synced 2025-05-12 12:47:50 +00:00
Squashed commit of the following:
commit b10e930476d8bbe82f679f3ae8ed44cea733898f Merge: a2c34aa 8531476 Author: Sammii <sammii.h@icloud.com> Date: Wed Apr 24 20:05:09 2024 +0100 Merge pull request #1 from Sammii-HK/feat/restyle-store Feat/restyle store commit 85314765721447f5612cb59b7e668653606e7b7b Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 20:03:12 2024 +0100 responsive design app page carousel commit d5563a7355c2581c29ebf3f11799e1047c0c29dd Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 19:57:47 2024 +0100 updating mobile carousel tile size commit 9976d0d427ea92dab510bface5da9a4e89feabee Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 19:30:07 2024 +0100 updating combox import commit f6a365df8239d26d5feb37cf53d43632ab7c90c4 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 19:16:08 2024 +0100 styling label price commit cf89e3b0648bb07ed038f872183bd7ecea064df0 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 19:08:16 2024 +0100 styling accordians and drop downs commit 49f3776ba8894d5fd2aa6b2fea0632f73d6ae093 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 18:53:14 2024 +0100 updating search and carousel styling commit 7666a25f918a1b53a088dd9240077af5b9013712 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 18:37:29 2024 +0100 styling product description commit b8280101ad367abfe48aed4fee8ef32ed1fd5d67 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 18:18:11 2024 +0100 removing `-full` from all rounded elements commit 77480edd79327898aa2c417d34d231db1d593736 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 18:15:27 2024 +0100 styling cart component commit 3ec0c4d5679b20c66442e5002225cbefe927a13e Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 18:06:53 2024 +0100 updating styling commit efcab218934934edea629406b823fd2b2ac3d193 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 18:06:43 2024 +0100 updating carousel for correct types commit b14934af8489a6294f1c47a3a41d40d0a7565550 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:57:31 2024 +0100 styling tile commit 0c72e76bad994317d5e39faedfc4aade9a368fe7 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:56:50 2024 +0100 styling label commit ca24a30543ddfbdd345ac405b14e0541a9af1020 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:54:54 2024 +0100 update main app layout for dark view commit 57c1c4cac9a15c2091f652f53f7bb85a6a5b613a Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:52:53 2024 +0100 commenting out weight li on description content commit d2f0ac2041b04117e24db0316b470646489aca07 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:52:02 2024 +0100 updating carousel to new style commit 545717006d7f25f36e71b67873d01316aeeafcf9 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:50:55 2024 +0100 updating getAllLiveProducts to handle no param commit 0bf97ecdf69b4f7a4aa00c090e09e613fa1aba8a Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:50:39 2024 +0100 styling and adjusting footer & navbar commit 18200b4e848affe01ca9014bdf8ac841ce176114 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:50:00 2024 +0100 updating cart to bag commit 82c30cdda8d38b3111b539a34127155bb82612d5 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:48:32 2024 +0100 SKUs page file check commit e88a45b9d51096b3250062708d5ef1a49138258c Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:47:30 2024 +0100 updatibng Prose import path commit 5e81519f983e58758d865b2905f279f34afb67a4 Author: Samantha Kellow <sammii.h@icloud.com> Date: Wed Apr 24 17:45:58 2024 +0100 updating app page metadata commit 9c9a5c035b245d8811a601ba2dd134c5c5f124d7 Author: Samantha Kellow <sammii.h@icloud.com> Date: Tue Apr 23 21:25:05 2024 +0100 making borders muted commit 8a40e08e41a7fdad172e5ffaebb7f791ec66f4e5 Author: Samantha Kellow <sammii.h@icloud.com> Date: Tue Apr 23 20:37:35 2024 +0100 moving ui components to own folder 🧹
This commit is contained in:
parent
a2c34aa970
commit
08b3b820e4
@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||||
import { ChevronDown } from "lucide-react"
|
import { ChevronDown } from "lucide-react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ const AccordionItem = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<AccordionPrimitive.Item
|
<AccordionPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("border-b", className)}
|
className={cn("border-b border-neutral-600", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
@ -52,9 +52,10 @@ const AccordionContent = React.forwardRef<
|
|||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="pb-4 pt-0">{children}</div>
|
<div className="pb-4 pt-0 text-neutral-50">{children}</div>
|
||||||
</AccordionPrimitive.Content>
|
</AccordionPrimitive.Content>
|
||||||
))
|
))
|
||||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||||
|
|
||||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger }
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
import Prose from 'components/prose';
|
import Prose from 'components/ui/prose';
|
||||||
import { getPage } from 'lib/shopify';
|
import { getPage } from 'lib/shopify';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
|||||||
return (
|
return (
|
||||||
<html lang="en" className={inter.variable}>
|
<html lang="en" className={inter.variable}>
|
||||||
<meta name="google-site-verification" content="nU-MGbKTYYrFX-Yh7SIgQ1szTEoYTF08PZU1nihRS6g" />
|
<meta name="google-site-verification" content="nU-MGbKTYYrFX-Yh7SIgQ1szTEoYTF08PZU1nihRS6g" />
|
||||||
<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">
|
<body className="bg-neutral-950 text-neutral-50">
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
|
26
app/page.tsx
26
app/page.tsx
@ -1,14 +1,11 @@
|
|||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Carousel } from 'components/carousel';
|
|
||||||
import { GridTileImage } from 'components/grid/tile';
|
|
||||||
import Footer from 'components/layout/footer';
|
import Footer from 'components/layout/footer';
|
||||||
import Link from 'next/link';
|
import { Carousel } from 'components/ui/carousel';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
description: 'scape²: art for the wall & wardrobe.',
|
description: 'scape²: consciously created, art to wear, altering angles, by independent artist Sammii.',
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: 'website'
|
type: 'website'
|
||||||
},
|
},
|
||||||
@ -18,9 +15,13 @@ export default async function HomePage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<Carousel />
|
|
||||||
<div className='container grid justify-around my-4 grid-cols-1 md:grid-cols-2 items-center justify-items-center'>
|
<div className='container grid justify-around my-4 grid-cols-1 items-center justify-items-center'>
|
||||||
<Link className='h-full w-full md:w-10/12 flex items-center justify-center justify-items-center' href="/wall">
|
<Carousel collection={undefined} />
|
||||||
|
<Carousel collection='flower' />
|
||||||
|
<Carousel collection='foliage' />
|
||||||
|
<Carousel collection='nature' />
|
||||||
|
{/* <Link className='h-full w-full md:w-10/12 flex items-center justify-center justify-items-center' href="/wall">
|
||||||
<Button className='py-3 w-full m-3 bg-transparent' variant={'dark'}>
|
<Button className='py-3 w-full m-3 bg-transparent' variant={'dark'}>
|
||||||
<h2 className='absolute z-50 text-3xl text-white'>Art for the Wall</h2>
|
<h2 className='absolute z-50 text-3xl text-white'>Art for the Wall</h2>
|
||||||
<GridTileImage width={500} height={500} src="/_next/image?url=https%3A%2F%2Fcdn.shopify.com%2Fs%2Ffiles%2F1%2F0587%2F3251%2F1370%2Fproducts%2F2f6e3c70-ed36-4034-b670-7eda7d47afe1.jpg%3Fv%3D1700689495&w=1080&q=75" alt='Art for the Wall' />
|
<GridTileImage width={500} height={500} src="/_next/image?url=https%3A%2F%2Fcdn.shopify.com%2Fs%2Ffiles%2F1%2F0587%2F3251%2F1370%2Fproducts%2F2f6e3c70-ed36-4034-b670-7eda7d47afe1.jpg%3Fv%3D1700689495&w=1080&q=75" alt='Art for the Wall' />
|
||||||
@ -31,11 +32,12 @@ export default async function HomePage() {
|
|||||||
<h2 className='absolute z-50 text-3xl text-white'>Art for the Wardrobe</h2>
|
<h2 className='absolute z-50 text-3xl text-white'>Art for the Wardrobe</h2>
|
||||||
<GridTileImage width={500} height={500} src="/_next/image?url=https%3A%2F%2Fcdn.shopify.com%2Fs%2Ffiles%2F1%2F0587%2F3251%2F1370%2Ffiles%2F2BCB00FC-35D9-4181-8F11-D6F4569D6446.jpg%3Fv%3D1700746166&w=1080&q=75" alt='Art for the Wardrobe' />
|
<GridTileImage width={500} height={500} src="/_next/image?url=https%3A%2F%2Fcdn.shopify.com%2Fs%2Ffiles%2F1%2F0587%2F3251%2F1370%2Ffiles%2F2BCB00FC-35D9-4181-8F11-D6F4569D6446.jpg%3Fv%3D1700746166&w=1080&q=75" alt='Art for the Wardrobe' />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link> */}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<Suspense>
|
</Suspense>
|
||||||
<Footer />
|
<Suspense>
|
||||||
</Suspense>
|
<Footer />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -82,7 +82,7 @@ export default async function ProductPage({ params }: { params: { handle: string
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="mx-auto max-w-screen-2xl px-4">
|
<div className="mx-auto max-w-screen-2xl px-4">
|
||||||
<div className="rounded-lg border border-neutral-200 bg-white p-8 px-4 dark:border-neutral-800 dark:bg-black md:p-12 lg:grid lg:grid-cols-6">
|
<div className="rounded-lg border border-neutral-900 p-8 px-4 dark:border-neutral-800 dark:bg-black md:p-12 lg:grid lg:grid-cols-6">
|
||||||
<div className="lg:col-span-3 flex justify-center">
|
<div className="lg:col-span-3 flex justify-center">
|
||||||
<Gallery
|
<Gallery
|
||||||
images={product.images.map((image: Image) => ({
|
images={product.images.map((image: Image) => ({
|
||||||
|
@ -31,23 +31,40 @@ export default async function SKUCheckPage() {
|
|||||||
dbxFiles = await dbx.filesListFolder({
|
dbxFiles = await dbx.filesListFolder({
|
||||||
path: '/scape squared/004 print ready - print files'
|
path: '/scape squared/004 print ready - print files'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// sabotage?
|
||||||
|
// dbxFiles.result.entries = dbxFiles.result.entries.filter(file => file.name !== "SCSQ300262_NECK.png");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dbxError = e + ""
|
dbxError = e + ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// sabotage?
|
|
||||||
// dbxFiles.result.entries.splice(5, 1);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{products.map((product) => {
|
{products.map((product) => {
|
||||||
const skus = createProductSKUs(product.title);
|
const skus = createProductSKUs(product.title);
|
||||||
|
|
||||||
|
if (!skus) return <div key={product.id} className="m-10 bg-red-300 ">SKU error for product {product.title}</div>
|
||||||
|
|
||||||
// sabotage?
|
// sabotage?
|
||||||
// product.variants[3]!.sku = 'SCSQ10001_STTU781_M_C002_B_NT';
|
// product.variants[3]!.sku = 'SCSQ10001_STTU781_M_C002_B_NT';
|
||||||
|
|
||||||
|
const productCode = skus[0]!.split('_')[0]
|
||||||
|
const productBase = productCode!.slice(0, (productCode!.length -1))
|
||||||
|
|
||||||
const productFiles = dbxFiles?.result.entries
|
const productFiles = dbxFiles?.result.entries
|
||||||
.filter((file) => file.name.startsWith(skus[0]!.split('_')[0]!))
|
.filter((file) => file.name.startsWith(productBase!))
|
||||||
|
|
||||||
|
const productFileNames = productFiles?.map(file => file.name);
|
||||||
|
|
||||||
|
const sizeDigits = ["1", "2", "3"];
|
||||||
|
const sideSuffixes = ["_NECK.png", "_BACK.png"];
|
||||||
|
|
||||||
|
const expectedFiles = sizeDigits.flatMap((sizeDigit) =>
|
||||||
|
sideSuffixes.flatMap((sideSuffix) => productBase + sizeDigit + sideSuffix)
|
||||||
|
);
|
||||||
|
|
||||||
|
const missingFiles = expectedFiles.filter(expected => !productFileNames?.includes(expected));
|
||||||
|
const extraFiles = productFileNames?.filter(filename => !expectedFiles.includes(filename));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={product.id} className="mt-10 lg:grid lg:grid-cols-12 gap-x-4 gap-y-1">
|
<div key={product.id} className="mt-10 lg:grid lg:grid-cols-12 gap-x-4 gap-y-1">
|
||||||
@ -55,10 +72,15 @@ export default async function SKUCheckPage() {
|
|||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'mb-4 lg:col-span-4 lg:col-start-3 rounded ' +
|
'mb-4 lg:col-span-4 lg:col-start-3 rounded ' +
|
||||||
(productFiles?.length !== 2 ? 'bg-amber-300' : '')
|
(missingFiles?.length > 0 ? 'bg-amber-300' : '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Dropbox: {productFiles?.map(file => file.name).join(", ")}{dbxError}
|
Dropbox:{' '}
|
||||||
|
{missingFiles.length === 0
|
||||||
|
? '✅ All expected files found'
|
||||||
|
: '⛔️ Missing files: ' + missingFiles.join(', ')}
|
||||||
|
{extraFiles && <div className='text-sm'>(Extra files: {extraFiles.join(", ")})</div>}
|
||||||
|
{dbxError}
|
||||||
</div>
|
</div>
|
||||||
{product.variants.map((variant, i) => (
|
{product.variants.map((variant, i) => (
|
||||||
<>
|
<>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Combox } from "components/combox";
|
|
||||||
import { ProductSKUs } from "components/product/sku-generator";
|
import { ProductSKUs } from "components/product/sku-generator";
|
||||||
|
import { Combox } from "components/ui/combox";
|
||||||
import { collectionsSKUs, garmentHandleKeys } from "constants/sku";
|
import { collectionsSKUs, garmentHandleKeys } from "constants/sku";
|
||||||
import { capitalizeFirstLetter, copyText } from "lib/helpers/actions";
|
import { capitalizeFirstLetter, copyText } from "lib/helpers/actions";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import { getCollectionProducts } from 'lib/shopify';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { GridTileImage } from './grid/tile';
|
|
||||||
|
|
||||||
export async function Carousel() {
|
|
||||||
// Collections that start with `hidden-*` are hidden from the search page.
|
|
||||||
const products = await getCollectionProducts({ collection: 'hidden-homepage-carousel' });
|
|
||||||
|
|
||||||
if (!products?.length) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className=" w-full overflow-x-auto pb-6 pt-1">
|
|
||||||
<div className="flex animate-carousel gap-4">
|
|
||||||
{[...products, ...products].map((product, i) => (
|
|
||||||
<Link
|
|
||||||
key={`${product.handle}${i}`}
|
|
||||||
href={`/products/${product.handle}`}
|
|
||||||
className="h-[30vh] w-2/3 flex-none md:w-1/3"
|
|
||||||
>
|
|
||||||
<GridTileImage
|
|
||||||
alt={product.title}
|
|
||||||
label={{
|
|
||||||
title: product.title,
|
|
||||||
amount: product.priceRange.maxVariantPrice.amount,
|
|
||||||
currencyCode: product.priceRange.maxVariantPrice.currencyCode
|
|
||||||
}}
|
|
||||||
src={product.featuredImage?.url}
|
|
||||||
width={600}
|
|
||||||
height={600}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -3,7 +3,7 @@
|
|||||||
import { PlusIcon } from '@heroicons/react/24/outline';
|
import { PlusIcon } from '@heroicons/react/24/outline';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { addItem } from 'components/cart/actions';
|
import { addItem } from 'components/cart/actions';
|
||||||
import LoadingDots from 'components/loading-dots';
|
import LoadingDots from 'components/ui/loading-dots';
|
||||||
import { ProductVariant } from 'lib/shopify/types';
|
import { ProductVariant } from 'lib/shopify/types';
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
import { useEffect, useState, useTransition } from 'react';
|
import { useEffect, useState, useTransition } from 'react';
|
||||||
@ -50,7 +50,7 @@ export function AddToCart({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'relative flex w-full items-center justify-center rounded-full bg-gray-600 p-4 tracking-wide text-white hover:opacity-90',
|
'relative flex w-full items-center justify-center rounded bg-neutral-800 p-4 tracking-wide text-white hover:opacity-90',
|
||||||
{
|
{
|
||||||
'cursor-not-allowed opacity-60': !availableForSale,
|
'cursor-not-allowed opacity-60': !availableForSale,
|
||||||
'cursor-not-allowed': isPending
|
'cursor-not-allowed': isPending
|
||||||
|
@ -3,7 +3,7 @@ import clsx from 'clsx';
|
|||||||
|
|
||||||
export default function CloseCart({ className }: { className?: string }) {
|
export default function CloseCart({ className }: { className?: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-black transition-colors dark:border-neutral-700 dark:text-white">
|
<div className="relative flex h-11 w-11 items-center justify-center text-neutral-200 transition-colors dark:border-neutral-700 dark:text-white">
|
||||||
<XMarkIcon className={clsx('h-6 transition-all ease-in-out hover:scale-110 ', className)} />
|
<XMarkIcon className={clsx('h-6 transition-all ease-in-out hover:scale-110 ', className)} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||||
import LoadingDots from 'components/loading-dots';
|
import LoadingDots from 'components/ui/loading-dots';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@ -28,7 +28,7 @@ export default function DeleteItemButton({ item }: { item: CartItem }) {
|
|||||||
}}
|
}}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'ease flex h-[17px] w-[17px] items-center justify-center rounded-full bg-neutral-500 transition-all duration-200',
|
'ease flex h-[17px] w-[17px] items-center justify-center rounded bg-neutral-500 transition-all duration-200',
|
||||||
{
|
{
|
||||||
'cursor-not-allowed px-0': isPending
|
'cursor-not-allowed px-0': isPending
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { useTransition } from 'react';
|
|||||||
import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline';
|
import { MinusIcon, PlusIcon } from '@heroicons/react/24/outline';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { removeItem, updateItemQuantity } from 'components/cart/actions';
|
import { removeItem, updateItemQuantity } from 'components/cart/actions';
|
||||||
import LoadingDots from 'components/loading-dots';
|
import LoadingDots from 'components/ui/loading-dots';
|
||||||
import type { CartItem } from 'lib/shopify/types';
|
import type { CartItem } from 'lib/shopify/types';
|
||||||
|
|
||||||
export default function EditItemQuantityButton({
|
export default function EditItemQuantityButton({
|
||||||
@ -41,7 +41,7 @@ export default function EditItemQuantityButton({
|
|||||||
}}
|
}}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'ease flex h-full min-w-[36px] max-w-[36px] flex-none items-center justify-center rounded-full px-2 transition-all duration-200 hover:border-neutral-800 hover:opacity-80',
|
'ease flex h-full min-w-[36px] max-w-[36px] flex-none items-center justify-center rounded px-2 transition-all duration-200 hover:border-neutral-800 hover:opacity-80',
|
||||||
{
|
{
|
||||||
'cursor-not-allowed': isPending,
|
'cursor-not-allowed': isPending,
|
||||||
'ml-auto': type === 'minus'
|
'ml-auto': type === 'minus'
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Dialog, Transition } from '@headlessui/react';
|
import { Dialog, Transition } from '@headlessui/react';
|
||||||
import { ShoppingCartIcon } from '@heroicons/react/24/outline';
|
import { ShoppingBagIcon } from '@heroicons/react/24/outline';
|
||||||
import Price from 'components/price';
|
import Price from 'components/ui/price';
|
||||||
import { DEFAULT_OPTION } from 'lib/constants';
|
import { DEFAULT_OPTION } from 'lib/constants';
|
||||||
import type { Cart } from 'lib/shopify/types';
|
import type { Cart } from 'lib/shopify/types';
|
||||||
import { createUrl } from 'lib/utils';
|
import { createUrl } from 'lib/utils';
|
||||||
@ -64,9 +64,9 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
leaveFrom="translate-x-0"
|
leaveFrom="translate-x-0"
|
||||||
leaveTo="translate-x-full"
|
leaveTo="translate-x-full"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="fixed bottom-0 right-0 top-0 flex h-full w-full flex-col border-l border-neutral-200 bg-white/80 p-6 text-black backdrop-blur-xl dark:border-neutral-700 dark:bg-black/80 dark:text-white md:w-[390px]">
|
<Dialog.Panel className="fixed bottom-0 right-0 top-0 flex h-full w-full flex-col border-l border-neutral-200 bg-neutral-111/80 p-6 text-neutral-10 backdrop-blur-xl dark:border-neutral-700 dark:bg-black/80 dark:text-white md:w-[390px]">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-lg font-semibold">My Cart</p>
|
<p className="text-lg font-semibold">Your Bag</p>
|
||||||
|
|
||||||
<button aria-label="Close cart" onClick={closeCart}>
|
<button aria-label="Close cart" onClick={closeCart}>
|
||||||
<CloseCart />
|
<CloseCart />
|
||||||
@ -75,8 +75,8 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
|
|
||||||
{!cart || cart.lines.length === 0 ? (
|
{!cart || cart.lines.length === 0 ? (
|
||||||
<div className="mt-20 flex w-full flex-col items-center justify-center overflow-hidden">
|
<div className="mt-20 flex w-full flex-col items-center justify-center overflow-hidden">
|
||||||
<ShoppingCartIcon className="h-16" />
|
<ShoppingBagIcon className="h-16" />
|
||||||
<p className="mt-6 text-center text-2xl font-bold">Your cart is empty.</p>
|
<p className="mt-6 text-center text-2xl font-bold">Your bag is empty. 😔</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full flex-col justify-between overflow-hidden p-1">
|
<div className="flex h-full flex-col justify-between overflow-hidden p-1">
|
||||||
@ -127,7 +127,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
{item.merchandise.product.title}
|
{item.merchandise.product.title}
|
||||||
</span>
|
</span>
|
||||||
{item.merchandise.title !== DEFAULT_OPTION ? (
|
{item.merchandise.title !== DEFAULT_OPTION ? (
|
||||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">
|
<p className="text-md text-neutral-400 dark:text-neutral-400">
|
||||||
{item.merchandise.title}
|
{item.merchandise.title}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
@ -139,7 +139,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
amount={item.cost.totalAmount.amount}
|
amount={item.cost.totalAmount.amount}
|
||||||
currencyCode={item.cost.totalAmount.currencyCode}
|
currencyCode={item.cost.totalAmount.currencyCode}
|
||||||
/>
|
/>
|
||||||
<div className="ml-auto flex h-9 flex-row items-center rounded-full border border-neutral-200 dark:border-neutral-700">
|
<div className="ml-auto flex h-9 flex-row items-center rounded border border-neutral-200 dark:border-neutral-700">
|
||||||
<EditItemQuantityButton item={item} type="minus" />
|
<EditItemQuantityButton item={item} type="minus" />
|
||||||
<p className="w-6 text-center ">
|
<p className="w-6 text-center ">
|
||||||
<span className="w-full text-sm">{item.quantity}</span>
|
<span className="w-full text-sm">{item.quantity}</span>
|
||||||
@ -154,12 +154,12 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
</ul>
|
</ul>
|
||||||
<div className="py-4 text-sm text-neutral-500 dark:text-neutral-400">
|
<div className="py-4 text-sm text-neutral-500 dark:text-neutral-400">
|
||||||
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 dark:border-neutral-700">
|
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 dark:border-neutral-700">
|
||||||
<p>Taxes</p>
|
{/* <p>Taxes</p>
|
||||||
<Price
|
<Price
|
||||||
className="text-right text-base text-black dark:text-white"
|
className="text-right text-base text-black dark:text-white"
|
||||||
amount={cart.cost.totalTaxAmount.amount}
|
amount={cart.cost.totalTaxAmount.amount}
|
||||||
currencyCode={cart.cost.totalTaxAmount.currencyCode}
|
currencyCode={cart.cost.totalTaxAmount.currencyCode}
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 pt-1 dark:border-neutral-700">
|
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 pt-1 dark:border-neutral-700">
|
||||||
<p>Shipping</p>
|
<p>Shipping</p>
|
||||||
@ -168,7 +168,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 pt-1 dark:border-neutral-700">
|
<div className="mb-3 flex items-center justify-between border-b border-neutral-200 pb-1 pt-1 dark:border-neutral-700">
|
||||||
<p>Total</p>
|
<p>Total</p>
|
||||||
<Price
|
<Price
|
||||||
className="text-right text-base text-black dark:text-white"
|
className="text-right text-base text-neutral-200 dark:text-white"
|
||||||
amount={cart.cost.totalAmount.amount}
|
amount={cart.cost.totalAmount.amount}
|
||||||
currencyCode={cart.cost.totalAmount.currencyCode}
|
currencyCode={cart.cost.totalAmount.currencyCode}
|
||||||
/>
|
/>
|
||||||
@ -176,7 +176,7 @@ export default function CartModal({ cart }: { cart: Cart | undefined }) {
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={cart.checkoutUrl}
|
href={cart.checkoutUrl}
|
||||||
className="block w-full rounded-full bg-gray-600 p-3 text-center text-sm font-medium text-white opacity-90 hover:opacity-100"
|
className="block w-full rounded bg-neutral-600 p-3 text-center text-sm font-medium text-white opacity-90 hover:opacity-100"
|
||||||
>
|
>
|
||||||
Proceed to Checkout
|
Proceed to Checkout
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ShoppingCartIcon } from '@heroicons/react/24/outline';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { ShoppingBagIcon } from 'lucide-react';
|
||||||
|
|
||||||
export default function OpenCart({
|
export default function OpenCart({
|
||||||
className,
|
className,
|
||||||
@ -9,13 +9,14 @@ export default function OpenCart({
|
|||||||
quantity?: number;
|
quantity?: number;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-11 w-11 items-center justify-center rounded-md border border-neutral-200 text-white transition-colors dark:border-neutral-700 dark:text-white">
|
// <div className="relative flex h-11 w-11 items-center justify-center rounded-md border border-neutral-700 text-white transition-colors dark:border-neutral-700 dark:text-white">
|
||||||
<ShoppingCartIcon
|
<div className="relative flex h-11 w-11 items-center justify-center rounded-md text-white transition-colors dark:text-white">
|
||||||
className={clsx('h-4 transition-all ease-in-out hover:scale-110 ', className)}
|
<ShoppingBagIcon
|
||||||
|
className={clsx('h-6 transition-all ease-in-out hover:scale-110', className)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{quantity ? (
|
{quantity ? (
|
||||||
<div className="absolute right-0 top-0 -mr-2 -mt-2 h-4 w-4 rounded bg-gray-600 text-[11px] font-medium text-white">
|
<div className="absolute right-0 top-0 -mr-2 -mt-2 h-4 w-4 rounded bg-neutral-800 text-[11px] font-medium text-white">
|
||||||
{quantity}
|
{quantity}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Label from '../label';
|
import Label from '../ui/label';
|
||||||
|
|
||||||
export function GridTileImage({
|
export function GridTileImage({
|
||||||
isInteractive = true,
|
isInteractive = true,
|
||||||
@ -20,11 +20,9 @@ export function GridTileImage({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex h-full w-full items-center justify-center overflow-hidden rounded-lg border bg-white hover:border-gray-600 dark:bg-black aspect-square align-middle',
|
'flex h-full w-full items-center justify-center overflow-hidden rounded-lg bg-white dark:bg-black aspect-square align-middle',
|
||||||
{
|
{
|
||||||
relative: label,
|
relative: label,
|
||||||
'border-2 border-gray-600': active,
|
|
||||||
'border-neutral-200 dark:border-neutral-800': !active
|
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
import FooterMenu from 'components/layout/footer-menu';
|
import FooterMenu from 'components/layout/footer-menu';
|
||||||
import LogoSquare from 'components/logo-square';
|
|
||||||
import { getMenu } from 'lib/shopify';
|
import { getMenu } from 'lib/shopify';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
@ -16,13 +14,13 @@ export default async function Footer() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="text-sm text-neutral-500 dark:text-neutral-400">
|
<footer className="text-sm text-neutral-500 dark:text-neutral-400">
|
||||||
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6 border-t border-neutral-200 px-6 py-12 text-sm dark:border-neutral-700 md:flex-row md:gap-12 md:px-4 xl:px-0">
|
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6 border-t border-neutral-700 px-6 py-12 text-sm dark:border-neutral-700 md:flex-row md:gap-12 md:px-4 xl:px-0">
|
||||||
<div>
|
{/* <div>
|
||||||
<Link className="flex items-center gap-2 text-black dark:text-white" href="/">
|
<Link className="flex items-center gap-2 text-black dark:text-white" href="/">
|
||||||
<LogoSquare size="sm" />
|
<LogoSquare size="sm" />
|
||||||
{/* <span className="uppercase">{SITE_NAME}</span> */}
|
<span className="uppercase">{SITE_NAME}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div> */}
|
||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<div className="flex h-[188px] w-[100px] flex-col gap-2 ">
|
<div className="flex h-[188px] w-[100px] flex-col gap-2 ">
|
||||||
@ -48,7 +46,7 @@ export default async function Footer() {
|
|||||||
</a>
|
</a>
|
||||||
</div> */}
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="border-t border-neutral-200 py-6 text-sm dark:border-neutral-700">
|
<div className="border-t border-neutral-700 py-6 text-sm dark:border-neutral-700">
|
||||||
<div className="mx-auto flex w-full max-w-7xl flex-col items-center gap-1 md:flex-row md:gap-0 pl-4">
|
<div className="mx-auto flex w-full max-w-7xl flex-col items-center gap-1 md:flex-row md:gap-0 pl-4">
|
||||||
<p>
|
<p>
|
||||||
© {copyrightDate} {copyrightName}
|
© {copyrightDate} {copyrightName}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import Cart from 'components/cart';
|
import Cart from 'components/cart';
|
||||||
import OpenCart from 'components/cart/open-cart';
|
import OpenCart from 'components/cart/open-cart';
|
||||||
import LogoType from 'components/logo-type';
|
import LogoType from 'components/ui/logo-type';
|
||||||
import { getMenu } from 'lib/shopify';
|
import { getMenu } from 'lib/shopify';
|
||||||
import { Menu } from 'lib/shopify/types';
|
import { Menu } from 'lib/shopify/types';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import MobileMenu from './mobile-menu';
|
import MobileMenu from './mobile-menu';
|
||||||
import Search from './search';
|
|
||||||
const { SITE_NAME } = process.env;
|
const { SITE_NAME } = process.env;
|
||||||
|
|
||||||
export default async function Navbar() {
|
export default async function Navbar() {
|
||||||
@ -30,7 +29,7 @@ export default async function Navbar() {
|
|||||||
</div> */}
|
</div> */}
|
||||||
<LogoType />
|
<LogoType />
|
||||||
</Link>
|
</Link>
|
||||||
{menu.length ? (
|
{/* {menu.length ? (
|
||||||
<ul className="hidden text-sm md:flex md:items-center">
|
<ul className="hidden text-sm md:flex md:items-center">
|
||||||
{menu.map((item: Menu) => (
|
{menu.map((item: Menu) => (
|
||||||
<li key={item.title}>
|
<li key={item.title}>
|
||||||
@ -43,11 +42,25 @@ export default async function Navbar() {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : null}
|
) : null} */}
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden justify-center md:flex md:w-1/3">
|
{/* <div className="hidden justify-center md:flex md:w-1/3">
|
||||||
<Search />
|
<Search />
|
||||||
</div>
|
</div> */}
|
||||||
|
{menu.length ? (
|
||||||
|
<ul className="hidden text-sm md:flex md:items-center">
|
||||||
|
{menu.map((item: Menu) => (
|
||||||
|
<li key={item.title}>
|
||||||
|
<Link
|
||||||
|
href={item.path}
|
||||||
|
className="m-3 text-neutral-500 underline-offset-4 hover:text-black hover:underline dark:text-neutral-400 dark:hover:text-neutral-300 lg:mr-8"
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
) : null}
|
||||||
<div className="flex justify-end md:w-1/3">
|
<div className="flex justify-end md:w-1/3">
|
||||||
<Suspense fallback={<OpenCart />}>
|
<Suspense fallback={<OpenCart />}>
|
||||||
<Cart />
|
<Cart />
|
||||||
|
@ -57,7 +57,7 @@ export default function MobileMenu({ menu }: { menu: Menu[] }) {
|
|||||||
leaveFrom="translate-x-0"
|
leaveFrom="translate-x-0"
|
||||||
leaveTo="translate-x-[-100%]"
|
leaveTo="translate-x-[-100%]"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="fixed bottom-0 left-0 right-0 top-0 flex h-full w-full flex-col bg-white pb-6 dark:bg-black">
|
<Dialog.Panel className="fixed bottom-0 left-0 right-0 top-0 flex h-full w-full flex-col bg-neutral-800/95 pb-6 dark:bg-black">
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<button className="mb-4" onClick={closeMobileMenu} aria-label="Close mobile menu">
|
<button className="mb-4" onClick={closeMobileMenu} aria-label="Close mobile menu">
|
||||||
<XMarkIcon className="h-6" />
|
<XMarkIcon className="h-6" />
|
||||||
@ -69,10 +69,10 @@ export default function MobileMenu({ menu }: { menu: Menu[] }) {
|
|||||||
{menu.length ? (
|
{menu.length ? (
|
||||||
<ul className="flex flex-col">
|
<ul className="flex flex-col">
|
||||||
{menu.map((item: Menu) => (
|
{menu.map((item: Menu) => (
|
||||||
<li key={item.title}>
|
<li className="mb-4" key={item.title}>
|
||||||
<Link
|
<Link
|
||||||
href={item.path}
|
href={item.path}
|
||||||
className="rounded-lg py-1 text-xl text-black transition-colors hover:text-neutral-500 dark:text-white"
|
className="rounded-lg py-1 text-xl text-neutral-200 transition-colors hover:text-neutral-500 dark:text-white"
|
||||||
onClick={closeMobileMenu}
|
onClick={closeMobileMenu}
|
||||||
>
|
>
|
||||||
{item.title}
|
{item.title}
|
||||||
|
@ -42,7 +42,7 @@ export default function FilterItemDropdown({ list }: { list: ListItem[] }) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpenSelect(!openSelect);
|
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"
|
className="flex w-full items-center justify-between rounded border border-neutral-500/30 px-4 py-2 text-sm text-neutral-100 dark:border-white/30"
|
||||||
>
|
>
|
||||||
<div>{active}</div>
|
<div>{active}</div>
|
||||||
<ChevronDownIcon className="h-4" />
|
<ChevronDownIcon className="h-4" />
|
||||||
@ -52,7 +52,7 @@ export default function FilterItemDropdown({ list }: { list: ListItem[] }) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpenSelect(false);
|
setOpenSelect(false);
|
||||||
}}
|
}}
|
||||||
className="absolute z-40 w-full rounded-b-md bg-white p-4 shadow-md dark:bg-black"
|
className="absolute z-40 w-full rounded-b-md bg-neutral-800 text-neutral-100 p-4 shadow-md dark:bg-black"
|
||||||
>
|
>
|
||||||
{list.map((item: ListItem, i) => (
|
{list.map((item: ListItem, i) => (
|
||||||
<FilterItem key={i} item={item} />
|
<FilterItem key={i} item={item} />
|
||||||
|
@ -22,7 +22,7 @@ function PathFilterItem({ item }: { item: PathFilterItem }) {
|
|||||||
}, [pathname, item.path]);
|
}, [pathname, item.path]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="mt-2 flex text-black dark:text-white" key={item.title}>
|
<li className="mt-2 flex text-neutral-200 dark:text-white" key={item.title}>
|
||||||
<DynamicTag
|
<DynamicTag
|
||||||
href={createUrl(item.path, newParams)}
|
href={createUrl(item.path, newParams)}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
@ -57,7 +57,7 @@ function SortFilterItem({ item }: { item: SortFilterItem }) {
|
|||||||
}, [searchParams, item.slug]);
|
}, [searchParams, item.slug]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="mt-2 flex text-sm text-black dark:text-white" key={item.title}>
|
<li className="mt-2 flex text-sm text-neutral-200 dark:text-white" key={item.title}>
|
||||||
<DynamicTag
|
<DynamicTag
|
||||||
prefetch={!active ? false : undefined}
|
prefetch={!active ? false : undefined}
|
||||||
href={href}
|
href={href}
|
||||||
|
@ -47,7 +47,7 @@ export function DescriptionContent({ product }: { product: Product }) {
|
|||||||
{itemDetails.print} -
|
{itemDetails.print} -
|
||||||
{certificationLink('oekoEco')}
|
{certificationLink('oekoEco')}
|
||||||
</li>
|
</li>
|
||||||
<li className='mt-1'>{itemDetails.weight.feel} weight, {itemDetails.weight.gsm} GSM</li>
|
{/* <li className='mt-1'>{itemDetails.weight.feel} weight, {itemDetails.weight.gsm} GSM</li> */}
|
||||||
{commonDetailKeys.map(detail => (
|
{commonDetailKeys.map(detail => (
|
||||||
|
|
||||||
// <div>{detail}</div>
|
// <div>{detail}</div>
|
||||||
|
@ -35,7 +35,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
|
|||||||
|
|
||||||
{images.length > 1 ? (
|
{images.length > 1 ? (
|
||||||
<div className="bottom-2 z-50 flex w-full justify-center absolute">
|
<div className="bottom-2 z-50 flex w-full justify-center absolute">
|
||||||
<div className="mx-auto flex h-8 items-center rounded-full border border-white bg-neutral-50/50 text-neutral-500 backdrop-blur dark:border-black dark:bg-neutral-900/80">
|
<div className="mx-auto flex h-8 items-center rounded border border-neutral-500 bg-neutral-500/30 text-neutral-200 backdrop-blur dark:border-black dark:bg-neutral-900/80">
|
||||||
<button
|
<button
|
||||||
aria-label="Previous product image"
|
aria-label="Previous product image"
|
||||||
onClick={() => handleNavigate('previous')}
|
onClick={() => handleNavigate('previous')}
|
||||||
@ -43,7 +43,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
|
|||||||
>
|
>
|
||||||
<ArrowLeftIcon className="h-5" />
|
<ArrowLeftIcon className="h-5" />
|
||||||
</button>
|
</button>
|
||||||
<div className="mx-1 h-6 w-px bg-neutral-500"></div>
|
<div className="mx-1 h-6 w-px bg-neutral-600"></div>
|
||||||
<button
|
<button
|
||||||
aria-label="Next product image"
|
aria-label="Next product image"
|
||||||
onClick={() => handleNavigate('next')}
|
onClick={() => handleNavigate('next')}
|
||||||
@ -57,7 +57,7 @@ export function Gallery({ images }: { images: { src: string; altText: string }[]
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{images.length > 1 ? (
|
{images.length > 1 ? (
|
||||||
<div className="flex items-center justify-center gap-2 overflow-auto py-1 pl-20 md:pl-0">
|
<div className="flex items-center justify-center gap-2 overflow-auto py-1 md:pl-0">
|
||||||
{images.map((image, index) => {
|
{images.map((image, index) => {
|
||||||
const isActive = index === currentImageIndex;
|
const isActive = index === currentImageIndex;
|
||||||
return (
|
return (
|
||||||
|
@ -8,7 +8,7 @@ export function ProductDescription({ product }: { product: Product }) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<h1 className="mb-2 text-2xl md:text-3xl font-medium">{product.title}</h1>
|
<h1 className="mb-2 text-xl sm:text-2xl md:text-3xl font-medium">{product.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<VariantDetails product={product} />
|
<VariantDetails product={product} />
|
||||||
<AddToCart variants={product.variants} availableForSale={product.availableForSale} />
|
<AddToCart variants={product.variants} availableForSale={product.availableForSale} />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import Price from "components/price";
|
import Price from "components/ui/price";
|
||||||
import { Product, ProductVariant } from "lib/shopify/types";
|
import { Product, ProductVariant } from "lib/shopify/types";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { VariantSelector } from "./variant-selector";
|
import { VariantSelector } from "./variant-selector";
|
||||||
@ -11,8 +11,8 @@ export function VariantDetails({ product }: { product: Product }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex flex-col border-b mb-6 dark:border-neutral-700'>
|
<div className='border-b mb-6 border-neutral-500 dark:border-neutral-700 display-flex justify-end'>
|
||||||
<div className="place-self-end mr-auto w-auto rounded-full bg-gray-600 p-2 text-sm text-white mb-6">
|
<div className="place-self-end justify-end mr-auto w-25 roundedp-2 text-sm text-neutral-300 mb-6">
|
||||||
<Price
|
<Price
|
||||||
amount={selectedVariant?.price?.amount || product.variants[0]!.price.amount}
|
amount={selectedVariant?.price?.amount || product.variants[0]!.price.amount}
|
||||||
currencyCode={product.priceRange.maxVariantPrice.currencyCode}
|
currencyCode={product.priceRange.maxVariantPrice.currencyCode}
|
||||||
|
@ -126,9 +126,9 @@ export function VariantSelector({
|
|||||||
href={optionUrl}
|
href={optionUrl}
|
||||||
title={`${option.name} ${value}${!isAvailableForSale ? ' (Out of Stock)' : ''}`}
|
title={`${option.name} ${value}${!isAvailableForSale ? ' (Out of Stock)' : ''}`}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex min-w-[48px] items-center justify-center rounded-full border bg-neutral-100 px-2 py-1 text-sm dark:border-neutral-800 dark:bg-neutral-900',
|
'flex min-w-[48px] items-center justify-center rounded border border-neutral-600 px-2 py-1 text-sm text-neutral-200 dark:border-neutral-800 dark:bg-neutral-900',
|
||||||
{
|
{
|
||||||
'cursor-default ring-2 ring-gray-600': isActive,
|
'cursor-default ring-1 ring-gray-600 bg-neutral-300 text-neutral-900': isActive,
|
||||||
'ring-1 ring-transparent transition duration-300 ease-in-out hover:scale-110 hover:ring-gray-600 ':
|
'ring-1 ring-transparent transition duration-300 ease-in-out hover:scale-110 hover:ring-gray-600 ':
|
||||||
!isActive && isAvailableForSale,
|
!isActive && isAvailableForSale,
|
||||||
'relative z-10 cursor-not-allowed overflow-hidden bg-neutral-100 text-neutral-500 ring-1 ring-neutral-300 before:absolute before:inset-x-0 before:-z-10 before:h-px before:-rotate-45 before:bg-neutral-300 before:transition-transform dark:bg-neutral-900 dark:text-neutral-400 dark:ring-neutral-700 before:dark:bg-neutral-700':
|
'relative z-10 cursor-not-allowed overflow-hidden bg-neutral-100 text-neutral-500 ring-1 ring-neutral-300 before:absolute before:inset-x-0 before:-z-10 before:h-px before:-rotate-45 before:bg-neutral-300 before:transition-transform dark:bg-neutral-900 dark:text-neutral-400 dark:ring-neutral-700 before:dark:bg-neutral-700':
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import clsx from 'clsx';
|
|
||||||
import type { FunctionComponent } from 'react';
|
|
||||||
|
|
||||||
interface TextProps {
|
|
||||||
html: string;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Prose: FunctionComponent<TextProps> = ({ html, className }) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
'prose mx-auto max-w-6xl text-base leading-7 text-black prose-headings:mt-8 prose-headings:font-semibold prose-headings:tracking-wide prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg prose-a:text-black prose-a:underline hover:prose-a:text-neutral-300 prose-strong:text-black prose-ol:mt-8 prose-ol:list-decimal prose-ol:pl-6 prose-ul:mt-8 prose-ul:list-disc prose-ul:pl-6 dark:text-white dark:prose-headings:text-white dark:prose-a:text-white dark:prose-strong:text-white',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
dangerouslySetInnerHTML={{ __html: html as string }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Prose;
|
|
59
components/ui/carousel.tsx
Normal file
59
components/ui/carousel.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { getCollectionProducts } from 'lib/shopify';
|
||||||
|
import { getAllLiveProducts } from 'lib/utils';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { GridTileImage } from '../grid/tile';
|
||||||
|
|
||||||
|
type Collection = 'flower' | 'foliage' | 'nature' | 'urban' | 'sky';
|
||||||
|
|
||||||
|
export async function Carousel({collection}:
|
||||||
|
{
|
||||||
|
collection: Collection | undefined
|
||||||
|
}) {
|
||||||
|
// Collections that start with `hidden-*` are hidden from the search page.
|
||||||
|
const scapeTitle = collection ? `${collection[0]?.toUpperCase()}${collection.slice(1)}scapes` : '';
|
||||||
|
|
||||||
|
const getProducts = async () => {
|
||||||
|
return !!collection ? getCollectionProducts({ collection: scapeTitle }) : getAllLiveProducts();
|
||||||
|
};
|
||||||
|
const products = await getProducts();
|
||||||
|
|
||||||
|
if (!products?.length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='max-w-full'>
|
||||||
|
{scapeTitle &&
|
||||||
|
<Link href={`/search/${scapeTitle}`}>
|
||||||
|
<p className='text-lg text-bold'>{scapeTitle}</p>
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
{!scapeTitle &&
|
||||||
|
// <Link href={`/search/${scapeTitle}`}>
|
||||||
|
<p className='text-lg text-bold'>All Scapes</p>
|
||||||
|
// </Link>
|
||||||
|
}
|
||||||
|
<div className=" w-full overflow-x-auto pb-6 pt-1 border-neutral-300 border-bottom:not-last-of-type">
|
||||||
|
<div className="flex animate-carousel gap-4">
|
||||||
|
{[...products, ...products].map((product, i) => (
|
||||||
|
<Link
|
||||||
|
key={`${product.handle}${i}`}
|
||||||
|
href={`/products/${product.handle}`}
|
||||||
|
className="h-50 w-1/2 md:w-1/3 flex-none"
|
||||||
|
>
|
||||||
|
<GridTileImage
|
||||||
|
alt={product.title}
|
||||||
|
label={{
|
||||||
|
title: product.title,
|
||||||
|
amount: product.priceRange.maxVariantPrice.amount,
|
||||||
|
currencyCode: product.priceRange.maxVariantPrice.currencyCode
|
||||||
|
}}
|
||||||
|
src={product.featuredImage?.url}
|
||||||
|
width={600}
|
||||||
|
height={600}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -18,10 +18,11 @@ const Label = ({
|
|||||||
'lg:px-20 lg:pb-[35%]': position === 'center'
|
'lg:px-20 lg:pb-[35%]': position === 'center'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="flex items-center rounded-full border bg-white/70 p-1 text-[10px] font-semibold text-black backdrop-blur-md @[275px]/label:text-xs dark:border-neutral-800 dark:bg-black/70 dark:text-white">
|
<div className="w-full flex items-center rounded bg-white/70 p-1 text-[10px] font-semibold text-black backdrop-blur-md @[275px]/label:text-xs dark:border-neutral-800 dark:bg-black/70 dark:text-white">
|
||||||
<h3 className="mr-4 inline pl-2 leading-none tracking-tight">{title}</h3>
|
<h3 className="mr-4 inline pl-2 leading-none tracking-tight">{title}</h3>
|
||||||
<Price
|
<Price
|
||||||
className="flex-none rounded-full bg-gray-600 p-2 text-white"
|
// className="flex-none rounded-full bg-gray-600 p-2 text-white"
|
||||||
|
className="ml-auto flex-none text-gray-600 text-bold"
|
||||||
amount={amount}
|
amount={amount}
|
||||||
currencyCode={currencyCode}
|
currencyCode={currencyCode}
|
||||||
currencyCodeClassName="hidden @[275px]/label:inline"
|
currencyCodeClassName="hidden @[275px]/label:inline"
|
@ -1,11 +1,11 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import LogoIcon from './icons/logo';
|
import LogoIcon from '../icons/logo';
|
||||||
|
|
||||||
export default function LogoSquare({ size }: { size?: 'sm' | undefined }) {
|
export default function LogoSquare({ size }: { size?: 'sm' | undefined }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex flex-none items-center justify-center border border-neutral-200 bg-white dark:border-neutral-700 dark:bg-black',
|
'flex flex-none items-center justify-center bg-white dark:border-neutral-700 dark:bg-black',
|
||||||
{
|
{
|
||||||
'h-[40px] w-[40px] rounded-xl': !size,
|
'h-[40px] w-[40px] rounded-xl': !size,
|
||||||
'h-[30px] w-[30px] rounded-lg': size === 'sm'
|
'h-[30px] w-[30px] rounded-lg': size === 'sm'
|
21
components/ui/prose.tsx
Normal file
21
components/ui/prose.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import clsx from 'clsx';
|
||||||
|
import type { FunctionComponent } from 'react';
|
||||||
|
|
||||||
|
interface TextProps {
|
||||||
|
html: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Prose: FunctionComponent<TextProps> = ({ html, className }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'prose mx-auto max-w-6xl text-base leading-7 text-neutral-50 prose-headings:mt-8 prose-headings:font-semibold prose-headings:tracking-wide prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg prose-a:text-black prose-a:underline hover:prose-a:text-neutral-300 prose-strong:text-black prose-ol:mt-8 prose-ol:list-decimal prose-ol:pl-6 prose-ul:mt-8 prose-ul:list-disc prose-ul:pl-6 dark:text-white dark:prose-headings:text-white dark:prose-a:text-white dark:prose-strong:text-white',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
dangerouslySetInnerHTML={{ __html: html as string }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Prose;
|
@ -29,8 +29,9 @@ export async function getLiveCollectionProducts(query: Parameters<typeof getColl
|
|||||||
return liveProducts;
|
return liveProducts;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllLiveProducts(query: Parameters<typeof getProducts>[0]) {
|
export async function getAllLiveProducts(query?: Parameters<typeof getProducts>[0]) {
|
||||||
const products = await getProducts(query);
|
const searchQuery = query || { query: undefined, reverse: undefined, sortKey: undefined}
|
||||||
|
const products = await getProducts(searchQuery);
|
||||||
const liveProducts = products.filter((product) => !product.tags.includes('hidden-product'));
|
const liveProducts = products.filter((product) => !product.tags.includes('hidden-product'));
|
||||||
return liveProducts
|
return liveProducts
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user