import { json } from "@remix-run/node"; import { useLoaderData, useFetcher } from "@remix-run/react"; import { Page, Layout, Card, TextField, Checkbox, Button, Thumbnail, Spinner, Toast, Frame, } from "@shopify/polaris"; import { useEffect, useState } from "react"; import { TitleBar } from "@shopify/app-bridge-react"; import { getTurn14AccessTokenFromMetafield } from "../utils/turn14Token.server"; import { authenticate } from "../shopify.server"; export const loader = async ({ request }) => { const accessToken = await getTurn14AccessTokenFromMetafield(request); const { admin } = await authenticate.admin(request); // Get brands const brandRes = await fetch("https://turn14.data4autos.com/v1/brands", { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, }); const brandJson = await brandRes.json(); if (!brandRes.ok) { return json({ error: brandJson.error || "Failed to fetch brands" }, { status: 500 }); } // Get collections const gqlRaw = await admin.graphql(` { collections(first: 100) { edges { node { id title handle } } } } `); const gql = await gqlRaw.json(); const collections = gql?.data?.collections?.edges?.map((e) => e.node) || []; return json({ brands: brandJson.data, collections, }); }; export const action = async ({ request }) => { const formData = await request.formData(); const selectedBrands = JSON.parse(formData.get("selectedBrands") || "[]"); const { admin } = await authenticate.admin(request); // Get current collections const gqlRaw = await admin.graphql(` { collections(first: 100) { edges { node { id title } } } } `); const gql = await gqlRaw.json(); const existingCollections = gql?.data?.collections?.edges?.map((e) => e.node) || []; const selectedTitles = selectedBrands.map((b) => b.name.toLowerCase()); const logoMap = Object.fromEntries(selectedBrands.map(b => [b.name.toLowerCase(), b.logo])); // Delete unselected for (const col of existingCollections) { if (!selectedTitles.includes(col.title.toLowerCase())) { await admin.graphql(` mutation { collectionDelete(input: { id: "${col.id}" }) { deletedCollectionId userErrors { message } } } `); } } // Create new // for (const brand of selectedBrands) { // const exists = existingCollections.find( // (c) => c.title.toLowerCase() === brand.name.toLowerCase() // ); // if (!exists) { // const escapedName = brand.name.replace(/"/g, '\\"'); // const logo = brand.logo || ""; // await admin.graphql(` // mutation { // collectionCreate(input: { // title: "${escapedName}", // descriptionHtml: "Products from brand ${escapedName}", // image: { // altText: "${escapedName} Logo", // src: "${logo}" // } // }) { // collection { id } // userErrors { message } // } // } // `); // } // } // for (const brand of selectedBrands) { // const exists = existingCollections.find( // (c) => c.title.toLowerCase() === brand.name.toLowerCase() // ); // if (!exists) { // const escapedName = brand.name.replace(/"/g, '\\"'); // // Only build the image block if there's a logo URL: // const imageBlock = brand.logo // ? ` // image: { // altText: "${escapedName} Logo", // src: "${brand.logo}" // } // ` // : ""; // await admin.graphql(` // mutation { // collectionCreate(input: { // title: "${escapedName}", // descriptionHtml: "Products from brand ${escapedName}" // ${imageBlock} // }) { // collection { id } // userErrors { message } // } // } // `); // } // } const fallbackLogo = "https://cdn.shopify.com/s/files/1/0757/9955/files/no-image_280x@2x.png"; for (const brand of selectedBrands) { const exists = existingCollections.find( (c) => c.title.toLowerCase() === brand.name.toLowerCase() ); if (exists) continue; const escapedName = brand.name.replace(/"/g, '\\"'); const logoSrc = brand.logo || fallbackLogo; await admin.graphql(` mutation { collectionCreate(input: { title: "${escapedName}", descriptionHtml: "Products from brand ${escapedName}", image: { altText: "${escapedName} Logo", src: "${logoSrc}" } }) { collection { id } userErrors { message } } } `); } const shopDataRaw = await admin.graphql(` { shop { id } } `); const shopRes = await admin.graphql(`{ shop { id } }`); const shopJson = await shopRes.json(); const shopId = shopJson?.data?.shop?.id; await admin.graphql(` mutation { metafieldsSet(metafields: [{ namespace: "turn14", key: "selected_brands", type: "json", ownerId: "${shopId}", value: ${JSON.stringify(JSON.stringify(selectedBrands))} }]) { metafields { id } userErrors { message } } } `); return json({ success: true }); }; export default function BrandsPage() { const { brands, collections } = useLoaderData(); const fetcher = useFetcher(); const isSubmitting = fetcher.state === "submitting"; const [toastActive, setToastActive] = useState(false); const [search, setSearch] = useState(""); const collectionTitles = new Set(collections.map((c) => c.title.toLowerCase())); const defaultSelected = brands .filter((b) => collectionTitles.has(b.name.toLowerCase())) .map((b) => b.id); const [selectedIds, setSelectedIds] = useState(defaultSelected); const [filteredBrands, setFilteredBrands] = useState(brands); useEffect(() => { const term = search.toLowerCase(); setFilteredBrands(brands.filter((b) => b.name.toLowerCase().includes(term))); }, [search, brands]); useEffect(() => { if (fetcher.data?.success) { setToastActive(true); } }, [fetcher.data]); const toggleSelect = (id) => { setSelectedIds((prev) => prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id] ); }; const toggleSelectAll = () => { const filteredBrandIds = filteredBrands.map(b => b.id); const allFilteredSelected = filteredBrandIds.every(id => selectedIds.includes(id)); if (allFilteredSelected) { // Deselect all filtered brands setSelectedIds(prev => prev.filter(id => !filteredBrandIds.includes(id))); } else { // Select all filtered brands setSelectedIds(prev => { const combined = new Set([...prev, ...filteredBrandIds]); return Array.from(combined); }); } }; const toastMarkup = toastActive ? ( setToastActive(false)} /> ) : null; const selectedBrands = brands.filter((b) => selectedIds.includes(b.id)); const allFilteredSelected = filteredBrands.length > 0 && filteredBrands.every(brand => selectedIds.includes(brand.id)); return (
{filteredBrands.map((brand) => (
toggleSelect(brand.id)} />
{brand.name}
))}
{toastMarkup}
); }