checkpoint for currency and variant selection

This commit is contained in:
Jieren Chen 2024-09-08 13:07:01 -04:00
parent 5812416bd3
commit 34009d2600
No known key found for this signature in database
GPG Key ID: 2FF322D21B5DB10B
9 changed files with 71 additions and 31 deletions

View File

@ -39,7 +39,7 @@ export const metadata = {
export default async function RootLayout({ children }: { children: ReactNode }) { export default async function RootLayout({ children }: { children: ReactNode }) {
const cartId = cookies().get('cartId')?.value; const cartId = cookies().get('cartId')?.value;
// Don't await the fetch, pass the Promise to the context provider // Don't await the fetch, pass the Promise to the context provider
const cart = getCart(cartId); const cart = getCart(cartId, 'USD');
return ( return (
<html lang="en" className={GeistSans.variable}> <html lang="en" className={GeistSans.variable}>

View File

@ -9,11 +9,13 @@ export const metadata = {
} }
}; };
export default function HomePage() { export default function HomePage({ searchParams }: { searchParams: { currency?: string } }) {
const currency = searchParams.currency || 'USD';
return ( return (
<> <>
<ThreeItemGrid /> <ThreeItemGrid currency={currency} />
<Carousel /> <Carousel currency={currency}/>
<Footer /> <Footer />
</> </>
); );

View File

@ -17,7 +17,7 @@ export async function generateMetadata({
}: { }: {
params: { handle: string }; params: { handle: string };
}): Promise<Metadata> { }): Promise<Metadata> {
const product = await getProduct(params.handle); const product = await getProduct({ handle: params.handle, currency: 'USD' });
if (!product) return notFound(); if (!product) return notFound();
@ -50,8 +50,12 @@ export async function generateMetadata({
}; };
} }
export default async function ProductPage({ params }: { params: { handle: string } }) { export default async function ProductPage({ params, searchParams }: { params: { handle: string }, searchParams: { currency?: string } }) {
const product = await getProduct(params.handle); const currency = searchParams.currency || 'USD';
const product = await getProduct({
handle: params.handle,
currency: searchParams.currency || 'USD'
});
if (!product) return notFound(); if (!product) return notFound();

View File

@ -2,9 +2,12 @@ import { getCollectionProducts } from 'lib/fourthwall';
import Link from 'next/link'; import Link from 'next/link';
import { GridTileImage } from './grid/tile'; import { GridTileImage } from './grid/tile';
export async function Carousel() { export async function Carousel({currency}: {currency: string}) {
// Collections that start with `hidden-*` are hidden from the search page. // Collections that start with `hidden-*` are hidden from the search page.
const products = await getCollectionProducts({ collection: process.env.FW_COLLECTION || '' }); const products = await getCollectionProducts({
collection: process.env.FW_COLLECTION || '',
currency,
});
if (!products?.length) return null; if (!products?.length) return null;
@ -19,7 +22,7 @@ export async function Carousel() {
key={`${product.handle}${i}`} key={`${product.handle}${i}`}
className="relative aspect-square h-[30vh] max-h-[275px] w-2/3 max-w-[475px] flex-none md:w-1/3" className="relative aspect-square h-[30vh] max-h-[275px] w-2/3 max-w-[475px] flex-none md:w-1/3"
> >
<Link href={`/product/${product.handle}`} className="relative h-full w-full"> <Link href={`/product/${product.handle}?currency=${currency}`} className="relative h-full w-full">
<GridTileImage <GridTileImage
alt={product.title} alt={product.title}
label={{ label={{

View File

@ -5,10 +5,12 @@ import Link from 'next/link';
function ThreeItemGridItem({ function ThreeItemGridItem({
item, item,
currency,
size, size,
priority priority
}: { }: {
item: Product; item: Product;
currency: string;
size: 'full' | 'half'; size: 'full' | 'half';
priority?: boolean; priority?: boolean;
}) { }) {
@ -18,7 +20,7 @@ function ThreeItemGridItem({
> >
<Link <Link
className="relative block aspect-square h-full w-full" className="relative block aspect-square h-full w-full"
href={`/product/${item.handle}`} href={`/product/${item.handle}?currency=${currency}`}
prefetch={true} prefetch={true}
> >
<GridTileImage <GridTileImage
@ -41,10 +43,11 @@ function ThreeItemGridItem({
); );
} }
export async function ThreeItemGrid() { export async function ThreeItemGrid({ currency } : { currency: string }) {
// Collections that start with `hidden-*` are hidden from the search page. // Collections that start with `hidden-*` are hidden from the search page.
const homepageItems = await getCollectionProducts({ const homepageItems = await getCollectionProducts({
collection: process.env.FW_COLLECTION || '', collection: process.env.FW_COLLECTION || '',
currency
}); });
if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null; if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null;
@ -53,9 +56,9 @@ export async function ThreeItemGrid() {
return ( return (
<section className="mx-auto grid max-w-screen-2xl gap-4 px-4 pb-4 md:grid-cols-6 md:grid-rows-2 lg:max-h-[calc(100vh-200px)]"> <section className="mx-auto grid max-w-screen-2xl gap-4 px-4 pb-4 md:grid-cols-6 md:grid-rows-2 lg:max-h-[calc(100vh-200px)]">
<ThreeItemGridItem size="full" item={firstProduct} priority={true} /> <ThreeItemGridItem size="full" item={firstProduct} priority={true} currency={currency}/>
<ThreeItemGridItem size="half" item={secondProduct} priority={true} /> <ThreeItemGridItem size="half" item={secondProduct} priority={true} currency={currency}/>
<ThreeItemGridItem size="half" item={thirdProduct} /> <ThreeItemGridItem size="half" item={thirdProduct} currency={currency}/>
</section> </section>
); );
} }

View File

@ -53,6 +53,9 @@ export async function Navbar() {
</Suspense> </Suspense>
</div> </div>
<div className="flex justify-end md:w-1/3"> <div className="flex justify-end md:w-1/3">
<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">
USD
</div>
<CartModal /> <CartModal />
</div> </div>
</div> </div>

View File

@ -65,19 +65,23 @@ async function fourthwallPost<T>(url: string, data: any, options: RequestInit =
*/ */
export async function getCollectionProducts({ export async function getCollectionProducts({
collection, collection,
currency,
reverse, reverse,
sortKey sortKey
}: { }: {
collection: string; collection: string;
currency: string;
reverse?: boolean; reverse?: boolean;
sortKey?: string; sortKey?: string;
}): Promise<Product[]> { }): Promise<Product[]> {
const res = await fourthwallGet<{results: FourthwallProduct[]}>(`${process.env.FW_URL}/api/public/v1.0/collections/${collection}/products?secret=${process.env.FW_SECRET}`, { const res = await fourthwallGet<{results: FourthwallProduct[]}>(`${process.env.FW_URL}/api/public/v1.0/collections/${collection}/products?secret=${process.env.FW_SECRET}&currency=${currency}`, {
headers: { headers: {
'X-ShopId': process.env.FW_SHOPID || '' 'X-ShopId': process.env.FW_SHOPID || ''
} }
}); });
console.warn(JSON.stringify(res.body.results, null, 2));
if (!res.body.results) { if (!res.body.results) {
console.log(`No collection found for \`${collection}\``); console.log(`No collection found for \`${collection}\``);
return []; return [];
@ -90,9 +94,9 @@ export async function getCollectionProducts({
/** /**
* Product operations * Product operations
*/ */
export async function getProduct(handle: string): Promise<Product | undefined> { export async function getProduct({ handle, currency } : { handle: string, currency: string }): Promise<Product | undefined> {
// TODO: replace with real URL // TODO: replace with real URL
const res = await fourthwallGet<{results: FourthwallProduct[]}>(`${process.env.FW_URL}/api/public/v1.0/collections/${process.env.FW_COLLECTION}/products?secret=${process.env.FW_SECRET}`, { const res = await fourthwallGet<{results: FourthwallProduct[]}>(`${process.env.FW_URL}/api/public/v1.0/collections/${process.env.FW_COLLECTION}/products?secret=${process.env.FW_SECRET}&currency=${currency}`, {
headers: { headers: {
'X-ShopId': process.env.FW_SHOPID || '' 'X-ShopId': process.env.FW_SHOPID || ''
} }
@ -111,7 +115,7 @@ export async function getProductRecommendations(productId: string): Promise<Prod
/** /**
* Cart operations * Cart operations
*/ */
export async function getCart(cartId: string | undefined): Promise<Cart | undefined> { export async function getCart(cartId: string | undefined, currency: string): Promise<Cart | undefined> {
if (!cartId) { if (!cartId) {
return undefined; return undefined;
} }

View File

@ -15,7 +15,7 @@ const DEFAULT_IMAGE: Image = {
const reshapeMoney = (money: FourthwallMoney): Money => { const reshapeMoney = (money: FourthwallMoney): Money => {
return { return {
amount: money.value.toString(), amount: money.value.toString(),
currencyCode: money.currencyCode currencyCode: money.currency
}; };
} }
@ -48,7 +48,10 @@ export const reshapeProduct = (product: FourthwallProduct): Product | undefined
const minPrice = Math.min(...variants.map((v) => v.unitPrice.value)); const minPrice = Math.min(...variants.map((v) => v.unitPrice.value));
const maxPrice = Math.max(...variants.map((v) => v.unitPrice.value)); const maxPrice = Math.max(...variants.map((v) => v.unitPrice.value));
const currencyCode = variants[0]?.unitPrice.currencyCode || 'USD'; const currencyCode = variants[0]?.unitPrice.currency || 'USD';
const sizes = new Set(variants.map((v) => v.attributes.size.name));
const colors = new Set(variants.map((v) => v.attributes.color.name));
return { return {
...rest, ...rest,
@ -69,9 +72,17 @@ export const reshapeProduct = (product: FourthwallProduct): Product | undefined
} }
}, },
featuredImage: reshapeImages(images, product.name)[0] || DEFAULT_IMAGE, featuredImage: reshapeImages(images, product.name)[0] || DEFAULT_IMAGE,
options: [{
id: 'color',
name: 'Color',
values: [...colors]
}, {
id: 'size',
name: 'Size',
values: [...sizes]
}],
// TODO: stubbed out // TODO: stubbed out
availableForSale: true, availableForSale: true,
options: [],
seo: { seo: {
title: product.name, title: product.name,
description: product.description, description: product.description,
@ -96,7 +107,13 @@ const reshapeVariants = (variants: FourthwallProductVariant[]): ProductVariant[]
id: v.id, id: v.id,
title: v.name, title: v.name,
availableForSale: true, availableForSale: true,
selectedOptions: [], selectedOptions: [{
name: 'Size',
value: v.attributes.size.name
}, {
name: 'Color',
value: v.attributes.color.name
}],
price: reshapeMoney(v.unitPrice), price: reshapeMoney(v.unitPrice),
})) }))
} }
@ -134,7 +151,7 @@ const reshapeCartItem = (item: FourthwallCartItem): CartItem => {
export const reshapeCart = (cart: FourthwallCart): Cart => { export const reshapeCart = (cart: FourthwallCart): Cart => {
const totalValue = cart.items.map((item) => item.quantity * item.variant.unitPrice.value).reduce((a, b) => a + b, 0); const totalValue = cart.items.map((item) => item.quantity * item.variant.unitPrice.value).reduce((a, b) => a + b, 0);
const currencyCode = cart.items[0]?.variant.unitPrice.currencyCode || 'USD'; const currencyCode = cart.items[0]?.variant.unitPrice.currency || 'USD';
return { return {
...cart, ...cart,

View File

@ -1,6 +1,6 @@
export type FourthwallMoney = { export type FourthwallMoney = {
value: number; value: number;
currencyCode: string; currency: string;
} }
export type FourthwallProduct = { export type FourthwallProduct = {
@ -29,17 +29,21 @@ export type FourthwallProductVariant = {
images: FourthwallProductImage[]; images: FourthwallProductImage[];
// other attr // other attr
attributes: {
description: string;
color: {
name: string;
swatch: string;
},
size: {
name: string;
};
}
}; };
export type FourthwallCart = { export type FourthwallCart = {
id: string | undefined; id: string | undefined;
// checkoutUrl: string;
// cost: {
// subtotalAmount: Money;
// totalAmount: Money;
// totalTaxAmount: Money;
// };
items: FourthwallCartItem[]; items: FourthwallCartItem[];
}; };