import { json } from "@remix-run/node"; import { useLoaderData, Form, useActionData, useNavigate } from "@remix-run/react"; import { Page, Layout, Card, TextField, Checkbox, Button, Spinner, Toast, Frame, Text, Banner, InlineStack, } from "@shopify/polaris"; import { useEffect, useMemo, useState } from "react"; import { TitleBar } from "@shopify/app-bridge-react"; import { getTurn14AccessTokenFromMetafield } from "../utils/turn14Token.server"; import { authenticate } from "../shopify.server"; const PLAN_NAME = "Starter Sync"; const ALLOWED_STATUSES = ["ACTIVE", "TRIAL"]; async function checkShopExists(shop) { try { const resp = await fetch(`https://backend.data4autos.com/checkisshopdataexists/${shop}`); const data = await resp.json(); return data.status === 1; } catch (err) { console.error("Error checking shop:", err); return false; } } function getIntervalLabel(interval) { if (interval === "ANNUAL") return "Every 12 months"; if (interval === "EVERY_30_DAYS") return "Every 30 days"; return interval || "N/A"; } function formatMoney(amount, currencyCode = "USD") { if (amount == null) return "N/A"; return `${currencyCode} ${Number(amount).toFixed(2)}`; } function formatDate(date) { if (!date) return "N/A"; return new Date(date).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" }); } async function getSubscriptionDetails(request) { const { admin, session } = await authenticate.admin(request); const shop = session.shop; const resp = await admin.graphql(` query CurrentSubscriptionDetails { currentAppInstallation { activeSubscriptions { id name status test createdAt trialDays currentPeriodEnd lineItems { id plan { pricingDetails { __typename ... on AppRecurringPricing { interval price { amount currencyCode } } } } } } } } `); const result = await resp.json(); const subscriptions = result?.data?.currentAppInstallation?.activeSubscriptions || []; const subscription = subscriptions.find((sub) => ALLOWED_STATUSES.includes(sub.status)) || subscriptions[0] || null; const recurringPricing = subscription?.lineItems?.find((item) => item?.plan?.pricingDetails?.__typename === "AppRecurringPricing")?.plan?.pricingDetails || null; const isSubscribed = !!subscription && ALLOWED_STATUSES.includes(subscription.status); return { shop, isSubscribed, subscription: subscription ? { id: subscription.id, name: subscription.name || PLAN_NAME, status: subscription.status, test: subscription.test ?? false, createdAt: subscription.createdAt, trialDays: subscription.trialDays ?? 0, currentPeriodEnd: subscription.currentPeriodEnd, interval: recurringPricing?.interval || null, priceAmount: recurringPricing?.price?.amount || null, currencyCode: recurringPricing?.price?.currencyCode || "USD", } : null, }; } export const loader = async ({ request }) => { console.log("🚀 Loader started"); let admin, session, shop; try { const authResult = await authenticate.admin(request); admin = authResult.admin; session = authResult.session; shop = session?.shop; } catch (err) { return json({ brands: [], collections: [], selectedBrandsFromShopify: [], shop: "", error: "Shopify authentication failed", isSubscribed: false, subscription: null }); } let { isSubscribed, subscription } = await getSubscriptionDetails(request); if (!isSubscribed) { try { const far = await fetch(`https://backend.data4autos.com/free-access/${encodeURIComponent(shop)}`); const fad = await far.json(); if (fad.allowed === true) isSubscribed = true; } catch {} } let accessToken = ""; try { accessToken = await getTurn14AccessTokenFromMetafield(request); } catch (err) { return json({ brands: [], collections: [], selectedBrandsFromShopify: [], shop, error: "Failed to fetch Turn14 access token", isSubscribed, subscription }); } let brandJson; try { const brandRes = await fetch("https://turn14.data4autos.com/v1/brands", { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }); brandJson = await brandRes.json(); if (!brandRes.ok) return json({ brands: [], collections: [], selectedBrandsFromShopify: [], shop, error: brandJson?.error || "Failed to fetch brands", isSubscribed, subscription }); } catch (err) { return json({ brands: [], collections: [], selectedBrandsFromShopify: [], shop, error: "Turn14 brands fetch crashed", isSubscribed, subscription }); } let collections = []; try { const gqlRaw = await admin.graphql(`{ collections(first: 100) { edges { node { id title } } } }`); const gql = await gqlRaw.json(); collections = gql?.data?.collections?.edges?.map((e) => e.node) || []; } catch (err) {} let selectedBrands = []; try { 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; if (rawValue) selectedBrands = JSON.parse(rawValue); } catch (err) {} return json({ brands: brandJson?.data || [], collections, selectedBrandsFromShopify: selectedBrands, shop, isSubscribed, subscription }); }; export const action = async ({ request }) => { const { session } = await authenticate.admin(request); const shop = session.shop; let { isSubscribed } = await getSubscriptionDetails(request); if (!isSubscribed) { try { const far = await fetch(`https://backend.data4autos.com/free-access/${encodeURIComponent(shop)}`); const fad = await far.json(); if (fad.allowed === true) isSubscribed = true; } catch {} } if (!isSubscribed) return json({ error: "An active subscription or free trial is required to save brand collections." }, { status: 403 }); const formData = await request.formData(); const selectedBrands = JSON.parse(formData.get("selectedBrands") || "[]"); const selectedOldBrands = JSON.parse(formData.get("selectedOldBrands") || "[]"); selectedBrands.forEach((brand) => { delete brand.pricegroups; }); selectedOldBrands.forEach((brand) => { delete brand.pricegroups; }); const resp = await fetch("https://backend.data4autos.com/managebrands", { method: "POST", headers: { "Content-Type": "application/json", "shop-domain": shop }, body: JSON.stringify({ shop, selectedBrands, selectedOldBrands }), }); if (!resp.ok) { const err = await resp.text(); return json({ error: err }, { status: resp.status }); } const { processId, status } = await resp.json(); return json({ processId, status }); }; export default function BrandsPage() { const navigate = useNavigate(); const { brands = [], selectedBrandsFromShopify = [], shop = "", error, isSubscribed = false, subscription = null } = useLoaderData() || {}; const actionData = useActionData() || {}; const [selectedIdsold, setSelectedIdsold] = useState([]); const [selectedIds, setSelectedIds] = useState(() => (selectedBrandsFromShopify ?? []).map((b) => b.id)); const [search, setSearch] = useState(""); const [filteredBrands, setFilteredBrands] = useState(brands); const [toastActive, setToastActive] = useState(false); const [polling, setPolling] = useState(false); const [status, setStatus] = useState(actionData.status || ""); const [Turn14Enabled, setTurn14Enabled] = useState(null); const [saving, setSaving] = useState(false); useEffect(() => { if (!shop) return; (async () => { const result = await checkShopExists(shop); setTurn14Enabled(result); })(); }, [shop]); useEffect(() => { setSelectedIdsold(selectedIds); }, [toastActive]); useEffect(() => { const term = search.toLowerCase(); setFilteredBrands(brands.filter((b) => b.name.toLowerCase().includes(term))); }, [search, brands]); useEffect(() => { if (actionData.status) { setStatus(actionData.status); setToastActive(true); setSaving(false); } if (actionData.error) setSaving(false); }, [actionData.status, actionData.error]); const checkStatus = async () => { if (!actionData.processId) return; setPolling(true); const resp = await fetch(`https://backend.data4autos.com/managebrands/status/${actionData.processId}`, { headers: { "shop-domain": window.shopify?.shop || "" } }); const jsonBody = await resp.json(); setStatus(jsonBody.status + (jsonBody.detail ? ` (${jsonBody.detail})` : "")); setPolling(false); }; const toggleSelect = (id) => { if (!isSubscribed) return; setSelectedIds((prev) => prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]); }; const allFilteredSelected = filteredBrands.length > 0 && filteredBrands.every((b) => selectedIds.includes(b.id)); const toggleSelectAll = () => { if (!isSubscribed) return; const ids = filteredBrands.map((b) => b.id); if (allFilteredSelected) setSelectedIds((prev) => prev.filter((id) => !ids.includes(id))); else setSelectedIds((prev) => Array.from(new Set([...prev, ...ids]))); }; const trialDaysLeft = useMemo(() => { if (!subscription?.trialDays || !subscription?.createdAt || subscription.status !== "TRIAL") return null; const created = new Date(subscription.createdAt); const trialEnd = new Date(created); trialEnd.setDate(trialEnd.getDate() + subscription.trialDays); return Math.max(0, Math.ceil((trialEnd.getTime() - Date.now()) / (1000 * 60 * 60 * 24))); }, [subscription]); const selectedBrands = brands.filter((b) => selectedIds.includes(b.id)); const selectedOldBrands = brands.filter((b) => selectedIdsold.includes(b.id)); const shopDomain = (shop || "").split(".")[0]; if (Turn14Enabled === false) { return (
🏷️ Brand Selection
{shop}
🔌
Turn14 not connected yet
Complete the connection in Settings before browsing brands.
); } return ( {/* Header */}
🏷️ Brand Selection
{filteredBrands.length} brands available · {selectedIds.length} selected
{!isSubscribed && (
⚠️ Subscription required
)}
{/* Subscription warning */} {!isSubscribed && (
⚠️ Active subscription required
Brand selection is only available with an active subscription or free trial.
)} {error && (
⛔ {error}
)} {actionData?.error && (
⛔ {actionData.error}
)} {/* Sticky toolbar */}
Select All
{selectedIds.length} selected
{(actionData?.processId) && (
Status: {status || "—"}
)}
setSaving(true)}>
{/* Brand grid */}
{filteredBrands.map((brand) => { const isSelected = selectedIds.includes(brand.id); return (
toggleSelect(brand.id)} style={{ background: isSelected ? "#eff6ff" : "#fff", border: `2px solid ${isSelected ? "#2563eb" : "#e5e7eb"}`, borderRadius: 12, padding: "16px 14px", cursor: isSubscribed ? "pointer" : "default", position: "relative", transition: "all 0.15s", boxShadow: isSelected ? "0 0 0 3px rgba(37,99,235,0.12)" : "none" }} > {/* Checkbox badge */}
{isSelected && }
{brand.name}
{brand.name}
ID: {brand.id}
); })}
{filteredBrands.length === 0 && (
🔍
No brands found
Try a different search term
)} {toastActive && ( setToastActive(false)} /> )}
); }