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"; // 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; const title = attrs.product_name; const descriptionHtml = attrs.part_description; const vendor = attrs.brand; const productType = attrs.category; const handle = slugify(attrs.part_number || title); const tags = [attrs.category, attrs.subcategory, attrs.brand] .filter(Boolean) .map((t) => t.trim()); const price = attrs.price?.toString() || "1000"; const compare_price = attrs.compare_price?.toString() || "2000"; const costPerItem = attrs.purchase_cost?.toString() || "100"; const sku = attrs.part_number || ''; const collectionId = "gid://shopify/Collection/447659409624"; // 🔥 Build a CreateMediaInput[] from every file in attrs.files const mediaInputs = (attrs.files || []).map((file) => ({ originalSource: file.url, mediaContentType: "IMAGE", alt: `${title} – ${file.media_content}`, })); // 1️⃣ Create product + images + join collection const createRes = await admin.graphql( `#graphql mutation CreateFullProduct( $product: ProductCreateInput! $media: [CreateMediaInput!] ) { productCreate(product: $product, media: $media) { product { id variants(first: 1) { nodes { id inventoryItem { id } } } } userErrors { field message } } }`, { variables: { product: { title, descriptionHtml, vendor, productType, handle, tags, collectionsToJoin: [collectionId], status: "ACTIVE", published: true, // Ensures product is published to the Online Store }, media: mediaInputs, // ← now includes all your images }, } ); const createJson = await createRes.json(); const createErrs = createJson.data.productCreate.userErrors; if (createErrs.length) { const handleTaken = createErrs.some((e) => /already in use/i.test(e.message) ); if (handleTaken) { results.push({ skippedHandle: handle, reason: "handle already in use" }); continue; } throw new Error( `Create errors: ${createErrs.map((e) => e.message).join(", ")}` ); } const newProduct = createJson.data.productCreate.product; const { id: variantId, inventoryItem: { id: inventoryItemId }, } = newProduct.variants.nodes[0]; // 2️⃣ Bulk-update variant price + compareAtPrice const variantInputs = [ { id: variantId, price: parseFloat(price), ...(compare_price && { compareAtPrice: parseFloat(compare_price) }), }, ]; const priceRes = await admin.graphql( `#graphql mutation UpdatePrice( $productId: ID! $variants: [ProductVariantsBulkInput!]! ) { productVariantsBulkUpdate( productId: $productId variants: $variants ) { productVariants { id price compareAtPrice } userErrors { field message } } }`, { variables: { productId: newProduct.id, variants: variantInputs, }, } ); const priceJson = await priceRes.json(); const priceErrs = priceJson.data.productVariantsBulkUpdate.userErrors; if (priceErrs.length) { throw new Error( `Price update errors: ${priceErrs.map((e) => e.message).join(", ")}` ); } const updatedVariant = priceJson.data.productVariantsBulkUpdate.productVariants[0]; // 3️⃣ Update inventory item to set SKU & cost const skuRes = await admin.graphql( `#graphql mutation SetSKUAndCost( $id: ID! $input: InventoryItemInput! ) { inventoryItemUpdate(id: $id, input: $input) { inventoryItem { id sku } userErrors { field message } } }`, { variables: { id: inventoryItemId, input: { sku, cost: parseFloat(costPerItem), }, }, } ); const skuJson = await skuRes.json(); const skuErrs = skuJson.data.inventoryItemUpdate.userErrors; if (skuErrs.length) { throw new Error( `SKU update errors: ${skuErrs.map((e) => e.message).join(", ")}` ); } const updatedItem = skuJson.data.inventoryItemUpdate.inventoryItem; const publicationsRes = await admin.graphql( `#graphql query { publications(first: 5) { nodes { id name } } }` ); const publications = await publicationsRes.json(); const onlineStorePublication = publications?.data?.publications?.nodes.find( (pub) => pub.name === "Online Store" ); const publicationId = onlineStorePublication?.id; console.log("1234567", publicationId) if (publicationId && newProduct.id) { const publishRes = await admin.graphql( `#graphql mutation PublishProduct($productId: ID!, $publicationId: ID!) { publishablePublish( id: $productId, input: [{ publicationId: $publicationId }] ) { publishable { publishedOnPublication(publicationId: $publicationId) } userErrors { field message } } }`, { variables: { productId: newProduct.id, // Product ID from the productCreate mutation publicationId: publicationId, // Online Store Publication ID }, } ); const publishJson = await publishRes.json(); const publishErrs = publishJson.data.publishablePublish.userErrors; if (publishErrs.length) { throw new Error( `Publish errors: ${publishErrs.map((e) => e.message).join(", ")}` ); } } results.push({ productId: newProduct.id, variant: { id: updatedVariant.id, price: updatedVariant.price, compareAtPrice: updatedVariant.compareAtPrice, sku: updatedItem.sku, }, }); } 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}

))}
)}
)}
))}
); }