feat: add bundle feature with 2x/3x set options
Some checks failed
Build and Deploy / build (push) Has been cancelled
Some checks failed
Build and Deploy / build (push) Has been cancelled
- Created BundleSelector component for selecting bundle options - Updated ProductDetail to show bundle options - Added bundle translations for all 4 locales - Added GraphQL query for bundle products - Updated TypeScript types for attributes - Saleor backend: created bundle products for all base products
This commit is contained in:
@@ -35,6 +35,18 @@ export const PRODUCT_FRAGMENT = gql`
|
||||
key
|
||||
value
|
||||
}
|
||||
attributes {
|
||||
attribute {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
values {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
${PRODUCT_VARIANT_FRAGMENT}
|
||||
`;
|
||||
|
||||
@@ -7,7 +7,7 @@ export { PRODUCT_VARIANT_FRAGMENT, CHECKOUT_LINE_FRAGMENT } from "./fragments/Va
|
||||
export { CHECKOUT_FRAGMENT, ADDRESS_FRAGMENT } from "./fragments/Checkout";
|
||||
|
||||
// Queries
|
||||
export { GET_PRODUCTS, GET_PRODUCT_BY_SLUG, GET_PRODUCTS_BY_CATEGORY } from "./queries/Products";
|
||||
export { GET_PRODUCTS, GET_PRODUCT_BY_SLUG, GET_PRODUCTS_BY_CATEGORY, GET_BUNDLE_PRODUCTS } from "./queries/Products";
|
||||
export { GET_CHECKOUT, GET_CHECKOUT_BY_ID } from "./queries/Checkout";
|
||||
|
||||
// Mutations
|
||||
@@ -34,4 +34,5 @@ export {
|
||||
formatPrice,
|
||||
getLocalizedProduct,
|
||||
parseDescription,
|
||||
getBundleProducts,
|
||||
} from "./products";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { saleorClient } from "./client";
|
||||
import { GET_PRODUCTS, GET_PRODUCT_BY_SLUG } from "./queries/Products";
|
||||
import { GET_PRODUCTS, GET_PRODUCT_BY_SLUG, GET_BUNDLE_PRODUCTS } from "./queries/Products";
|
||||
import type { Product } from "@/types/saleor";
|
||||
|
||||
const CHANNEL = process.env.NEXT_PUBLIC_SALEOR_CHANNEL || "default-channel";
|
||||
@@ -155,3 +155,65 @@ export function getLocalizedProduct(
|
||||
seoDescription: translation?.seoDescription || product.seoDescription,
|
||||
};
|
||||
}
|
||||
|
||||
interface ProductsResponse {
|
||||
products?: {
|
||||
edges: Array<{ node: Product }>;
|
||||
};
|
||||
}
|
||||
|
||||
export async function getBundleProducts(
|
||||
locale: string = "SR",
|
||||
first: number = 50
|
||||
): Promise<Product[]> {
|
||||
try {
|
||||
const { data } = await saleorClient.query<ProductsResponse>({
|
||||
query: GET_BUNDLE_PRODUCTS,
|
||||
variables: {
|
||||
channel: CHANNEL,
|
||||
locale: locale.toUpperCase(),
|
||||
first,
|
||||
},
|
||||
});
|
||||
|
||||
return data?.products?.edges.map((edge) => edge.node) || [];
|
||||
} catch (error) {
|
||||
console.error("Error fetching bundle products from Saleor:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function getBundleProductsForProduct(
|
||||
allProducts: Product[],
|
||||
baseProductId: string
|
||||
): Product[] {
|
||||
return allProducts.filter((product) => {
|
||||
const bundleItemsAttr = product.attributes?.find(
|
||||
(attr) => attr.attribute.slug === "bundle-items"
|
||||
);
|
||||
if (!bundleItemsAttr) return false;
|
||||
return bundleItemsAttr.values.some((val) => {
|
||||
const referencedId = Buffer.from(val.slug.split(":")[1] || val.id).toString("base64");
|
||||
const expectedId = `UHJvZHVjdDo${baseProductId.split("UHJvZHVjdDo")[1]}`;
|
||||
return referencedId.includes(baseProductId.split("UHJvZHVjdDo")[1] || "") ||
|
||||
val.slug.includes(baseProductId.split("UHJvZHVjdDo")[1] || "");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function getProductBundleComponents(product: Product): number | null {
|
||||
const bundleAttr = product.attributes?.find(
|
||||
(attr) => attr.attribute.slug === "bundle-items"
|
||||
);
|
||||
if (!bundleAttr) return null;
|
||||
|
||||
const bundleAttrMatch = product.name.match(/(\d+)x/i);
|
||||
if (bundleAttrMatch) {
|
||||
return parseInt(bundleAttrMatch[1], 10);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function isBundleProduct(product: Product): boolean {
|
||||
return getProductBundleComponents(product) !== null;
|
||||
}
|
||||
|
||||
@@ -49,3 +49,16 @@ export const GET_PRODUCTS_BY_CATEGORY = gql`
|
||||
}
|
||||
${PRODUCT_LIST_ITEM_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const GET_BUNDLE_PRODUCTS = gql`
|
||||
query GetBundleProducts($channel: String!, $locale: LanguageCodeEnum!, $first: Int!) {
|
||||
products(channel: $channel, first: $first) {
|
||||
edges {
|
||||
node {
|
||||
...ProductFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${PRODUCT_FRAGMENT}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user