/* import React, { useState } from "react"; import { json } from "@remix-run/node"; import { useLoaderData, Form, useActionData } from "@remix-run/react"; import { Page, Layout, Card, Thumbnail, TextContainer, Spinner, Button, TextField, Banner, InlineError, } from "@shopify/polaris"; import { authenticate } from "../shopify.server"; import { TitleBar } from "@shopify/app-bridge-react"; // Load selected brands and access token from Shopify metafield export const loader = async ({ request }) => { const { admin } = await authenticate.admin(request); const { getTurn14AccessTokenFromMetafield } = await import( "../utils/turn14Token.server" ); const accessToken = await getTurn14AccessTokenFromMetafield(request); const res = await admin.graphql(` { shop { metafield(namespace: "turn14", key: "selected_brands") { value } } } `); const data = await res.json(); const rawValue = data?.data?.shop?.metafield?.value; let brands = []; try { brands = JSON.parse(rawValue); } catch (err) { console.error("❌ Failed to parse metafield value:", err); } return json({ brands, accessToken }); }; // Handle adding products for a specific brand export const action = async ({ request }) => { const { admin } = await authenticate.admin(request); const formData = await request.formData(); const brandId = formData.get("brandId"); const rawCount = formData.get("productCount"); const productCount = parseInt(rawCount, 10) || 10; const { getTurn14AccessTokenFromMetafield } = await import( "../utils/turn14Token.server" ); const accessToken = await getTurn14AccessTokenFromMetafield(request); // Fetch items from Turn14 API const itemsRes = await fetch( `https://turn14.data4autos.com/v1/items/brandallitems/${brandId}`, { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, } ); const itemsData = await itemsRes.json(); function slugify(str) { return str .toString() .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); } const items1 = Array.isArray(itemsData) ? itemsData.slice(0, productCount) : []; const results = []; for (const item of items1) { const attrs = item.attributes; // 0️⃣ Build and normalize collection titles const category = attrs.category; const subcategory = attrs.subcategory || ""; const brand = attrs.brand; const subcats = subcategory .split(/[,\/]/) .map((s) => s.trim()) .filter(Boolean); const collectionTitles = Array.from( new Set([category, ...subcats, brand].filter(Boolean)) ); // 1️⃣ Find or create collections, collect their IDs const collectionIds = []; for (const title of collectionTitles) { // lookup const lookupRes = await admin.graphql(` { collections(first: 1, query: "title:\\"${title}\\" AND collection_type:manual") { nodes { id } } } `); const lookupJson = await lookupRes.json(); const existing = lookupJson.data.collections.nodes; if (existing.length) { collectionIds.push(existing[0].id); } else { // create const createColRes = await admin.graphql(` mutation($input: CollectionInput!) { collectionCreate(input: $input) { collection { id } userErrors { field message } } } `, { variables: { input: { title } } }); const createColJson = await createColRes.json(); const errs = createColJson.data.collectionCreate.userErrors; if (errs.length) { throw new Error(`Could not create collection "${title}": ${errs.map(e => e.message).join(", ")}`); } collectionIds.push(createColJson.data.collectionCreate.collection.id); } } // 2️⃣ Build tags const tags = [ attrs.category, ...subcats, attrs.brand, attrs.part_number, attrs.mfr_part_number, attrs.price_group, attrs.units_per_sku && `${attrs.units_per_sku} per SKU`, attrs.barcode ].filter(Boolean).map((t) => t.trim()); // 3️⃣ Prepare media inputs const mediaInputs = (attrs.files || []) .filter((f) => f.type === "Image" && f.url) .map((file) => ({ originalSource: file.url, mediaContentType: "IMAGE", alt: `${attrs.product_name} — ${file.media_content}`, })); // 2️⃣ Pick the longest “Market Description” or fallback to part_description const marketDescs = (attrs.descriptions || []) .filter((d) => d.type === "Market Description") .map((d) => d.description); const descriptionHtml = marketDescs.length ? marketDescs.reduce((a, b) => (b.length > a.length ? b : a)) : attrs.part_description; // 4️⃣ Create product + attach to collections + add media const createProdRes = await admin.graphql(` mutation($prod: ProductCreateInput!, $media: [CreateMediaInput!]) { productCreate(product: $prod, media: $media) { product { id variants(first: 1) { nodes { id inventoryItem { id } } } } userErrors { field message } } } `, { variables: { prod: { title: attrs.product_name, descriptionHtml: descriptionHtml, vendor: attrs.brand, productType: attrs.category, handle: slugify(attrs.part_number || attrs.product_name), tags, collectionsToJoin: collectionIds, status: "ACTIVE", }, media: mediaInputs, }, }); const createProdJson = await createProdRes.json(); const prodErrs = createProdJson.data.productCreate.userErrors; if (prodErrs.length) { const taken = prodErrs.some((e) => /already in use/i.test(e.message)); if (taken) { results.push({ skippedHandle: attrs.part_number, reason: "handle in use" }); continue; } throw new Error(`ProductCreate errors: ${prodErrs.map(e => e.message).join(", ")}`); } const product = createProdJson.data.productCreate.product; const variantNode = product.variants.nodes[0]; const variantId = variantNode.id; const inventoryItemId = variantNode.inventoryItem.id; // 5️⃣ Bulk-update variant (price, compare-at, barcode) const price = parseFloat(attrs.price) || 1000; const comparePrice = parseFloat(attrs.compare_price) || null; const barcode = attrs.barcode || ""; const bulkRes = await admin.graphql(` mutation($productId: ID!, $variants: [ProductVariantsBulkInput!]!) { productVariantsBulkUpdate(productId: $productId, variants: $variants) { productVariants { id price compareAtPrice barcode } userErrors { field message } } } `, { variables: { productId: product.id, variants: [{ id: variantId, price, ...(comparePrice !== null && { compareAtPrice: comparePrice }), ...(barcode && { barcode }), }], }, }); const bulkJson = await bulkRes.json(); const bulkErrs = bulkJson.data.productVariantsBulkUpdate.userErrors; if (bulkErrs.length) { throw new Error(`Bulk update errors: ${bulkErrs.map(e => e.message).join(", ")}`); } const updatedVariant = bulkJson.data.productVariantsBulkUpdate.productVariants[0]; // 6️⃣ Update inventory item (SKU, cost & weight) const costPerItem = parseFloat(attrs.purchase_cost) || 0; const weightValue = parseFloat(attrs.dimensions?.[0]?.weight) || 0; const invRes = await admin.graphql(` mutation($id: ID!, $input: InventoryItemInput!) { inventoryItemUpdate(id: $id, input: $input) { inventoryItem { id sku measurement { weight { value unit } } } userErrors { field message } } } `, { variables: { id: inventoryItemId, input: { sku: attrs.part_number, cost: costPerItem, measurement: { weight: { value: weightValue, unit: "POUNDS" } }, }, }, }); const invJson = await invRes.json(); const invErrs = invJson.data.inventoryItemUpdate.userErrors; if (invErrs.length) { throw new Error(`Inventory update errors: ${invErrs.map(e => e.message).join(", ")}`); } const inventoryItem = invJson.data.inventoryItemUpdate.inventoryItem; // 7️⃣ Collect results results.push({ productId: product.id, variant: { id: updatedVariant.id, price: updatedVariant.price, compareAtPrice: updatedVariant.compareAtPrice, sku: inventoryItem.sku, barcode: updatedVariant.barcode, weight: inventoryItem.measurement.weight.value, weightUnit: inventoryItem.measurement.weight.unit, }, collections: collectionTitles, tags, }); } return json({ success: true, results }); }; // Main React component for managing brand products export default function ManageBrandProducts() { const actionData = useActionData(); const { brands, accessToken } = useLoaderData(); const [expandedBrand, setExpandedBrand] = useState(null); const [itemsMap, setItemsMap] = useState({}); const [loadingMap, setLoadingMap] = useState({}); const [productCount, setProductCount] = useState("10"); const toggleBrandItems = async (brandId) => { const isExpanded = expandedBrand === brandId; if (isExpanded) { setExpandedBrand(null); } else { setExpandedBrand(brandId); if (!itemsMap[brandId]) { setLoadingMap((prev) => ({ ...prev, [brandId]: true })); try { const res = await fetch(`https://turn14.data4autos.com/v1/items/brand/${brandId}?page=1`, { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, }); const data = await res.json(); setItemsMap((prev) => ({ ...prev, [brandId]: data })); } catch (err) { console.error("Error fetching items:", err); } setLoadingMap((prev) => ({ ...prev, [brandId]: false })); } } }; return ( {brands.length === 0 && (

No brands selected yet.

)} {brands.map((brand) => (

ID: {brand.id}

{expandedBrand === brand.id && ( {actionData?.success && (

{actionData.results.map((r) => ( Product {r.productId} – Variant {r.variant.id} @ ${r.variant.price} (SKU: {r.variant.sku})
))}

)}
{loadingMap[brand.id] ? ( ) : (
{(itemsMap[brand.id] || []).map((item) => (

Part Number: {item.attributes.part_number}

Category: {item.attributes.category} > {item.attributes.subcategory}

))}
)}
)}
))}
); } */ import React, { useState } from "react"; import { json } from "@remix-run/node"; import { useLoaderData, Form, useActionData } from "@remix-run/react"; import { Page, Layout, IndexTable, Card, Thumbnail, TextContainer, Spinner, Button, TextField, Banner, } from "@shopify/polaris"; import { authenticate } from "../shopify.server"; import { TitleBar } from "@shopify/app-bridge-react"; export const loader = async ({ request }) => { const { admin } = await authenticate.admin(request); const { getTurn14AccessTokenFromMetafield } = await import("../utils/turn14Token.server"); const accessToken = await getTurn14AccessTokenFromMetafield(request); const res = await admin.graphql(`{ shop { metafield(namespace: "turn14", key: "selected_brands") { value } } }`); const data = await res.json(); const rawValue = data?.data?.shop?.metafield?.value; let brands = []; try { brands = JSON.parse(rawValue); } catch (err) { console.error("❌ Failed to parse metafield value:", err); } return json({ brands, accessToken }); }; export default function ManageBrandProducts() { const actionData = useActionData(); const { brands, accessToken } = useLoaderData(); const [expandedBrand, setExpandedBrand] = useState(null); const [itemsMap, setItemsMap] = useState({}); const [loadingMap, setLoadingMap] = useState({}); const [productCount, setProductCount] = useState("10"); const toggleBrandItems = async (brandId) => { const isExpanded = expandedBrand === brandId; if (isExpanded) { setExpandedBrand(null); } else { setExpandedBrand(brandId); if (!itemsMap[brandId]) { setLoadingMap((prev) => ({ ...prev, [brandId]: true })); try { const res = await fetch(`https://turn14.data4autos.com/v1/items/brand/${brandId}?page=1`, { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, }); const data = await res.json(); setItemsMap((prev) => ({ ...prev, [brandId]: data })); } catch (err) { console.error("Error fetching items:", err); } setLoadingMap((prev) => ({ ...prev, [brandId]: false })); } } }; return ( {brands.length === 0 ? (

No brands selected yet.

) : ( {brands.map((brand, index) => ( {brand.id} ))} )} {brands.map( (brand) => expandedBrand === brand.id && ( {actionData?.success && (

{actionData.results.map((r) => ( Product {r.productId} – Variant {r.variant.id} @ ${r.variant.price} (SKU: {r.variant.sku})
))}

)}
{loadingMap[brand.id] ? ( ) : (
{(itemsMap[brand.id] || []).map((item) => (

Part Number: {item.attributes.part_number}

Category: {item.attributes.category} > {item.attributes.subcategory}

))}
)}
) )}
); }