feat(ecommerce): migrate Ari Shopping to Next.js 15 headless storefront v4
Build and Push Frontend / build (push) Failing after 5m39s
Build and Push Frontend / build (push) Failing after 5m39s
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import type { CatalogProvider, Product } from "@/lib/headless/types";
|
||||
|
||||
async function medusaFetch<T>(path: string): Promise<T> {
|
||||
const baseUrl = process.env.MEDUSA_BACKEND_URL;
|
||||
if (!baseUrl) throw new Error("Medusa no está configurado.");
|
||||
const response = await fetch(`${baseUrl.replace(/\/$/, "")}${path}`, {
|
||||
headers: process.env.MEDUSA_PUBLISHABLE_KEY
|
||||
? { "x-publishable-api-key": process.env.MEDUSA_PUBLISHABLE_KEY }
|
||||
: undefined,
|
||||
next: { revalidate: 300 },
|
||||
});
|
||||
if (!response.ok) throw new Error(`Medusa respondió HTTP ${response.status}`);
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
function notImplemented(): never {
|
||||
throw new Error("Completa el mapeo Medusa → Product en lib/headless/providers/medusa.ts.");
|
||||
}
|
||||
|
||||
export const medusaCatalogProvider: CatalogProvider = {
|
||||
async getProducts() {
|
||||
await medusaFetch<unknown>("/store/products?limit=24");
|
||||
return notImplemented();
|
||||
},
|
||||
async getProductBySlug(): Promise<Product | null> {
|
||||
return notImplemented();
|
||||
},
|
||||
async searchProducts(): Promise<Product[]> {
|
||||
return notImplemented();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import catalog from "@/data/products.json";
|
||||
import type { CatalogProvider, Product, ProductFilters } from "@/lib/headless/types";
|
||||
|
||||
const products = catalog satisfies Product[];
|
||||
|
||||
function normalize(value: string): string {
|
||||
return value.toLocaleLowerCase("es").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
}
|
||||
|
||||
function matches(product: Product, filters: ProductFilters): boolean {
|
||||
if (filters.category && filters.category !== "Todos" && product.category !== filters.category) return false;
|
||||
if (filters.collection && product.collection !== filters.collection) return false;
|
||||
if (filters.brand && product.brand !== filters.brand) return false;
|
||||
if (filters.ageRange && product.ageRange !== filters.ageRange) return false;
|
||||
if (filters.minPrice !== undefined && product.price !== null && product.price < filters.minPrice) return false;
|
||||
if (filters.maxPrice !== undefined && product.price !== null && product.price > filters.maxPrice) return false;
|
||||
if (filters.query) {
|
||||
const query = normalize(filters.query);
|
||||
const haystack = normalize([
|
||||
product.name,
|
||||
product.description,
|
||||
product.category,
|
||||
product.collection,
|
||||
product.brand,
|
||||
...product.tags,
|
||||
].join(" "));
|
||||
if (!haystack.includes(query)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const mockCatalogProvider: CatalogProvider = {
|
||||
async getProducts(filters = {}) {
|
||||
return products.filter((product) => matches(product, filters));
|
||||
},
|
||||
async getProductBySlug(slug) {
|
||||
return products.find((product) => product.slug === slug) ?? null;
|
||||
},
|
||||
async searchProducts(query, limit = 6) {
|
||||
return products.filter((product) => matches(product, { query })).slice(0, limit);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { CatalogProvider, Product } from "@/lib/headless/types";
|
||||
|
||||
interface ShopifyGraphQLResponse<T> {
|
||||
data?: T;
|
||||
errors?: Array<{ message: string }>;
|
||||
}
|
||||
|
||||
async function shopifyFetch<T>(query: string, variables: Record<string, unknown> = {}): Promise<T> {
|
||||
const url = process.env.SHOPIFY_STOREFRONT_URL;
|
||||
const token = process.env.SHOPIFY_STOREFRONT_TOKEN;
|
||||
if (!url || !token) throw new Error("Shopify Storefront API no está configurada.");
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Shopify-Storefront-Access-Token": token,
|
||||
},
|
||||
body: JSON.stringify({ query, variables }),
|
||||
next: { revalidate: 300 },
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Shopify respondió HTTP ${response.status}`);
|
||||
const payload = (await response.json()) as ShopifyGraphQLResponse<T>;
|
||||
if (payload.errors?.length) throw new Error(payload.errors.map((error) => error.message).join("; "));
|
||||
if (!payload.data) throw new Error("Shopify no devolvió datos.");
|
||||
return payload.data;
|
||||
}
|
||||
|
||||
function notImplemented(): never {
|
||||
throw new Error("Completa el mapeo Shopify → Product en lib/headless/providers/shopify.ts.");
|
||||
}
|
||||
|
||||
export const shopifyCatalogProvider: CatalogProvider = {
|
||||
async getProducts() {
|
||||
await shopifyFetch<{ products: unknown }>(`query Products { products(first: 24) { nodes { id title handle } } }`);
|
||||
return notImplemented();
|
||||
},
|
||||
async getProductBySlug(): Promise<Product | null> {
|
||||
return notImplemented();
|
||||
},
|
||||
async searchProducts(): Promise<Product[]> {
|
||||
return notImplemented();
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user