index-shop/assets/js/shopify-cart.js
isUnknown 28501fec7c Implement custom Shopify cart with drawer UI
Replace Shopify Buy Button iframe with custom implementation using Storefront API 2026-01. Create interactive cart drawer with full product management capabilities (add, remove, update quantities) and seamless checkout flow.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-14 11:26:14 +01:00

232 lines
5 KiB
JavaScript

/**
* Shopify Storefront API Client
* Custom implementation using Cart API (2026-01)
*/
class ShopifyCart {
constructor(config) {
this.domain = config.domain;
this.storefrontAccessToken = config.storefrontAccessToken;
this.apiVersion = '2026-01';
this.endpoint = `https://${this.domain}/api/${this.apiVersion}/graphql.json`;
this.cartId = null;
this.cartItems = [];
// Load existing cart from localStorage
this.loadCart();
}
/**
* Make GraphQL request to Shopify Storefront API
*/
async query(query, variables = {}) {
const response = await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': this.storefrontAccessToken,
},
body: JSON.stringify({ query, variables }),
});
const result = await response.json();
if (result.errors) {
console.error('Shopify API Error:', result.errors);
throw new Error(result.errors[0].message);
}
return result.data;
}
/**
* Get product information by ID
*/
async getProduct(productId) {
const query = `
query getProduct($id: ID!) {
product(id: $id) {
id
title
description
availableForSale
variants(first: 10) {
edges {
node {
id
title
price {
amount
currencyCode
}
availableForSale
}
}
}
}
}
`;
const data = await this.query(query, {
id: `gid://shopify/Product/${productId}`
});
return data.product;
}
/**
* Create a new cart
*/
async createCart(lines = []) {
const query = `
mutation cartCreate($input: CartInput!) {
cartCreate(input: $input) {
cart {
id
checkoutUrl
lines(first: 10) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id
title
price {
amount
currencyCode
}
product {
title
}
}
}
}
}
}
}
userErrors {
field
message
}
}
}
`;
const data = await this.query(query, {
input: { lines }
});
if (data.cartCreate.userErrors.length > 0) {
throw new Error(data.cartCreate.userErrors[0].message);
}
this.cartId = data.cartCreate.cart.id;
this.saveCart();
return data.cartCreate.cart;
}
/**
* Add item to cart
*/
async addToCart(variantId, quantity = 1) {
const lines = [{
merchandiseId: `gid://shopify/ProductVariant/${variantId}`,
quantity: quantity
}];
let cart;
if (this.cartId) {
// Add to existing cart
const query = `
mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
id
checkoutUrl
lines(first: 10) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id
title
price {
amount
currencyCode
}
product {
title
}
}
}
}
}
}
}
userErrors {
field
message
}
}
}
`;
const data = await this.query(query, {
cartId: this.cartId,
lines
});
if (data.cartLinesAdd.userErrors.length > 0) {
throw new Error(data.cartLinesAdd.userErrors[0].message);
}
cart = data.cartLinesAdd.cart;
} else {
// Create new cart
cart = await this.createCart(lines);
}
this.cartItems = cart.lines.edges;
return cart;
}
/**
* Get checkout URL to redirect user
*/
getCheckoutUrl(cart) {
return cart?.checkoutUrl || null;
}
/**
* Save cart ID to localStorage
*/
saveCart() {
if (this.cartId) {
localStorage.setItem('shopify_cart_id', this.cartId);
}
}
/**
* Load cart ID from localStorage
*/
loadCart() {
this.cartId = localStorage.getItem('shopify_cart_id');
}
/**
* Clear cart
*/
clearCart() {
this.cartId = null;
this.cartItems = [];
localStorage.removeItem('shopify_cart_id');
}
}
// Export for use in other scripts
window.ShopifyCart = ShopifyCart;