mirror of
https://github.com/vercel/commerce.git
synced 2025-05-30 21:16:59 +00:00
currency and checkout
This commit is contained in:
parent
4653f74188
commit
b6ba260b77
@ -1,12 +1,6 @@
|
|||||||
import { CartProvider } from 'components/cart/cart-context';
|
|
||||||
import { Navbar } from 'components/layout/navbar';
|
|
||||||
import { WelcomeToast } from 'components/welcome-toast';
|
|
||||||
import { GeistSans } from 'geist/font/sans';
|
import { GeistSans } from 'geist/font/sans';
|
||||||
import { getCart } from 'lib/fourthwall';
|
|
||||||
import { ensureStartsWith } from 'lib/utils';
|
import { ensureStartsWith } from 'lib/utils';
|
||||||
import { cookies } from 'next/headers';
|
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Toaster } from 'sonner';
|
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env;
|
const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env;
|
||||||
@ -37,21 +31,11 @@ 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;
|
|
||||||
// Don't await the fetch, pass the Promise to the context provider
|
|
||||||
const cart = getCart(cartId, 'USD');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={GeistSans.variable}>
|
<html lang="en" className={GeistSans.variable}>
|
||||||
<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-50 text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
|
||||||
<CartProvider cartPromise={cart}>
|
{children}
|
||||||
<Navbar />
|
|
||||||
<main>
|
|
||||||
{children}
|
|
||||||
<Toaster closeButton />
|
|
||||||
<WelcomeToast />
|
|
||||||
</main>
|
|
||||||
</CartProvider>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
14
app/page.tsx
14
app/page.tsx
@ -1,6 +1,9 @@
|
|||||||
import { Carousel } from 'components/carousel';
|
import { Carousel } from 'components/carousel';
|
||||||
import { ThreeItemGrid } from 'components/grid/three-items';
|
import { ThreeItemGrid } from 'components/grid/three-items';
|
||||||
import Footer from 'components/layout/footer';
|
import Footer from 'components/layout/footer';
|
||||||
|
import { Wrapper } from 'components/wrapper';
|
||||||
|
import { getCart } from 'lib/fourthwall';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
description: 'High-performance ecommerce store built with Next.js, Vercel, and Shopify.',
|
description: 'High-performance ecommerce store built with Next.js, Vercel, and Shopify.',
|
||||||
@ -9,14 +12,17 @@ export const metadata = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HomePage({ searchParams }: { searchParams: { currency?: string } }) {
|
export default async function HomePage({ searchParams }: { searchParams: { currency?: string } }) {
|
||||||
|
const cartId = cookies().get('cartId')?.value;
|
||||||
const currency = searchParams.currency || 'USD';
|
const currency = searchParams.currency || 'USD';
|
||||||
|
// Don't await the fetch, pass the Promise to the context provider
|
||||||
|
const cart = getCart(cartId, currency);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Wrapper currency={currency} cart={cart}>
|
||||||
<ThreeItemGrid currency={currency} />
|
<ThreeItemGrid currency={currency} />
|
||||||
<Carousel currency={currency}/>
|
<Carousel currency={currency} />
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@ import Footer from 'components/layout/footer';
|
|||||||
import { Gallery } from 'components/product/gallery';
|
import { Gallery } from 'components/product/gallery';
|
||||||
import { ProductProvider } from 'components/product/product-context';
|
import { ProductProvider } from 'components/product/product-context';
|
||||||
import { ProductDescription } from 'components/product/product-description';
|
import { ProductDescription } from 'components/product/product-description';
|
||||||
|
import { Wrapper } from 'components/wrapper';
|
||||||
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
||||||
import { getProduct, getProductRecommendations } from 'lib/fourthwall';
|
import { getCart, getProduct, getProductRecommendations } from 'lib/fourthwall';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
@ -50,9 +52,14 @@ export async function generateMetadata({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function ProductPage({ params, searchParams }: { params: { handle: string }, searchParams: { currency?: string } }) {
|
export default async function ProductPage({ params, searchParams }: { params: { handle: string }, searchParams: { currency?: string } }) {
|
||||||
|
const currency = searchParams.currency || 'USD';
|
||||||
|
const cartId = cookies().get('cartId')?.value;
|
||||||
|
|
||||||
|
const cart = getCart(cartId, currency)
|
||||||
|
|
||||||
const product = await getProduct({
|
const product = await getProduct({
|
||||||
handle: params.handle,
|
handle: params.handle,
|
||||||
currency: searchParams.currency || 'USD'
|
currency,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!product) return notFound();
|
if (!product) return notFound();
|
||||||
@ -75,37 +82,39 @@ export default async function ProductPage({ params, searchParams }: { params: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProductProvider>
|
<Wrapper currency={currency} cart={cart}>
|
||||||
<script
|
<ProductProvider>
|
||||||
type="application/ld+json"
|
<script
|
||||||
dangerouslySetInnerHTML={{
|
type="application/ld+json"
|
||||||
__html: JSON.stringify(productJsonLd)
|
dangerouslySetInnerHTML={{
|
||||||
}}
|
__html: JSON.stringify(productJsonLd)
|
||||||
/>
|
}}
|
||||||
<div className="mx-auto max-w-screen-2xl px-4">
|
/>
|
||||||
<div className="flex flex-col rounded-lg border border-neutral-200 bg-white p-8 md:p-12 lg:flex-row lg:gap-8 dark:border-neutral-800 dark:bg-black">
|
<div className="mx-auto max-w-screen-2xl px-4">
|
||||||
<div className="h-full w-full basis-full lg:basis-4/6">
|
<div className="flex flex-col rounded-lg border border-neutral-200 bg-white p-8 md:p-12 lg:flex-row lg:gap-8 dark:border-neutral-800 dark:bg-black">
|
||||||
<Suspense
|
<div className="h-full w-full basis-full lg:basis-4/6">
|
||||||
fallback={
|
<Suspense
|
||||||
<div className="relative aspect-square h-full max-h-[550px] w-full overflow-hidden" />
|
fallback={
|
||||||
}
|
<div className="relative aspect-square h-full max-h-[550px] w-full overflow-hidden" />
|
||||||
>
|
}
|
||||||
<Gallery
|
>
|
||||||
product={product}
|
<Gallery
|
||||||
/>
|
product={product}
|
||||||
</Suspense>
|
/>
|
||||||
</div>
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="basis-full lg:basis-2/6">
|
<div className="basis-full lg:basis-2/6">
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<ProductDescription product={product} />
|
<ProductDescription product={product} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<RelatedProducts id={product.id} />
|
||||||
</div>
|
</div>
|
||||||
<RelatedProducts id={product.id} />
|
<Footer />
|
||||||
</div>
|
</ProductProvider>
|
||||||
<Footer />
|
</Wrapper>
|
||||||
</ProductProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { TAGS } from 'lib/constants';
|
import { TAGS } from 'lib/constants';
|
||||||
import { addToCart, createCart, getCart, removeFromCart, updateCart } from 'lib/fourthwall';
|
import { addToCart, createCart, createCheckout, getCart, removeFromCart, updateCart } from 'lib/fourthwall';
|
||||||
import { revalidateTag } from 'next/cache';
|
import { revalidateTag } from 'next/cache';
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
@ -29,7 +29,7 @@ export async function removeItem(prevState: any, merchandiseId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cart = await getCart(cartId);
|
const cart = await getCart(cartId, 'USD');
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
return 'Error fetching cart';
|
return 'Error fetching cart';
|
||||||
@ -64,7 +64,7 @@ export async function updateItemQuantity(
|
|||||||
const { merchandiseId, quantity } = payload;
|
const { merchandiseId, quantity } = payload;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cart = await getCart(cartId);
|
const cart = await getCart(cartId, 'USD');
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
return 'Error fetching cart';
|
return 'Error fetching cart';
|
||||||
@ -96,20 +96,22 @@ export async function updateItemQuantity(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function redirectToCheckout() {
|
export async function redirectToCheckout(currency: string) {
|
||||||
let cartId = cookies().get('cartId')?.value;
|
let cartId = cookies().get('cartId')?.value;
|
||||||
|
|
||||||
if (!cartId) {
|
if (!cartId) {
|
||||||
return 'Missing cart ID';
|
return 'Missing cart ID';
|
||||||
}
|
}
|
||||||
|
|
||||||
let cart = await getCart(cartId);
|
let cart = await getCart(cartId, 'USD');
|
||||||
|
|
||||||
if (!cart) {
|
if (!cart) {
|
||||||
return 'Error fetching cart';
|
return 'Error fetching cart';
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect(cart.checkoutUrl);
|
const { id } = await createCheckout(cartId, currency);
|
||||||
|
|
||||||
|
redirect(`${process.env.FW_CHECKOUT}/checkout/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createCartAndSetCookie() {
|
export async function createCartAndSetCookie() {
|
||||||
|
@ -27,7 +27,6 @@ function SubmitButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(selectedVariantId);
|
|
||||||
if (!selectedVariantId) {
|
if (!selectedVariantId) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
@ -145,7 +145,7 @@ function cartReducer(state: Cart | undefined, action: CartAction): Cart {
|
|||||||
|
|
||||||
export function CartProvider({
|
export function CartProvider({
|
||||||
children,
|
children,
|
||||||
cartPromise
|
cartPromise,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
cartPromise: Promise<Cart | undefined>;
|
cartPromise: Promise<Cart | undefined>;
|
||||||
|
@ -192,7 +192,7 @@ export default function CartModal() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form action={redirectToCheckout}>
|
<form action={() => redirectToCheckout(cart.currency)}>
|
||||||
<CheckoutButton />
|
<CheckoutButton />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,13 +43,13 @@ function ThreeItemGridItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ThreeItemGrid({ currency } : { currency: string }) {
|
export async function ThreeItemGrid({currency}: { currency: string}) {
|
||||||
// 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
|
currency,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null;
|
if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null;
|
||||||
|
|
||||||
const [firstProduct, secondProduct, thirdProduct] = homepageItems;
|
const [firstProduct, secondProduct, thirdProduct] = homepageItems;
|
||||||
|
61
components/layout/navbar/currency.tsx
Normal file
61
components/layout/navbar/currency.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
const Currencies = [
|
||||||
|
'USD',
|
||||||
|
'EUR',
|
||||||
|
'GBP',
|
||||||
|
'CAD',
|
||||||
|
'AUD',
|
||||||
|
'JPY',
|
||||||
|
];
|
||||||
|
|
||||||
|
export function CurrencySelector({ currency }: { currency: string; }) {
|
||||||
|
const selectedCurrency = currency;
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleSelect = (currency: string) => {
|
||||||
|
// navigate to the current page with the new currency as query param
|
||||||
|
const newParams = new URLSearchParams(window.location.search);
|
||||||
|
newParams.set('currency', currency);
|
||||||
|
window.history.pushState({}, '', `${window.location.pathname}?${newParams}`);
|
||||||
|
window.location.reload();
|
||||||
|
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative inline-block text-left">
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500"
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
>
|
||||||
|
{selectedCurrency}
|
||||||
|
<svg className="-mr-1 ml-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||||
|
<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<div className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10">
|
||||||
|
<div className="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
|
||||||
|
{Currencies.map((currency) => (
|
||||||
|
<button
|
||||||
|
key={currency}
|
||||||
|
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900"
|
||||||
|
role="menuitem"
|
||||||
|
onClick={() => handleSelect(currency)}
|
||||||
|
>
|
||||||
|
{currency}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
import CartModal from 'components/cart/modal';
|
import CartModal from 'components/cart/modal';
|
||||||
import LogoSquare from 'components/logo-square';
|
import LogoSquare from 'components/logo-square';
|
||||||
import { getMenu } from 'lib/fourthwall';
|
|
||||||
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 { CurrencySelector } from './currency';
|
||||||
import MobileMenu from './mobile-menu';
|
import MobileMenu from './mobile-menu';
|
||||||
import Search, { SearchSkeleton } from './search';
|
import Search, { SearchSkeleton } from './search';
|
||||||
|
|
||||||
const { SITE_NAME } = process.env;
|
const { SITE_NAME } = process.env;
|
||||||
|
|
||||||
export async function Navbar() {
|
export function Navbar({currency}: {currency: string}) {
|
||||||
const menu = await getMenu('next-js-frontend-header-menu');
|
const menu: any[] = [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="relative flex items-center justify-between p-4 lg:px-6">
|
<nav className="relative flex items-center justify-between p-4 lg:px-6">
|
||||||
<div className="block flex-none md:hidden">
|
<div className="block flex-none md:hidden">
|
||||||
@ -53,9 +53,7 @@ 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">
|
<CurrencySelector currency={currency} />
|
||||||
USD
|
|
||||||
</div>
|
|
||||||
<CartModal />
|
<CartModal />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
17
components/wrapper.tsx
Normal file
17
components/wrapper.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Cart } from "lib/shopify/types";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { Toaster } from "sonner";
|
||||||
|
import { CartProvider } from "./cart/cart-context";
|
||||||
|
import { Navbar } from "./layout/navbar";
|
||||||
|
import { WelcomeToast } from "./welcome-toast";
|
||||||
|
|
||||||
|
export function Wrapper({ children, currency, cart }: { children: ReactNode, currency: string, cart: Promise<Cart | undefined> }) {
|
||||||
|
return <CartProvider cartPromise={cart}>
|
||||||
|
<Navbar currency={currency} />
|
||||||
|
<main>
|
||||||
|
{children}
|
||||||
|
<Toaster closeButton />
|
||||||
|
<WelcomeToast />
|
||||||
|
</main>
|
||||||
|
</CartProvider>
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
import { Cart, Menu, Product } from "lib/shopify/types";
|
import { Cart, Menu, Product } from "lib/shopify/types";
|
||||||
import { reshapeCart, reshapeProduct, reshapeProducts } from "./reshape";
|
import { reshapeCart, reshapeProduct, reshapeProducts } from "./reshape";
|
||||||
import { FourthwallCart, FourthwallProduct } from "./types";
|
import { FourthwallCart, FourthwallCheckout, FourthwallProduct } from "./types";
|
||||||
|
|
||||||
|
const API_URL = process.env.FW_API_URL
|
||||||
|
const API_SECRET = process.env.FW_SECRET
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helpers
|
* Helpers
|
||||||
@ -74,16 +77,14 @@ export async function getCollectionProducts({
|
|||||||
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}¤cy=${currency}`, {
|
const res = await fourthwallGet<{results: FourthwallProduct[]}>(`${API_URL}/api/public/v1.0/collections/${collection}/products?secret=${API_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.warn(`No collection found for \`${collection}\``);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ export async function getCollectionProducts({
|
|||||||
*/
|
*/
|
||||||
export async function getProduct({ handle, currency } : { handle: string, currency: 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}¤cy=${currency}`, {
|
const res = await fourthwallGet<{results: FourthwallProduct[]}>(`${API_URL}/api/public/v1.0/collections/${process.env.FW_COLLECTION}/products?secret=${API_SECRET}¤cy=${currency}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'X-ShopId': process.env.FW_SHOPID || ''
|
'X-ShopId': process.env.FW_SHOPID || ''
|
||||||
}
|
}
|
||||||
@ -120,7 +121,7 @@ export async function getCart(cartId: string | undefined, currency: string): Pro
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fourthwallGet<FourthwallCart>(`${process.env.FW_URL}/api/public/v1.0/carts/${cartId}?secret=${process.env.FW_SECRET}`, {
|
const res = await fourthwallGet<FourthwallCart>(`${API_URL}/api/public/v1.0/carts/${cartId}?secret=${API_SECRET}¤cy=${currency}`, {
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ export async function getCart(cartId: string | undefined, currency: string): Pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createCart(): Promise<Cart> {
|
export async function createCart(): Promise<Cart> {
|
||||||
const res = await fourthwallPost<FourthwallCart>(`https://api.staging.fourthwall.com/api/public/v1.0/carts?secret=${process.env.FW_SECRET}`, {
|
const res = await fourthwallPost<FourthwallCart>(`https://api.staging.fourthwall.com/api/public/v1.0/carts?secret=${API_SECRET}`, {
|
||||||
items: []
|
items: []
|
||||||
}, {
|
}, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -149,7 +150,7 @@ export async function addToCart(
|
|||||||
quantity: line.quantity
|
quantity: line.quantity
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const res = await fourthwallPost<FourthwallCart>(`${process.env.FW_URL}/api/public/v1.0/carts/${cartId}/add?secret=${process.env.FW_SECRET}`, {
|
const res = await fourthwallPost<FourthwallCart>(`${API_URL}/api/public/v1.0/carts/${cartId}/add?secret=${API_SECRET}`, {
|
||||||
items,
|
items,
|
||||||
}, {
|
}, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -166,7 +167,7 @@ export async function removeFromCart(cartId: string, lineIds: string[]): Promise
|
|||||||
variantId: id
|
variantId: id
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const res = await fourthwallPost<FourthwallCart>(`${process.env.FW_URL}/api/public/v1.0/carts/${cartId}/remove?secret=${process.env.FW_SECRET}`, {
|
const res = await fourthwallPost<FourthwallCart>(`${API_URL}/api/public/v1.0/carts/${cartId}/remove?secret=${API_SECRET}`, {
|
||||||
items,
|
items,
|
||||||
}, {
|
}, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -187,7 +188,7 @@ export async function updateCart(
|
|||||||
quantity: line.quantity
|
quantity: line.quantity
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const res = await fourthwallPost<FourthwallCart>(`${process.env.FW_URL}/api/public/v1.0/carts/${cartId}/change?secret=${process.env.FW_SECRET}`, {
|
const res = await fourthwallPost<FourthwallCart>(`${API_URL}/api/public/v1.0/carts/${cartId}/change?secret=${API_SECRET}`, {
|
||||||
items,
|
items,
|
||||||
}, {
|
}, {
|
||||||
headers: {
|
headers: {
|
||||||
@ -199,6 +200,21 @@ export async function updateCart(
|
|||||||
return reshapeCart(res.body);
|
return reshapeCart(res.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createCheckout(
|
||||||
|
cartId: string,
|
||||||
|
cartCurrency: string
|
||||||
|
): Promise<FourthwallCheckout> {
|
||||||
|
const res = await fourthwallPost<{ id: string }>(`${API_URL}/api/public/v1.0/checkouts?secret=${API_SECRET}`, {
|
||||||
|
cartId,
|
||||||
|
cartCurrency
|
||||||
|
}, {
|
||||||
|
headers: {
|
||||||
|
'X-ShopId': process.env.FW_SHOPID || ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.body;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Stubbed out
|
* TODO: Stubbed out
|
||||||
|
@ -167,6 +167,7 @@ export const reshapeCart = (cart: FourthwallCart): Cart => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
lines: cart.items.map(reshapeCartItem),
|
lines: cart.items.map(reshapeCartItem),
|
||||||
|
currency: currencyCode,
|
||||||
// TODO: Stubbed out
|
// TODO: Stubbed out
|
||||||
checkoutUrl: 'TT',
|
checkoutUrl: 'TT',
|
||||||
totalQuantity: cart.items.map((item) => item.quantity).reduce((a, b) => a + b, 0)
|
totalQuantity: cart.items.map((item) => item.quantity).reduce((a, b) => a + b, 0)
|
||||||
|
@ -51,3 +51,7 @@ export type FourthwallCartItem = {
|
|||||||
variant: FourthwallProductVariant;
|
variant: FourthwallProductVariant;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FourthwallCheckout = {
|
||||||
|
id: string
|
||||||
|
};
|
||||||
|
@ -10,6 +10,7 @@ export type Edge<T> = {
|
|||||||
|
|
||||||
export type Cart = Omit<ShopifyCart, 'lines'> & {
|
export type Cart = Omit<ShopifyCart, 'lines'> & {
|
||||||
lines: CartItem[];
|
lines: CartItem[];
|
||||||
|
currency: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CartProduct = {
|
export type CartProduct = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user