From 899737f765cd77613ab2e9bac0fb82c6c1acba7e Mon Sep 17 00:00:00 2001 From: mkucmus Date: Thu, 13 Jul 2023 11:40:21 +0200 Subject: [PATCH 1/6] fix: use referencedId for item id --- lib/shopware/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/shopware/index.ts b/lib/shopware/index.ts index bb974a196..b5938c8ed 100644 --- a/lib/shopware/index.ts +++ b/lib/shopware/index.ts @@ -239,7 +239,7 @@ export async function getCart(): Promise { id: cartData.token || '', lines: cartData.lineItems?.map((lineItem) => ({ - id: lineItem.id || '', + id: lineItem.referencedId || '', quantity: lineItem.quantity, cost: { totalAmount: { From ab51c9464e091243bc193abbdbc2b21c3fa13012 Mon Sep 17 00:00:00 2001 From: mkucmus Date: Thu, 13 Jul 2023 23:09:28 +0200 Subject: [PATCH 2/6] feat: add to cart and cart sidebar --- app/product/[...handle]/page.tsx | 7 ++-- components/cart/actions.ts | 29 ++++++++------- components/cart/add-to-cart.tsx | 15 ++++---- components/cart/index.tsx | 6 ++-- components/cart/modal.tsx | 5 +-- lib/shopware/api.ts | 61 +++++++++++++++++++++++--------- lib/shopware/index.ts | 9 +++-- pnpm-lock.yaml | 28 +-------------- 8 files changed, 88 insertions(+), 72 deletions(-) diff --git a/app/product/[...handle]/page.tsx b/app/product/[...handle]/page.tsx index 573fe475b..f1ae3aa18 100644 --- a/app/product/[...handle]/page.tsx +++ b/app/product/[...handle]/page.tsx @@ -2,12 +2,11 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { Suspense } from 'react'; +import { AddToCart } from 'components/cart/add-to-cart'; import Grid from 'components/grid'; import Footer from 'components/layout/footer'; import ProductGridItems from 'components/layout/product-grid-items'; -import { AddToCart } from 'components/cart/add-to-cart'; import { Gallery } from 'components/product/gallery'; -import { VariantSelector } from 'components/product/variant-selector'; import Prose from 'components/prose'; import { HIDDEN_PRODUCT_TAG } from 'lib/constants'; import { getProduct, getProductRecommendations } from 'lib/shopware'; @@ -98,13 +97,13 @@ export default async function ProductPage({ params }: { params: { handle: string
- + {/* */} {product.descriptionHtml ? ( ) : null} - +
diff --git a/components/cart/actions.ts b/components/cart/actions.ts index 5e5f2b2ea..b5a187971 100644 --- a/components/cart/actions.ts +++ b/components/cart/actions.ts @@ -1,17 +1,21 @@ 'use server'; -import { addToCart, removeFromCart, updateCart } from 'lib/shopify'; +import { requestAddToCart, requestCart } from 'lib/shopware/api'; import { cookies } from 'next/headers'; +export const fetchCart = async (cartId?: string) => { + await requestCart(cartId); +}; export const addItem = async (variantId: string | undefined): Promise => { - const cartId = cookies().get('cartId')?.value; + const cartId = cookies().get('sw-context-token')?.value || ''; - if (!cartId || !variantId) { + if (!variantId) { return new Error('Missing cartId or variantId'); } try { - await addToCart(cartId, [{ merchandiseId: variantId, quantity: 1 }]); + await requestAddToCart(variantId, cartId); } catch (e) { + console.error('eeeee', e); return new Error('Error adding item', { cause: e }); } }; @@ -23,7 +27,7 @@ export const removeItem = async (lineId: string): Promise => return new Error('Missing cartId'); } try { - await removeFromCart(cartId, [lineId]); + //await removeFromCart(cartId, [lineId]); } catch (e) { return new Error('Error removing item', { cause: e }); } @@ -44,14 +48,15 @@ export const updateItemQuantity = async ({ return new Error('Missing cartId'); } try { - await updateCart(cartId, [ - { - id: lineId, - merchandiseId: variantId, - quantity - } - ]); + // await updateCart(cartId, [ + // { + // id: lineId, + // merchandiseId: variantId, + // quantity + // } + // ]); } catch (e) { return new Error('Error updating item quantity', { cause: e }); } }; + diff --git a/components/cart/add-to-cart.tsx b/components/cart/add-to-cart.tsx index eecee848e..5362ff590 100644 --- a/components/cart/add-to-cart.tsx +++ b/components/cart/add-to-cart.tsx @@ -7,13 +7,16 @@ import { useEffect, useState, useTransition } from 'react'; import LoadingDots from 'components/loading-dots'; import { ProductVariant } from 'lib/shopify/types'; +import { Product } from 'lib/shopware/types'; export function AddToCart({ + product, variants, - availableForSale + availableForSale, }: { variants: ProductVariant[]; availableForSale: boolean; + product: Product }) { const [selectedVariantId, setSelectedVariantId] = useState(variants[0]?.id); const router = useRouter(); @@ -39,12 +42,12 @@ export function AddToCart({ onClick={() => { if (!availableForSale) return; startTransition(async () => { - const error = await addItem(selectedVariantId); + const error = await addItem(product.id); - if (error) { - alert(error); - return; - } + if (error) { + console.error(error); + return; + } router.refresh(); }); diff --git a/components/cart/index.tsx b/components/cart/index.tsx index ab2372af1..6a68685ff 100644 --- a/components/cart/index.tsx +++ b/components/cart/index.tsx @@ -1,11 +1,13 @@ +import { fetchCart } from 'components/cart/actions'; import { getCart } from 'lib/shopware'; import { cookies } from 'next/headers'; import CartModal from './modal'; export default async function Cart() { const cartId = cookies().get('sw-context-token')?.value; - let cartIdUpdated = true; - const cart = await getCart(); + await fetchCart(cartId); + let cartIdUpdated = false; + const cart = await getCart(cartId); if (cartId !== cart.id) { cartIdUpdated = true; diff --git a/components/cart/modal.tsx b/components/cart/modal.tsx index bb14abf2a..71deaa6a4 100644 --- a/components/cart/modal.tsx +++ b/components/cart/modal.tsx @@ -21,7 +21,7 @@ type MerchandiseSearchParams = { }; export default function CartModal({ cart, cartIdUpdated }: { cart: Cart; cartIdUpdated: boolean }) { - const [, setCookie] = useCookies(['cartId']); + const [, setCookie] = useCookies(['sw-context-token']); const [isOpen, setIsOpen] = useState(false); const quantityRef = useRef(cart.totalQuantity); const openCart = () => setIsOpen(true); @@ -29,7 +29,7 @@ export default function CartModal({ cart, cartIdUpdated }: { cart: Cart; cartIdU useEffect(() => { if (cartIdUpdated) { - setCookie('cartId', cart.id, { + setCookie('sw-context-token', cart.id, { path: '/', sameSite: 'strict', secure: process.env.NODE_ENV === 'production' @@ -39,6 +39,7 @@ export default function CartModal({ cart, cartIdUpdated }: { cart: Cart; cartIdU }, [setCookie, cartIdUpdated, cart.id]); useEffect(() => { + console.warn('cart modal', cart); // Open cart modal when when quantity changes. if (cart.totalQuantity !== quantityRef.current) { // But only if it's not already open (quantity also changes when editing items in cart). diff --git a/lib/shopware/api.ts b/lib/shopware/api.ts index 89c666674..ed73ee8b4 100644 --- a/lib/shopware/api.ts +++ b/lib/shopware/api.ts @@ -1,5 +1,6 @@ import { createAPIClient, RequestReturnType } from '@shopware/api-client'; import { operations } from '@shopware/api-client/api-types'; +import { cookies } from 'next/headers'; import { ExtendedCategory, ExtendedCriteria, @@ -19,11 +20,19 @@ import { const domainSW = `https://${process.env.SHOPWARE_STORE_DOMAIN!}/${process.env.SHOPWARE_API_TYPE!}`; const accessTokenSW = `${process.env.SHOPWARE_ACCESS_TOKEN}`; -const apiInstance = createAPIClient({ - baseURL: domainSW, - accessToken: accessTokenSW, - apiType: 'store-api' -}); +function getApiClient(cartId?: string) { + const apiInstance = createAPIClient({ + baseURL: domainSW, + accessToken: accessTokenSW, + apiType: 'store-api', + contextToken: cartId, + onContextChanged(newContextToken: string) { + //cookies().set('sw-context-token', newContextToken); + } + }); + + return apiInstance; +} // reimport operations return types to use it in application export type ApiReturnType = RequestReturnType< @@ -35,7 +44,7 @@ export async function requestNavigation( type: StoreNavigationTypeSW, depth: number ): Promise { - return await apiInstance.invoke( + return await getApiClient(cookies().get('sw-context-token')).invoke( 'readNavigation post /navigation/{activeId}/{rootId} sw-include-seo-urls', { activeId: type, @@ -49,7 +58,7 @@ export async function requestCategory( categoryId: string, criteria?: Partial ): Promise { - return await apiInstance.invoke('readCategory post /category/{navigationId}?slots', { + return await getApiClient().invoke('readCategory post /category/{navigationId}?slots', { navigationId: categoryId, criteria }); @@ -58,20 +67,20 @@ export async function requestCategory( export async function requestCategoryList( criteria: Partial ): Promise { - return await apiInstance.invoke('readCategoryList post /category', criteria); + return await getApiClient().invoke('readCategoryList post /category', criteria); } export async function requestProductsCollection( criteria: Partial ): Promise { - return await apiInstance.invoke('readProduct post /product', criteria); + return await getApiClient().invoke('readProduct post /product', criteria); } export async function requestCategoryProductsCollection( categoryId: string, criteria: Partial ): Promise { - return await apiInstance.invoke('readProductListing post /product-listing/{categoryId}', { + return await getApiClient().invoke('readProductListing post /product-listing/{categoryId}', { ...criteria, categoryId: categoryId }); @@ -80,14 +89,14 @@ export async function requestCategoryProductsCollection( export async function requestSearchCollectionProducts( criteria?: Partial ): Promise { - return await apiInstance.invoke('searchPage post /search', { + return await getApiClient().invoke('searchPage post /search', { search: encodeURIComponent(criteria?.query || ''), ...criteria }); } export async function requestSeoUrls(routeName: RouteNames, page: number = 1, limit: number = 100) { - return await apiInstance.invoke('readSeoUrl post /seo-url', { + return await getApiClient().invoke('readSeoUrl post /seo-url', { page: page, limit: limit, filter: [ @@ -105,7 +114,7 @@ export async function requestSeoUrl( page: number = 1, limit: number = 1 ): Promise { - return await apiInstance.invoke('readSeoUrl post /seo-url', { + return await getApiClient().invoke('readSeoUrl post /seo-url', { page: page, limit: limit, filter: [ @@ -134,7 +143,7 @@ export async function requestCrossSell( productId: string, criteria?: Partial ): Promise { - return await apiInstance.invoke( + return await getApiClient().invoke( 'readProductCrossSellings post /product/{productId}/cross-selling', { productId: productId, @@ -143,6 +152,26 @@ export async function requestCrossSell( ); } -export async function requestCart() { - return apiInstance.invoke('readCart get /checkout/cart?name', {}); +export async function requestCart(cartId?: string) { + return getApiClient(cartId).invoke('readCart get /checkout/cart?name', {}); +} + +export async function requestContext(cartId?: string) { + return getApiClient(cartId).invoke('readCart get /checkout/cart?name', {}); +} + +export async function requestAddToCart(itemId: string, cartId: string) { + try { + return getApiClient(cartId).invoke('addLineItem post /checkout/cart/line-item', { + items: [ + { + referencedId: itemId, + quantity: 1, + type: 'product' + } + ] + }); + } catch (e) { + console.error('e', e); + } } \ No newline at end of file diff --git a/lib/shopware/index.ts b/lib/shopware/index.ts index b5938c8ed..a7a7fe1b4 100644 --- a/lib/shopware/index.ts +++ b/lib/shopware/index.ts @@ -217,8 +217,8 @@ export async function getProductRecommendations(productId: string): Promise { - const cartData = await requestCart(); +export async function getCart(cartId?: string): Promise { + const cartData = await requestCart(cartId); let cart: Cart = { checkoutUrl: 'https://frontends-demo.vercel.app', @@ -260,7 +260,10 @@ export async function getCart(): Promise { title: lineItem.label }, availableForSale: true, - featuredImage: (lineItem as any).cover?.url, + featuredImage: { + altText: 'Cover image of ' + lineItem.label, + url: (lineItem as any).cover?.url + }, handle: '', options: [], variants: [], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 169c48afc..dcd7e05f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ dependencies: version: 1.7.15(react-dom@18.2.0)(react@18.2.0) '@shopware/api-client': specifier: 0.0.0-canary-20230706101754 - version: 0.0.0-canary-20230706101754 + version: link:../../frontends/packages/api-client-next '@vercel/og': specifier: ^0.5.8 version: 0.5.8 @@ -379,12 +379,6 @@ packages: resolution: {integrity: sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==} dev: true - /@shopware/api-client@0.0.0-canary-20230706101754: - resolution: {integrity: sha512-h7nCTWVu6bLbxdKT8vEJcUVKVu/RveaZ3M1PxrQAMiP6b2nQeqRh5+QlqTEFGk+Middy11MINH70Wp6GMx+Y+A==} - dependencies: - ofetch: 1.1.1 - dev: false - /@shuding/opentype.js@1.4.0-beta.0: resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} engines: {node: '>= 8.0.0'} @@ -1046,10 +1040,6 @@ packages: engines: {node: '>=6'} dev: true - /destr@2.0.0: - resolution: {integrity: sha512-FJ9RDpf3GicEBvzI3jxc2XhHzbqD8p4ANw/1kPsFBfTvP1b7Gn/Lg1vO7R9J4IVgoMbyUmFrFGZafJ1hPZpvlg==} - dev: false - /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true @@ -2383,10 +2373,6 @@ packages: - babel-plugin-macros dev: false - /node-fetch-native@1.2.0: - resolution: {integrity: sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==} - dev: false - /node-releases@2.0.12: resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==} dev: true @@ -2486,14 +2472,6 @@ packages: es-abstract: 1.21.2 dev: true - /ofetch@1.1.1: - resolution: {integrity: sha512-SSMoktrp9SNLi20BWfB/BnnKcL0RDigXThD/mZBeQxkIRv1xrd9183MtLdsqRYLYSqW0eTr5t8w8MqjNhvoOQQ==} - dependencies: - destr: 2.0.0 - node-fetch-native: 1.2.0 - ufo: 1.1.2 - dev: false - /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -3457,10 +3435,6 @@ packages: hasBin: true dev: true - /ufo@1.1.2: - resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} - dev: false - /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: From 55453755279942782c169668f9050a48178e6317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Meyer?= Date: Fri, 21 Jul 2023 08:43:01 +0200 Subject: [PATCH 3/6] feat(poc): working on addToCart --- components/cart/actions.ts | 4 ++++ components/cart/add-to-cart.tsx | 3 ++- components/cart/modal.tsx | 2 +- lib/shopware/api.ts | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/components/cart/actions.ts b/components/cart/actions.ts index 75b1a14b7..d21c47697 100644 --- a/components/cart/actions.ts +++ b/components/cart/actions.ts @@ -27,6 +27,7 @@ export const removeItem = async (lineId: string): Promise => return new Error('Missing cartId'); } try { + console.log('removeItem lineId', lineId); //await removeFromCart(cartId, [lineId]); } catch (e) { return new Error('Error removing item', { cause: e }); @@ -48,6 +49,9 @@ export const updateItemQuantity = async ({ return new Error('Missing cartId'); } try { + console.log('lineId', lineId); + console.log('variantId', variantId); + console.log('quantity', quantity); // await updateCart(cartId, [ // { // id: lineId, diff --git a/components/cart/add-to-cart.tsx b/components/cart/add-to-cart.tsx index e353938ee..fbce2622c 100644 --- a/components/cart/add-to-cart.tsx +++ b/components/cart/add-to-cart.tsx @@ -33,7 +33,8 @@ export function AddToCart({ if (variant) { setSelectedVariantId(variant.id); } - }, [searchParams, variants, setSelectedVariantId]); + console.log('selectedVariantId', selectedVariantId); + }, [searchParams, variants, setSelectedVariantId, selectedVariantId]); return (