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>
This commit is contained in:
parent
c08662caf8
commit
28501fec7c
13 changed files with 1158 additions and 81 deletions
232
assets/js/shopify-cart.js
Normal file
232
assets/js/shopify-cart.js
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/**
|
||||
* 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue