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:
105
src/lib/saleor/products.ts
Normal file
105
src/lib/saleor/products.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user