// app/routes/add-full-product.jsx import { json } from "@remix-run/node"; import { useLoaderData, useActionData, Form } from "@remix-run/react"; import { Page, Layout, Card, TextField, Select, Button, Banner, InlineError, Text, } from "@shopify/polaris"; import { useState, useEffect } from "react"; import { authenticate } from "../shopify.server"; export const loader = async ({ request }) => { const { admin } = await authenticate.admin(request); const resp = await admin.graphql(` { collections(first: 10, query: "collection_type:manual") { nodes { id title } } } `); const { data } = await resp.json(); return json({ collections: data.collections.nodes }); }; export const action = async ({ request }) => { const form = await request.formData(); const { title, descriptionHtml, vendor, productType, handle, tags, price, sku, costPerItem, // new collectionId, imageUrl, } = Object.fromEntries(form); const { admin } = await authenticate.admin(request); // 1️⃣ Create product + attach image + 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: tags.split(",").map((t) => t.trim()), collectionsToJoin: [collectionId], status: "ACTIVE", }, media: [ { originalSource: imageUrl, mediaContentType: "IMAGE", alt: `${title} image`, }, ], }, } ); const createJson = await createRes.json(); if (createJson.data.productCreate.userErrors.length) { return json( { errors: createJson.data.productCreate.userErrors.map((e) => e.message) }, { status: 400 } ); } const newProduct = createJson.data.productCreate.product; const variantNode = newProduct.variants.nodes[0]; const variantId = variantNode.id; const inventoryItemId = variantNode.inventoryItem.id; // 2️⃣ Set price via bulk update const priceRes = await admin.graphql( `#graphql mutation UpdatePrice($productId: ID!, $variants: [ProductVariantsBulkInput!]!) { productVariantsBulkUpdate(productId: $productId, variants: $variants) { productVariants { id price } userErrors { field message } } }`, { variables: { productId: newProduct.id, variants: [{ id: variantId, price: parseFloat(price) }], }, } ); const priceJson = await priceRes.json(); if (priceJson.data.productVariantsBulkUpdate.userErrors.length) { return json( { errors: priceJson.data.productVariantsBulkUpdate.userErrors.map((e) => e.message) }, { status: 400 } ); } const updatedVariant = priceJson.data.productVariantsBulkUpdate.productVariants[0]; // 3️⃣ Update inventory item: set SKU + cost per item const skuCostRes = 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), // unit cost in shop’s default currency :contentReference[oaicite:1]{index=1} }, }, } ); const skuCostJson = await skuCostRes.json(); if (skuCostJson.data.inventoryItemUpdate.userErrors.length) { return json( { errors: skuCostJson.data.inventoryItemUpdate.userErrors.map((e) => e.message) }, { status: 400 } ); } const finalSKU = skuCostJson.data.inventoryItemUpdate.inventoryItem.sku; return json({ success: true, product: newProduct, variant: { id: updatedVariant.id, price: updatedVariant.price, sku: finalSKU, }, }); }; export default function AddFullProductPage() { const { collections } = useLoaderData(); const actionData = useActionData(); // Controlled inputs const [title, setTitle] = useState("Dummy Test Product"); const [descriptionHtml, setDescriptionHtml] = useState( "
This is a dummy product for testing.
" ); const [vendor, setVendor] = useState("Test Vendor"); const [productType, setProductType] = useState("Test Type"); const [handle, setHandle] = useState("dummy-test-product"); const [tags, setTags] = useState("test, dummy, sample"); const [price, setPrice] = useState("9.99"); const [costPerItem, setCostPerItem] = useState("5.00"); // new default const [sku, setSku] = useState("DUMMY-9"); const [imageUrl, setImageUrl] = useState( "https://via.placeholder.com/300x300.png?text=Dummy+Image" ); const [collectionId, setCollectionId] = useState( collections[0]?.id || "" ); useEffect(() => { if (!collectionId && collections.length) { setCollectionId(collections[0].id); } }, [collections, collectionId]); return ({actionData.product.id} – Variant {actionData.variant.id} @ $ {actionData.variant.price} (SKU: {actionData.variant.sku})