Skip to content

Commit c484494

Browse files
pbennett1-godaddywcole1-godaddyampcode-com
authored
Cart components and mutations (#1222)
* add cart mutations * add channel id to provider * update catalog and order schema and endpoints * fix first sku lookup * add storefront translations * use id instead of sku for addToCart * add remove from cart functionality * add redirect to hosted checkout * add first sku to cart product grid if multi-variant * add tailwind animatino ulitilies * remove unused skuCount * fix: resolve hydration mismatch in Cart and remove internal env references Amp-Thread-ID: https://ampcode.com/threads/T-cdf96c39-faac-4ab4-a89d-1fddcf3b0cb5 Co-authored-by: Amp <[email protected]> * chore: add changeset for cart functionality Amp-Thread-ID: https://ampcode.com/threads/T-cdf96c39-faac-4ab4-a89d-1fddcf3b0cb5 Co-authored-by: Amp <[email protected]> --------- Co-authored-by: Wes Cole <[email protected]> Co-authored-by: Amp <[email protected]>
1 parent 173f704 commit c484494

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2292
-126
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@godaddy/react": patch
3+
---
4+
5+
Added cart functionality with `useAddToCart` hook and `Cart` component.
6+
Fixed hydration mismatch in `Cart` component.
7+
Removed internal environment references.

examples/nextjs/app/providers.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
2525
apiHost={process.env.NEXT_PUBLIC_GODADDY_API_HOST}
2626
storeId={process.env.NEXT_PUBLIC_GODADDY_STORE_ID}
2727
clientId={process.env.NEXT_PUBLIC_GODADDY_CLIENT_ID}
28+
channelId={process.env.NEXT_PUBLIC_GODADDY_CHANNEL_ID}
2829
Link={Link}
2930
appearance={{
3031
variables: { primary: '#ff0000', 'primary-foreground': '#FFFFFF' },
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use server';
2+
3+
import { createCheckoutSession } from '@godaddy/react/server';
4+
import { redirect } from 'next/navigation';
5+
6+
export async function checkoutWithOrder(orderId: string) {
7+
const session = await createCheckoutSession(
8+
{
9+
returnUrl: `https://godaddy.com`,
10+
successUrl: `https://godaddy.com/success`,
11+
draftOrderId: orderId,
12+
storeId: process.env.NEXT_PUBLIC_GODADDY_STORE_ID || '',
13+
channelId: process.env.NEXT_PUBLIC_GODADDY_CHANNEL_ID || '',
14+
enableShippingAddressCollection: true,
15+
enableBillingAddressCollection: true,
16+
enableTaxCollection: true,
17+
shipping: {
18+
fulfillmentLocationId: 'default-location',
19+
originAddress: {
20+
addressLine1: '1600 Pennsylvania Ave NW',
21+
adminArea1: 'DC',
22+
adminArea3: 'Washington',
23+
countryCode: 'US',
24+
postalCode: '20500',
25+
},
26+
},
27+
taxes: {
28+
originAddress: {
29+
addressLine1: '1600 Pennsylvania Ave NW',
30+
adminArea1: 'DC',
31+
adminArea3: 'Washington',
32+
countryCode: 'US',
33+
postalCode: '20500',
34+
},
35+
},
36+
locations: [
37+
{
38+
id: 'default-location',
39+
isDefault: true,
40+
address: {
41+
addressLine1: '1600 Pennsylvania Ave NW',
42+
adminArea1: 'DC',
43+
adminArea3: 'Washington',
44+
countryCode: 'US',
45+
postalCode: '20500',
46+
},
47+
},
48+
],
49+
paymentMethods: {
50+
card: {
51+
processor: 'godaddy',
52+
checkoutTypes: ['standard'],
53+
},
54+
express: {
55+
processor: 'godaddy',
56+
checkoutTypes: ['express'],
57+
},
58+
paypal: {
59+
processor: 'paypal',
60+
checkoutTypes: ['standard'],
61+
},
62+
offline: {
63+
processor: 'offline',
64+
checkoutTypes: ['standard'],
65+
},
66+
},
67+
},
68+
{
69+
auth: {
70+
clientId: process.env.NEXT_PUBLIC_GODADDY_CLIENT_ID || '',
71+
clientSecret: process.env.GODADDY_CLIENT_SECRET || '',
72+
},
73+
}
74+
);
75+
76+
if (!session) {
77+
throw new Error('Failed to create checkout session');
78+
}
79+
80+
console.log({ session });
81+
82+
if (!session.url) {
83+
throw new Error('No checkout URL returned');
84+
}
85+
86+
redirect(session.url);
87+
}

examples/nextjs/app/store/layout.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { Cart } from '@godaddy/react';
44
import { ShoppingCart } from 'lucide-react';
55
import { createContext, useContext, useState } from 'react';
6+
import { checkoutWithOrder } from './actions';
67

78
interface CartContextType {
89
openCart: () => void;
@@ -25,10 +26,20 @@ export default function StoreLayout({
2526
children: React.ReactNode;
2627
}) {
2728
const [isCartOpen, setIsCartOpen] = useState(false);
29+
const [isCheckingOut, setIsCheckingOut] = useState(false);
2830

2931
const openCart = () => setIsCartOpen(true);
3032
const closeCart = () => setIsCartOpen(false);
3133

34+
const handleCheckout = async (orderId: string) => {
35+
setIsCheckingOut(true);
36+
try {
37+
await checkoutWithOrder(orderId);
38+
} catch (_error) {
39+
setIsCheckingOut(false);
40+
}
41+
};
42+
3243
return (
3344
<CartContext.Provider value={{ openCart, closeCart }}>
3445
<section className='relative max-w-6xl mx-auto'>
@@ -43,7 +54,12 @@ export default function StoreLayout({
4354

4455
{children}
4556

46-
<Cart open={isCartOpen} onOpenChange={setIsCartOpen} />
57+
<Cart
58+
open={isCartOpen}
59+
onOpenChange={setIsCartOpen}
60+
onCheckout={handleCheckout}
61+
isCheckingOut={isCheckingOut}
62+
/>
4763
</section>
4864
</CartContext.Provider>
4965
);

examples/nextjs/app/store/product/[productId]/product.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import { ProductDetails } from '@godaddy/react';
44
import { ArrowLeft } from 'lucide-react';
55
import Link from 'next/link';
6+
import { useCart } from '../../layout';
67

78
export default function Product({ productId }: { productId: string }) {
9+
const { openCart } = useCart();
10+
811
return (
912
<div className='container mx-auto'>
1013
<Link
@@ -14,7 +17,10 @@ export default function Product({ productId }: { productId: string }) {
1417
<ArrowLeft className='h-4 w-4' />
1518
Back to Store
1619
</Link>
17-
<ProductDetails productId={productId} />
20+
<ProductDetails
21+
productId={productId}
22+
onAddToCartSuccess={openCart}
23+
/>
1824
</div>
1925
);
2026
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
'use client';
22

33
import { ProductGrid } from '@godaddy/react';
4+
import { useCart } from './layout';
45

56
export default function ProductsPage() {
7+
const { openCart } = useCart();
8+
69
return (
710
<div className='container mx-auto'>
8-
<ProductGrid getProductHref={sku => `/store/product/${sku}`} />
11+
<ProductGrid
12+
getProductHref={sku => `/store/product/${sku}`}
13+
onAddToCartSuccess={openCart}
14+
/>
915
</div>
1016
);
1117
}

packages/localizations/src/deDe.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,4 +348,39 @@ export const deDe = {
348348
DEPENDENCY_ERROR:
349349
'Wir können Ihre Bestellung derzeit nicht bearbeiten. Bitte warten Sie einen Moment und versuchen Sie es erneut',
350350
},
351+
storefront: {
352+
product: 'Produkt',
353+
sale: 'SALE',
354+
noImage: 'Kein Bild',
355+
noImageAvailable: 'Kein Bild verfügbar',
356+
selectOptions: 'Optionen auswählen',
357+
adding: 'Wird hinzugefügt...',
358+
addToCart: 'In den Warenkorb',
359+
shoppingCart: 'Warenkorb',
360+
failedToLoadCart: 'Warenkorb konnte nicht geladen werden:',
361+
retry: 'Wiederholen',
362+
yourCartIsEmpty: 'Ihr Warenkorb ist leer',
363+
addItemsToGetStarted: 'Fügen Sie Artikel hinzu, um zu beginnen',
364+
errorLoadingProducts: 'Fehler beim Laden der Produkte:',
365+
errorLoadingProduct: 'Fehler beim Laden des Produkts:',
366+
productNotFound: 'Produkt nicht gefunden',
367+
loadingVariantDetails: 'Variantendetails werden geladen...',
368+
combinationNotAvailable:
369+
'Diese Kombination ist nicht verfügbar. Bitte wählen Sie andere Optionen.',
370+
variantsMatch:
371+
'Varianten entsprechen Ihrer Auswahl. Wählen Sie weitere Attribute aus, um die Auswahl einzugrenzen.',
372+
quantity: 'Menge',
373+
addingToCart: 'Wird zum Warenkorb hinzugefügt...',
374+
outOfStock: 'Nicht vorrätig',
375+
viewDetails: 'Details anzeigen',
376+
productType: 'Produkttyp:',
377+
productId: 'Produkt-ID:',
378+
selectedSku: 'Ausgewählte SKU:',
379+
stockStatus: 'Lagerstatus:',
380+
lowStock: 'Geringer Lagerbestand',
381+
inStock: 'Auf Lager',
382+
remove: 'Entfernen',
383+
removing: 'Wird entfernt...',
384+
checkout: 'Zur Kasse',
385+
},
351386
};

packages/localizations/src/enIe.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,4 +325,39 @@ export const enIe = {
325325
DEPENDENCY_ERROR:
326326
"We're unable to process your order right now. Please wait a moment and try again",
327327
},
328+
storefront: {
329+
product: 'Product',
330+
sale: 'SALE',
331+
noImage: 'No image',
332+
noImageAvailable: 'No image available',
333+
selectOptions: 'Select Options',
334+
adding: 'Adding...',
335+
addToCart: 'Add to Cart',
336+
shoppingCart: 'Shopping Cart',
337+
failedToLoadCart: 'Failed to load cart:',
338+
retry: 'Retry',
339+
yourCartIsEmpty: 'Your cart is empty',
340+
addItemsToGetStarted: 'Add items to get started',
341+
errorLoadingProducts: 'Error loading products:',
342+
errorLoadingProduct: 'Error loading product:',
343+
productNotFound: 'Product not found',
344+
loadingVariantDetails: 'Loading variant details...',
345+
combinationNotAvailable:
346+
'This combination is not available. Please select different options.',
347+
variantsMatch:
348+
'variants match your selection. Select more attributes to narrow down.',
349+
quantity: 'Quantity',
350+
addingToCart: 'Adding to Cart...',
351+
outOfStock: 'Out of Stock',
352+
viewDetails: 'View Details',
353+
productType: 'Product Type:',
354+
productId: 'Product ID:',
355+
selectedSku: 'Selected SKU:',
356+
stockStatus: 'Stock Status:',
357+
lowStock: 'Low Stock',
358+
inStock: 'In Stock',
359+
remove: 'Remove',
360+
removing: 'Removing...',
361+
checkout: 'Checkout',
362+
},
328363
};

packages/localizations/src/enUs.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,4 +325,39 @@ export const enUs = {
325325
DEPENDENCY_ERROR:
326326
"We're unable to process your order right now. Please wait a moment and try again",
327327
},
328+
storefront: {
329+
product: 'Product',
330+
sale: 'SALE',
331+
noImage: 'No image',
332+
noImageAvailable: 'No image available',
333+
selectOptions: 'Select Options',
334+
adding: 'Adding...',
335+
addToCart: 'Add to Cart',
336+
shoppingCart: 'Shopping Cart',
337+
failedToLoadCart: 'Failed to load cart:',
338+
retry: 'Retry',
339+
yourCartIsEmpty: 'Your cart is empty',
340+
addItemsToGetStarted: 'Add items to get started',
341+
errorLoadingProducts: 'Error loading products:',
342+
errorLoadingProduct: 'Error loading product:',
343+
productNotFound: 'Product not found',
344+
loadingVariantDetails: 'Loading variant details...',
345+
combinationNotAvailable:
346+
'This combination is not available. Please select different options.',
347+
variantsMatch:
348+
'variants match your selection. Select more attributes to narrow down.',
349+
quantity: 'Quantity',
350+
addingToCart: 'Adding to Cart...',
351+
outOfStock: 'Out of Stock',
352+
viewDetails: 'View Details',
353+
productType: 'Product Type:',
354+
productId: 'Product ID:',
355+
selectedSku: 'Selected SKU:',
356+
stockStatus: 'Stock Status:',
357+
lowStock: 'Low Stock',
358+
inStock: 'In Stock',
359+
remove: 'Remove',
360+
removing: 'Removing...',
361+
checkout: 'Checkout',
362+
},
328363
};

packages/localizations/src/esAr.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,4 +331,39 @@ export const esAr = {
331331
DEPENDENCY_ERROR:
332332
'No podemos procesar su pedido en este momento. Espere un momento e inténtelo de nuevo',
333333
},
334+
storefront: {
335+
product: 'Producto',
336+
sale: 'OFERTA',
337+
noImage: 'Sin imagen',
338+
noImageAvailable: 'Sin imagen disponible',
339+
selectOptions: 'Seleccionar opciones',
340+
adding: 'Agregando...',
341+
addToCart: 'Agregar al carrito',
342+
shoppingCart: 'Carrito de compras',
343+
failedToLoadCart: 'Error al cargar el carrito:',
344+
retry: 'Reintentar',
345+
yourCartIsEmpty: 'Tu carrito está vacío',
346+
addItemsToGetStarted: 'Agregá artículos para comenzar',
347+
errorLoadingProducts: 'Error al cargar productos:',
348+
errorLoadingProduct: 'Error al cargar producto:',
349+
productNotFound: 'Producto no encontrado',
350+
loadingVariantDetails: 'Cargando detalles de la variante...',
351+
combinationNotAvailable:
352+
'Esta combinación no está disponible. Por favor seleccioná opciones diferentes.',
353+
variantsMatch:
354+
'variantes coinciden con tu selección. Seleccioná más atributos para reducir las opciones.',
355+
quantity: 'Cantidad',
356+
addingToCart: 'Agregando al carrito...',
357+
outOfStock: 'Agotado',
358+
viewDetails: 'Ver detalles',
359+
productType: 'Tipo de producto:',
360+
productId: 'ID del producto:',
361+
selectedSku: 'SKU seleccionado:',
362+
stockStatus: 'Estado del stock:',
363+
lowStock: 'Stock bajo',
364+
inStock: 'En stock',
365+
remove: 'Eliminar',
366+
removing: 'Eliminando...',
367+
checkout: 'Pagar',
368+
},
334369
};

0 commit comments

Comments
 (0)