backend_shopify_data4autos/app/routes/app.managebrand.jsx
metatroncubeswdev 6cb1d01b0c Inital commit
2025-06-27 18:48:53 -04:00

298 lines
9.7 KiB
JavaScript

import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import {
Page,
Layout,
Card,
Thumbnail,
TextContainer,
Spinner,
Button,
Text,
TextField,
} from "@shopify/polaris";
import { useState } from "react";
import { authenticate } from "../shopify.server";
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 { brands, accessToken } = useLoaderData();
const [expandedBrand, setExpandedBrand] = useState(null);
const [itemsMap, setItemsMap] = useState({});
const [loadingMap, setLoadingMap] = useState({});
const [productCount, setProductCount] = useState("10");
const [adding, setAdding] = useState(false);
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 }));
}
}
};
const handleAddProducts = async (brandId) => {
const count = parseInt(productCount || "10");
const items = (itemsMap[brandId] || []).slice(0, count);
if (!items.length) return alert("No products to add.");
setAdding(true);
for (const item of items) {
const attr = item.attributes;
// Step 1: Create Product (only allowed fields)
const productInput = {
title: attr.product_name,
descriptionHtml: `<p>${attr.part_description}</p>`,
vendor: attr.brand,
productType: attr.category,
tags: [attr.subcategory, attr.brand].filter(Boolean).join(", "),
};
const createProductRes = await fetch("/admin/api/2023-04/graphql.json", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": accessToken,
},
body: JSON.stringify({
query: `
mutation productCreate($input: ProductInput!) {
productCreate(input: $input) {
product {
id
title
}
userErrors {
field
message
}
}
}`,
variables: { input: productInput },
}),
});
const createProductResult = await createProductRes.json();
const product = createProductResult?.data?.productCreate?.product;
const productErrors = createProductResult?.data?.productCreate?.userErrors;
if (productErrors?.length || !product?.id) {
console.error("❌ Product create error:", productErrors);
continue;
}
const productId = product.id;
// Step 2: Create Variant
const variantInput = {
productId,
sku: attr.part_number,
barcode: attr.barcode || undefined,
price: "0.00",
weight: attr.dimensions?.[0]?.weight || 0,
weightUnit: "KILOGRAMS",
inventoryManagement: "SHOPIFY",
};
await fetch("/admin/api/2023-04/graphql.json", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": accessToken,
},
body: JSON.stringify({
query: `
mutation productVariantCreate($input: ProductVariantInput!) {
productVariantCreate(input: $input) {
productVariant {
id
}
userErrors {
field
message
}
}
}`,
variables: { input: variantInput },
}),
});
// Step 3: Add Image
if (attr.thumbnail) {
await fetch("/admin/api/2023-04/graphql.json", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Access-Token": accessToken,
},
body: JSON.stringify({
query: `
mutation productImageCreate($productId: ID!, $image: ImageInput!) {
productImageCreate(productId: $productId, image: $image) {
image {
id
src
}
userErrors {
field
message
}
}
}`,
variables: {
productId,
image: {
src: attr.thumbnail,
},
},
}),
});
}
console.log("✅ Added:", attr.product_name);
}
setAdding(false);
alert(`${items.length} products added.`);
};
return (
<Page title="Manage Brand Products">
<Layout>
{brands.length === 0 && (
<Layout.Section>
<Card sectioned>
<p>No brands selected yet.</p>
</Card>
</Layout.Section>
)}
{brands.map((brand) => (
<div key={brand.id}>
<Layout.Section oneThird>
<Card title={brand.name} sectioned>
<Thumbnail
source={brand.logo || "https://cdn.shopify.com/s/files/1/0757/9955/files/no-image_280x@2x.png"}
alt={brand.name}
size="large"
/>
<TextContainer spacing="tight">
<p><strong>Brand:</strong> {brand.name}</p>
<p><strong>ID:</strong> {brand.id}</p>
</TextContainer>
<div style={{ marginTop: "1rem" }}>
<Button onClick={() => toggleBrandItems(brand.id)} fullWidth>
{expandedBrand === brand.id ? "Hide Products" : "Show Products"}
</Button>
</div>
</Card>
</Layout.Section>
{expandedBrand === brand.id && (
<Layout.Section fullWidth>
<Card sectioned>
<TextField
label="Number of products to add"
type="number"
value={productCount}
onChange={setProductCount}
autoComplete="off"
/>
<div style={{ marginTop: "1rem" }}>
<Button
onClick={() => handleAddProducts(brand.id)}
loading={adding}
disabled={adding}
primary
>
Add First {productCount} Products to Store
</Button>
</div>
</Card>
<Card title={`Items from ${brand.name}`} sectioned>
{loadingMap[brand.id] ? (
<Spinner accessibilityLabel="Loading items" size="small" />
) : (
<div style={{ paddingTop: "1rem" }}>
{(itemsMap[brand.id] || []).map(item => (
<Card key={item.id} title={item.attributes.product_name} sectioned>
<Layout>
<Layout.Section oneThird>
<Thumbnail
source={
item.attributes.thumbnail ||
"https://cdn.shopify.com/s/files/1/0757/9955/files/no-image_280x@2x.png"
}
alt={item.attributes.product_name}
size="large"
/>
</Layout.Section>
<Layout.Section>
<TextContainer spacing="tight">
<p><strong>Part Number:</strong> {item.attributes.part_number}</p>
<p><strong>Brand:</strong> {item.attributes.brand}</p>
<p><strong>Category:</strong> {item.attributes.category} > {item.attributes.subcategory}</p>
<p><strong>Dimensions:</strong> {item.attributes.dimensions?.[0]?.length} x {item.attributes.dimensions?.[0]?.width} x {item.attributes.dimensions?.[0]?.height} in</p>
</TextContainer>
</Layout.Section>
</Layout>
</Card>
))}
</div>
)}
</Card>
</Layout.Section>
)}
</div>
))}
</Layout>
</Page>
);
}