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 } } } `); } } 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 toastMarkup = toastActive ? ( setToastActive(false)} /> ) : null; const selectedBrands = brands.filter((b) => selectedIds.includes(b.id)); return (
{filteredBrands.map((brand) => (
toggleSelect(brand.id)} />
{brand.name}
))}
{toastMarkup}
); }