feat(saleor): Phase 1 - GraphQL Client Setup

- Add Apollo Client for Saleor GraphQL API
- Create GraphQL fragments (Product, Variant, Checkout)
- Create GraphQL queries (Products, Checkout)
- Create GraphQL mutations (Checkout operations)
- Add TypeScript types for Saleor entities
- Add product helper functions
- Install @apollo/client and graphql dependencies

Part of WordPress/WooCommerce → Saleor migration
This commit is contained in:
Unchained
2026-03-21 12:36:21 +02:00
parent db1914d69b
commit 7b94537670
27 changed files with 7879 additions and 3 deletions

105
src/lib/saleor/products.ts Normal file
View File

@@ -0,0 +1,105 @@
import { saleorClient } from "./client";
import { GET_PRODUCTS, GET_PRODUCT_BY_SLUG } from "./queries/Products";
import type { Product, ProductList } from "@/types/saleor";
const CHANNEL = process.env.NEXT_PUBLIC_SALEOR_CHANNEL || "default-channel";
export async function getProducts(
locale: string = "SR",
first: number = 100
): Promise<Product[]> {
try {
const { data } = await saleorClient.query({
query: GET_PRODUCTS,
variables: {
channel: CHANNEL,
locale: locale.toUpperCase(),
first,
},
});
return data?.products?.edges.map((edge: { node: Product }) => edge.node) || [];
} catch (error) {
console.error("Error fetching products from Saleor:", error);
return [];
}
}
export async function getProductBySlug(
slug: string,
locale: string = "SR"
): Promise<Product | null> {
try {
const { data } = await saleorClient.query({
query: GET_PRODUCT_BY_SLUG,
variables: {
slug,
channel: CHANNEL,
locale: locale.toUpperCase(),
},
});
return data?.product || null;
} catch (error) {
console.error(`Error fetching product ${slug} from Saleor:`, error);
return null;
}
}
export function getProductPrice(product: Product): string {
const variant = product.variants?.[0];
if (!variant?.pricing?.price?.gross?.amount) {
return "";
}
return formatPrice(
variant.pricing.price.gross.amount,
variant.pricing.price.gross.currency
);
}
export function getProductImage(product: Product): string {
if (product.media && product.media.length > 0) {
return product.media[0].url;
}
if (product.variants?.[0]?.media && product.variants[0].media.length > 0) {
return product.variants[0].media[0].url;
}
return "/placeholder-product.jpg";
}
export function isProductAvailable(product: Product): boolean {
const variant = product.variants?.[0];
if (!variant) return false;
return (variant.quantityAvailable || 0) > 0;
}
export function formatPrice(amount: number, currency: string = "RSD"): string {
return new Intl.NumberFormat("sr-RS", {
style: "currency",
currency: currency,
minimumFractionDigits: 0,
}).format(amount);
}
// Get localized product data
export function getLocalizedProduct(
product: Product,
locale: string = "SR"
): {
name: string;
slug: string;
description: string;
seoTitle?: string;
seoDescription?: string;
} {
const isEnglish = locale.toLowerCase() === "en";
const translation = isEnglish ? product.translation : null;
return {
name: translation?.name || product.name,
slug: translation?.slug || product.slug,
description: translation?.description || product.description,
seoTitle: translation?.seoTitle || product.seoTitle,
seoDescription: translation?.seoDescription || product.seoDescription,
};
}