import React, { useEffect, useMemo, useState } from "react"; import { json } from "@remix-run/node"; import { useLoaderData, Form, useActionData, useNavigate } from "@remix-run/react"; import { Page, Layout, IndexTable, Card, Thumbnail, TextContainer, Spinner, Button, TextField, Banner, Toast, Frame, Select, ProgressBar, Checkbox, Text, Popover, OptionList, InlineStack, } from "@shopify/polaris"; import { authenticate } from "../shopify.server"; import { TitleBar } from "@shopify/app-bridge-react"; const PLAN_NAME = "Starter Sync"; const ALLOWED_STATUSES = ["ACTIVE", "TRIAL"]; const styles = { gridContainer: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px", }, gridItem: { display: "flex", flexDirection: "column", }, gridFullWidthItem: { gridColumn: "span 2", display: "flex", flexDirection: "column", }, }; 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) { switch (interval) { case "ANNUAL": return "Every 12 months"; case "EVERY_30_DAYS": return "Every 30 days"; default: 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 }) => { const { getTurn14AccessTokenFromMetafield } = await import( "../utils/turn14Token.server" ); const { admin } = await authenticate.admin(request); const { session } = await authenticate.admin(request); const shop = session.shop; const { isSubscribed, subscription } = await getSubscriptionDetails(request); let accessToken = ""; try { accessToken = await getTurn14AccessTokenFromMetafield(request); } catch (err) { console.error("Error getting Turn14 access token:", err); return json({ brands: [], accessToken: "", shop, isSubscribed, subscription, }); } 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, shop, isSubscribed, subscription, }); }; const makes_list_raw = [ "Alfa Romeo", "Ferrari", "Dodge", "Subaru", "Toyota", "Volkswagen", "Volvo", "Audi", "BMW", "Buick", "Cadillac", "Chevrolet", "Chrysler", "CX Automotive", "Nissan", "Ford", "Hyundai", "Infiniti", "Lexus", "Mercury", "Mazda", "Oldsmobile", "Plymouth", "Pontiac", "Rolls-Royce", "Eagle", "Lincoln", "Mercedes-Benz", "GMC", "Saab", "Honda", "Saturn", "Mitsubishi", "Isuzu", "Jeep", "AM General", "Geo", "Suzuki", "E. P. Dutton, Inc.", "Land Rover", "PAS, Inc", "Acura", "Jaguar", "Lotus", "Grumman Olson", "Porsche", "American Motors Corporation", "Kia", "Lamborghini", "Panoz Auto-Development", "Maserati", "Saleen", "Aston Martin", "Dabryan Coach Builders Inc", "Federal Coach", "Vector", "Bentley", "Daewoo", "Qvale", "Roush Performance", "Autokraft Limited", "Bertone", "Panther Car Company Limited", "Texas Coach Company", "TVR Engineering Ltd", "Morgan", "MINI", "Yugo", "BMW Alpina", "Renault", "Bitter Gmbh and Co. Kg", "Scion", "Maybach", "Lambda Control Systems", "Merkur", "Peugeot", "Spyker", "London Coach Co Inc", "Hummer", "Bugatti", "Pininfarina", "Shelby", "Saleen Performance", "smart", "Tecstar, LP", "Kenyon Corporation Of America", "Avanti Motor Corporation", "Bill Dovell Motor Car Company", "Import Foreign Auto Sales Inc", "S and S Coach Company E.p. Dutton", "Superior Coaches Div E.p. Dutton", "Vixen Motor Company", "Volga Associated Automobile", "Wallace Environmental", "Import Trade Services", "J.K. Motors", "Panos", "Quantum Technologies", "London Taxi", "Red Shift Ltd.", "Ruf Automobile Gmbh", "Excalibur Autos", "Mahindra", "VPG", "Fiat", "Sterling", "Azure Dynamics", "McLaren Automotive", "Ram", "CODA Automotive", "Fisker", "Tesla", "Mcevoy Motors", "BYD", "ASC Incorporated", "SRT", "CCC Engineering", "Mobility Ventures LLC", "Pagani", "Genesis", "Karma", "Koenigsegg", "Aurora Cars Ltd", "RUF Automobile", "Dacia", "STI", "Daihatsu", "Polestar", "Kandi", "Rivian", "Lucid", "JBA Motorcars, Inc.", "Lordstown", "Vinfast", "INEOS Automotive", "Bugatti Rimac", "Grumman Allied Industries", "Environmental Rsch and Devp Corp", "Evans Automobiles", "Laforza Automobile Inc", "General Motors", "Consulier Industries Inc", "Goldacre", "Isis Imports Ltd", "PAS Inc - GMC", ]; const makes_list = makes_list_raw.sort(); export const action = async ({ request }) => { const { isSubscribed } = await getSubscriptionDetails(request); if (!isSubscribed) { return json( { error: "An active subscription or free trial is required to add products.", }, { status: 403 } ); } const { admin } = await authenticate.admin(request); const formData = await request.formData(); const brandId = formData.get("brandId"); const rawCount = formData.get("productCount"); const selectedProductIds = JSON.parse( formData.get("selectedProductIds") || "[]" ); const productCount = parseInt(rawCount, 10) || 10; const { getTurn14AccessTokenFromMetafield } = await import( "../utils/turn14Token.server" ); const accessToken = await getTurn14AccessTokenFromMetafield(request); const { session } = await authenticate.admin(request); const shop = session.shop; const resp = await fetch("https://backend.data4autos.com/manageProducts", { method: "POST", headers: { "Content-Type": "application/json", "shop-domain": shop, }, body: JSON.stringify({ shop, brandID: brandId, turn14accessToken: accessToken, productCount, selectedProductIds, }), }); console.log("Response from manageProducts:", resp.status, resp.statusText); if (!resp.ok) { const err = await resp.text(); return json({ error: err }, { status: resp.status }); } const { processId, status } = await resp.json(); console.log("Process ID:", processId, "Status:", status); return json({ success: true, processId, status }); }; export default function ManageBrandProducts() { const actionData = useActionData(); const navigate = useNavigate(); const { shop, brands, accessToken, isSubscribed, subscription } = useLoaderData(); const [expandedBrand, setExpandedBrand] = useState(null); const [itemsMap, setItemsMap] = useState({}); const [loadingMap, setLoadingMap] = useState({}); const [productCount, setProductCount] = useState("10"); const [initialLoad, setInitialLoad] = useState(true); const [toastActive, setToastActive] = useState(false); const [polling, setPolling] = useState(false); const [status, setStatus] = useState(actionData?.status || ""); const [processId, setProcessId] = useState(actionData?.processId || null); const [progress, setProgress] = useState(0); const [totalProducts, setTotalProducts] = useState(0); const [processedProducts, setProcessedProducts] = useState(0); const [currentProduct, setCurrentProduct] = useState(null); const [results, setResults] = useState([]); const [detail, setDetail] = useState(""); const [Turn14Enabled, setTurn14Enabled] = useState("12345"); const [filters, setFilters] = useState({ make: "", model: "", year: "", drive: "", baseModel: "", }); const [filterregulatstock, setfilterregulatstock] = useState(false); const [isFilter_EnableZeroStock, set_isFilter_EnableZeroStock] = useState(true); const [isFilter_IncludeLtlFreightRequired, setisFilter_IncludeLtlFreightRequired] = useState(true); const [isFilter_Excludeclearance_item, setisFilter_Excludeclearance_item] = useState(false); const [ isFilter_Excludeair_freight_prohibited, setisFilter_Excludeair_freight_prohibited, ] = useState(false); const [isFilter_IncludeProductWithNoImages, setisFilter_IncludeProductWithNoImages] = useState(true); const [popoverActive, setPopoverActive] = useState(false); useEffect(() => { if (!shop) { console.log("⚠️ shop is undefined or empty"); return; } (async () => { const result = await checkShopExists(shop); console.log("✅ API status result:", result, "| shop:", shop); setTurn14Enabled(result); })(); }, [shop]); useEffect(() => { if (actionData?.processId) { setProcessId(actionData.processId); setStatus(actionData.status || "processing"); setToastActive(true); } }, [actionData]); const checkStatus = async () => { if (!processId) return; setPolling(true); try { const response = await fetch( `https://backend.data4autos.com/manageProducts/status/${processId}` ); const data = await response.json(); setStatus(data.status); setDetail(data.detail); setProgress(data.progress); setTotalProducts(data.stats?.total || 0); setProcessedProducts(data.stats?.processed || 0); setCurrentProduct(data.current); if (data.results) { setResults(data.results); } if (data.status !== "done" && data.status !== "error") { setTimeout(checkStatus, 2000); } else { setPolling(false); } } catch (error) { setPolling(false); setStatus("error"); setDetail("Failed to check status"); console.error("Error checking status:", error); } }; useEffect(() => { let interval; if (status?.includes("processing") && processId) { interval = setInterval(checkStatus, 5000); } return () => clearInterval(interval); }, [status, processId]); const toggleAllBrands = async () => { for (const brand of brands) { await toggleBrandItems(brand.id); } }; useEffect(() => { if (initialLoad && brands.length > 0 && isSubscribed) { toggleAllBrands(); setInitialLoad(false); } }, [brands, initialLoad, isSubscribed]); const toggleBrandItems = async (brandId) => { if (!isSubscribed) return; 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/brandallitemswithfitment/${brandId}`, { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", }, } ); const data = await res.json(); const dataitems = data.items; const validItems = Array.isArray(dataitems) ? dataitems.filter((item) => item && item.id && item.attributes) : []; setItemsMap((prev) => ({ ...prev, [brandId]: validItems })); } catch (err) { console.error("Error fetching items:", err); setItemsMap((prev) => ({ ...prev, [brandId]: [] })); } setLoadingMap((prev) => ({ ...prev, [brandId]: false })); } } }; const toastMarkup = toastActive ? ( setToastActive(false)} /> ) : null; const handleFilterChange = (field) => (value) => { setFilters((prev) => ({ ...prev, [field]: value })); }; const applyFitmentFilters = (items) => { return items.filter((item) => { const tags = item?.attributes?.fitmmentTags || {}; const productName = item?.attributes?.product_name || ""; const brand = item?.attributes?.brand || ""; const descriptions = item?.attributes?.descriptions || []; const makeMatch = !filters.make || tags.make?.includes(filters.make) || productName.includes(filters.make) || brand.includes(filters.make) || descriptions.some((desc) => desc.description.includes(filters.make)); const modelMatch = !filters.model || tags.model?.includes(filters.model) || productName.includes(filters.model) || brand.includes(filters.model) || descriptions.some((desc) => desc.description.includes(filters.model)); const yearMatch = !filters.year || tags.year?.includes(filters.year) || productName.includes(filters.year) || brand.includes(filters.year) || descriptions.some((desc) => desc.description.includes(filters.year)); const driveMatch = !filters.drive || tags.drive?.includes(filters.drive) || productName.includes(filters.drive) || brand.includes(filters.drive) || descriptions.some((desc) => desc.description.includes(filters.drive)); const baseModelMatch = !filters.baseModel || tags.baseModel?.includes(filters.baseModel) || productName.includes(filters.baseModel) || brand.includes(filters.baseModel) || descriptions.some((desc) => desc.description.includes(filters.baseModel) ); let isMatch = makeMatch && modelMatch && yearMatch && driveMatch && baseModelMatch; if (filterregulatstock) { isMatch = isMatch && item?.attributes?.regular_stock; } if (!isFilter_EnableZeroStock) { isMatch = isMatch && item?.inventoryQuantity > 0; } if (!isFilter_IncludeLtlFreightRequired) { isMatch = isMatch && item?.attributes?.ltl_freight_required !== true; } if (isFilter_Excludeclearance_item) { isMatch = isMatch && item?.attributes?.clearance_item !== true; } if (isFilter_Excludeair_freight_prohibited) { isMatch = isMatch && item?.attributes?.air_freight_prohibited !== true; } if (!isFilter_IncludeProductWithNoImages) { isMatch = isMatch && item?.attributes?.files && item?.attributes?.files.length > 0; } return isMatch; }); }; const shopDomain = (shop || "").split(".")[0]; const items = [ { icon: "⚙️", text: "Manage API settings", link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/settings`, }, { icon: "🏷️", text: "Browse and import available brands", link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/brands`, }, { icon: "📦", text: "Sync brand collections to Shopify", link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/managebrand`, }, { icon: "🔐", text: "Handle secure Turn14 login credentials", link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/help`, }, ]; const togglePopover = () => setPopoverActive((active) => !active); const activator = ( ); const trialDaysLeft = useMemo(() => { if (!subscription?.trialDays || !subscription?.createdAt) return null; if (subscription.status !== "TRIAL") return null; const created = new Date(subscription.createdAt); const trialEnd = new Date(created); trialEnd.setDate(trialEnd.getDate() + subscription.trialDays); const now = new Date(); const msLeft = trialEnd.getTime() - now.getTime(); const daysLeft = Math.ceil(msLeft / (1000 * 60 * 60 * 24)); return Math.max(0, daysLeft); }, [subscription]); if (Turn14Enabled === false) { const safeItems = Array.isArray(items) && items.length >= 4 ? items : [ { link: "#", icon: "🔗", text: "Connect Turn14" }, { link: "#", icon: "⚙️", text: "Settings" }, { link: "#", icon: "❓", text: "Help" }, { link: "#", icon: "📄", text: "Documentation" }, ]; return (
Turn14 isn’t connected yet
This shop hasn’t been configured with Turn14 / Data4Autos. To get started, open Settings and complete the connection.
Once connected, you’ll be able to browse brands and sync collections.
); } return ( {!isSubscribed && (

This feature is available only for merchants with an active subscription or during the free trial period.

Current status:{" "} {subscription?.status || "Not subscribed"}

Plan: {subscription?.name || PLAN_NAME}

Billing:{" "} {getIntervalLabel(subscription?.interval)}

Price:{" "} {formatMoney( subscription?.priceAmount, subscription?.currencyCode )}

Next renewal / period end:{" "} {formatDate(subscription?.currentPeriodEnd)}

Trial days left:{" "} {trialDaysLeft != null ? `${trialDaysLeft} day(s)` : "N/A"}

)} {actionData?.error && (

{actionData.error}

)} {brands.length === 0 ? (

No brands selected yet.

) : ( Brand ID ), }, { title: (
Brand Name
), }, { title: (
Brand Logo
), }, { title: (
Action
), }, { title: (
Products Count
), }, ]} selectable={false} > {brands.map((brand, index) => ( {brand.id} {brand.name} {itemsMap[brand.id]?.length || 0} ))}
)} {isSubscribed && brands.map((brand) => { const filteredItems = applyFitmentFilters(itemsMap[brand.id] || []); return ( expandedBrand === brand.id && ( {processId && (

Process ID: {processId}

Status: {status || "—"}

{progress > 0 && (

{processedProducts} of {totalProducts} products processed {currentProduct && ` - Current: ${currentProduct.name} (${currentProduct.number}/${currentProduct.total})`}

)}
{status === "done" && results.length > 0 && (

Results: {results.length} products processed successfully

)} {status === "error" && (
Error: {detail}
)}
)} {loadingMap[brand.id] ? ( ) : (
item.id) )} />
setProductCount(value)} autoComplete="off" /> set_isFilter_EnableZeroStock( !isFilter_EnableZeroStock ) } /> setfilterregulatstock(!filterregulatstock) } /> setisFilter_IncludeLtlFreightRequired( !isFilter_IncludeLtlFreightRequired ) } /> setisFilter_Excludeclearance_item( !isFilter_Excludeclearance_item ) } /> setisFilter_Excludeair_freight_prohibited( !isFilter_Excludeair_freight_prohibited ) } /> setisFilter_IncludeProductWithNoImages( !isFilter_IncludeProductWithNoImages ) } />