3596350425
Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com>
256 lines
5.8 KiB
TypeScript
256 lines
5.8 KiB
TypeScript
import { toast } from "sonner";
|
|
|
|
// Shopify API Configuration
|
|
const SHOPIFY_API_VERSION = '2025-07';
|
|
const SHOPIFY_STORE_PERMANENT_DOMAIN = 'sweet-office-treats-2ar62.myshopify.com';
|
|
const SHOPIFY_STOREFRONT_URL = `https://${SHOPIFY_STORE_PERMANENT_DOMAIN}/api/${SHOPIFY_API_VERSION}/graphql.json`;
|
|
const SHOPIFY_STOREFRONT_TOKEN = 'd9c2c43fc30d96d1742cc76da58c4ddb';
|
|
|
|
export interface ShopifyProduct {
|
|
node: {
|
|
id: string;
|
|
title: string;
|
|
description: string;
|
|
handle: string;
|
|
priceRange: {
|
|
minVariantPrice: {
|
|
amount: string;
|
|
currencyCode: string;
|
|
};
|
|
};
|
|
images: {
|
|
edges: Array<{
|
|
node: {
|
|
url: string;
|
|
altText: string | null;
|
|
};
|
|
}>;
|
|
};
|
|
variants: {
|
|
edges: Array<{
|
|
node: {
|
|
id: string;
|
|
title: string;
|
|
price: {
|
|
amount: string;
|
|
currencyCode: string;
|
|
};
|
|
availableForSale: boolean;
|
|
selectedOptions: Array<{
|
|
name: string;
|
|
value: string;
|
|
}>;
|
|
};
|
|
}>;
|
|
};
|
|
sellingPlanGroups?: {
|
|
edges: Array<{
|
|
node: {
|
|
name: string;
|
|
sellingPlans: {
|
|
edges: Array<{
|
|
node: {
|
|
id: string;
|
|
name: string;
|
|
};
|
|
}>;
|
|
};
|
|
};
|
|
}>;
|
|
};
|
|
options: Array<{
|
|
name: string;
|
|
values: string[];
|
|
}>;
|
|
};
|
|
}
|
|
|
|
// Storefront API helper function
|
|
export async function storefrontApiRequest(query: string, variables: Record<string, unknown> = {}) {
|
|
const response = await fetch(SHOPIFY_STOREFRONT_URL, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Shopify-Storefront-Access-Token': SHOPIFY_STOREFRONT_TOKEN
|
|
},
|
|
body: JSON.stringify({
|
|
query,
|
|
variables,
|
|
}),
|
|
});
|
|
|
|
if (response.status === 402) {
|
|
toast.error("Shopify: Betalning krävs", {
|
|
description: "Shopify API kräver en aktiv betalplan. Besök admin.shopify.com för att uppgradera.",
|
|
});
|
|
return null;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.errors) {
|
|
throw new Error(`Error calling Shopify: ${data.errors.map((e: { message: string }) => e.message).join(', ')}`);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// Fetch products query
|
|
export const STOREFRONT_PRODUCTS_QUERY = `
|
|
query GetProducts($first: Int!, $query: String) {
|
|
products(first: $first, query: $query) {
|
|
edges {
|
|
node {
|
|
id
|
|
title
|
|
description
|
|
handle
|
|
priceRange {
|
|
minVariantPrice {
|
|
amount
|
|
currencyCode
|
|
}
|
|
}
|
|
images(first: 5) {
|
|
edges {
|
|
node {
|
|
url
|
|
altText
|
|
}
|
|
}
|
|
}
|
|
variants(first: 10) {
|
|
edges {
|
|
node {
|
|
id
|
|
title
|
|
price {
|
|
amount
|
|
currencyCode
|
|
}
|
|
availableForSale
|
|
selectedOptions {
|
|
name
|
|
value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sellingPlanGroups(first: 5) {
|
|
edges {
|
|
node {
|
|
name
|
|
sellingPlans(first: 10) {
|
|
edges {
|
|
node {
|
|
id
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
options {
|
|
name
|
|
values
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
// Cart mutations
|
|
export const CART_QUERY = `
|
|
query cart($id: ID!) {
|
|
cart(id: $id) {
|
|
id
|
|
totalQuantity
|
|
checkoutUrl
|
|
lines(first: 100) {
|
|
edges {
|
|
node {
|
|
id
|
|
quantity
|
|
merchandise {
|
|
... on ProductVariant {
|
|
id
|
|
title
|
|
price {
|
|
amount
|
|
currencyCode
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
export const CART_CREATE_MUTATION = `
|
|
mutation cartCreate($input: CartInput!) {
|
|
cartCreate(input: $input) {
|
|
cart {
|
|
id
|
|
checkoutUrl
|
|
lines(first: 100) { edges { node { id merchandise { ... on ProductVariant { id } } } } }
|
|
}
|
|
userErrors { field message }
|
|
}
|
|
}
|
|
`;
|
|
|
|
export const CART_LINES_ADD_MUTATION = `
|
|
mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
|
|
cartLinesAdd(cartId: $cartId, lines: $lines) {
|
|
cart {
|
|
id
|
|
lines(first: 100) { edges { node { id merchandise { ... on ProductVariant { id } } } } }
|
|
}
|
|
userErrors { field message }
|
|
}
|
|
}
|
|
`;
|
|
|
|
export const CART_LINES_UPDATE_MUTATION = `
|
|
mutation cartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
|
|
cartLinesUpdate(cartId: $cartId, lines: $lines) {
|
|
cart { id }
|
|
userErrors { field message }
|
|
}
|
|
}
|
|
`;
|
|
|
|
export const CART_LINES_REMOVE_MUTATION = `
|
|
mutation cartLinesRemove($cartId: ID!, $lineIds: [ID!]!) {
|
|
cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
|
|
cart { id }
|
|
userErrors { field message }
|
|
}
|
|
}
|
|
`;
|
|
|
|
export function formatCheckoutUrl(checkoutUrl: string): string {
|
|
try {
|
|
const url = new URL(checkoutUrl);
|
|
url.searchParams.set('channel', 'online_store');
|
|
return url.toString();
|
|
} catch {
|
|
return checkoutUrl;
|
|
}
|
|
}
|
|
|
|
export function isCartNotFoundError(userErrors: Array<{ field: string[] | null; message: string }>): boolean {
|
|
return userErrors.some(e =>
|
|
e.message.toLowerCase().includes('cart not found') ||
|
|
e.message.toLowerCase().includes('does not exist')
|
|
);
|
|
}
|