mirror of
https://github.com/vercel/commerce.git
synced 2025-05-30 21:16:59 +00:00
checkpoint for currency and variant selection
This commit is contained in:
parent
5812416bd3
commit
34009d2600
@ -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}>
|
||||||
|
@ -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 />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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={{
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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}¤cy=${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}¤cy=${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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user