+
- {(actionData.processId || false) && (
-
-
- Process ID: {actionData.processId}
-
-
- Status: {status || "—"}
-
-
- Check Status
-
-
- )}
-
-
+ {/* Left side - Search + Select All */}
+
+ {(actionData?.processId || false) && (
+
+
+ Process ID: {actionData.processId}
+
+
+ Status: {status || "—"}
+
+
+ Check Status
+
+
+ )}
+
+
+
+ {/* Right side - Save Button */}
+
@@ -236,30 +413,39 @@ export default function BrandsPage() {
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))",
gap: 16,
+ marginTop: "120px"
}}
>
- {filteredBrands.map(brand => (
+ {filteredBrands.map((brand) => (
-
-
toggleSelect(brand.id)}
- />
-
- {brand.name}
+
+ {/* Checkbox in top-right corner */}
+
+ toggleSelect(brand.id)}
+ />
+
+
+ {/* Brand image */}
+
+
+
+ {/* Brand name */}
+
+ {brand.name}
+
))}
-
-
diff --git a/app/routes/app.brands_2408.jsx b/app/routes/app.brands_2408.jsx
new file mode 100644
index 0000000..530d17f
--- /dev/null
+++ b/app/routes/app.brands_2408.jsx
@@ -0,0 +1,321 @@
+import { json } from "@remix-run/node";
+import { useLoaderData, Form, useActionData } from "@remix-run/react";
+import {
+ Page,
+ Layout,
+ Card,
+ TextField,
+ Checkbox,
+ Button,
+ Thumbnail,
+ Spinner,
+ Toast,
+ Frame,
+ Text,
+} 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);
+
+ // fetch 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 });
+ }
+
+ // fetch Shopify collections
+ const gqlRaw = await admin.graphql(`
+ {
+ collections(first: 100) {
+ edges {
+ node {
+ id
+ title
+ }
+ }
+ }
+ }
+ `);
+ const gql = await gqlRaw.json();
+ const collections = gql?.data?.collections?.edges.map(e => e.node) || [];
+
+
+
+
+
+
+
+ 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: brandJson.data, collections, selectedBrandsFromShopify: brands || [] });
+};
+
+export const action = async ({ request }) => {
+ const formData = await request.formData();
+ const selectedBrands = JSON.parse(formData.get("selectedBrands") || "[]");
+ const selectedOldBrands = JSON.parse(formData.get("selectedOldBrands") || "[]");
+ const { session } = await authenticate.admin(request);
+ const shop = session.shop; // "veloxautomotive.myshopify.com"
+
+ 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 { brands, collections, selectedBrandsFromShopify } = useLoaderData();
+ console.log(`selectedBrandsFromShopify: ${JSON.stringify(selectedBrandsFromShopify)}`);
+ const actionData = useActionData() || {};
+ const [selectedIdsold, setSelectedIdsold] = useState([])
+ // const [selectedIds, setSelectedIds] = useState(() => {
+ // const titles = new Set(collections.map(c => c.title.toLowerCase()));
+ // return brands
+ // .filter(b => titles.has(b.name.toLowerCase()))
+ // .map(b => b.id);
+ // });
+
+ const [selectedIds, setSelectedIds] = useState(() => {
+ return selectedBrandsFromShopify.map(b => b.id);
+ });
+ console.log("Selected IDS : ", selectedIds)
+ 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 || "");
+
+
+ useEffect(() => {
+ const selids = selectedIds
+ console.log("Selected IDS : ", selids)
+ setSelectedIdsold(selids)
+ }, [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);
+ }
+ }, [actionData.status]);
+
+ 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 =>
+ 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 = () => {
+ 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])));
+ }
+ };
+
+ var isSubmitting;
+ console.log("actionData", actionData);
+ if (actionData.status) {
+ isSubmitting = !actionData.status && !actionData.error && !actionData.processId;
+ } else {
+ isSubmitting = false;
+ }
+ console.log("isSubmitting", isSubmitting);
+
+ const toastMarkup = toastActive ? (
+ setToastActive(false)}
+ />
+ ) : null;
+
+ const selectedBrands = brands.filter(b => selectedIds.includes(b.id));
+ const selectedOldBrands = brands.filter(b => selectedIdsold.includes(b.id));
+ console.log("Selected Brands:", selectedBrands)
+ return (
+
+
+
+
+
+ Data4Autos Turn14 Brands List
+
+
+
+
+
+
+ {/* Left side - Search + Select All */}
+
+ {(actionData?.processId || false) && (
+
+
+ Process ID: {actionData.processId}
+
+
+ Status: {status || "—"}
+
+
+ Check Status
+
+
+ )}
+
+
+
+
+ {/* Right side - Save Button */}
+
+
+
+
+
+
+ {filteredBrands.map((brand) => (
+
+
+ {/* Checkbox in top-right corner */}
+
+ toggleSelect(brand.id)}
+ />
+
+
+ {/* Brand image */}
+
+
+
+ {/* Brand name */}
+
+ {brand.name}
+
+
+
+ ))}
+
+
+
+
+ {toastMarkup}
+
+
+ );
+}
diff --git a/app/routes/app.help.jsx b/app/routes/app.help.jsx
index 65b5c1e..25369bd 100644
--- a/app/routes/app.help.jsx
+++ b/app/routes/app.help.jsx
@@ -63,23 +63,53 @@ export default function HelpPage() {
{faqs.map((faq, index) => (
-
-
+ {/* Header */}
+ toggle(index)}
- fullWidth
- disclosure={openIndex === index}
- variant="plain"
>
- {faq.title}
-
-
-
- {faq.content}
+
+ {faq.title}
+
+ ▶
+
+
+
+ {/* Collapsible Body */}
+
+
+
+ {faq.content}
+
+
))}
+
Still have questions? Email us at{" "}
diff --git a/app/routes/app.jsx b/app/routes/app.jsx
index 61a82c3..47d2ab9 100644
--- a/app/routes/app.jsx
+++ b/app/routes/app.jsx
@@ -63,7 +63,7 @@ export default function App() {
🏷️ Brands
📦 Manage Brands
🆘 Help
- 🆘 Testing
+ {/* 🆘 Testing */}
diff --git a/app/routes/app.managebrand.jsx b/app/routes/app.managebrand.jsx
index d876521..612df03 100644
--- a/app/routes/app.managebrand.jsx
+++ b/app/routes/app.managebrand.jsx
@@ -17,10 +17,28 @@ import {
Frame,
Select,
ProgressBar,
+ Checkbox,
+ Text,
} from "@shopify/polaris";
import { authenticate } from "../shopify.server";
import { TitleBar } from "@shopify/app-bridge-react";
+
+async function checkShopExists(shop) {
+ try {
+ const resp = await fetch(
+ `https://backend.data4autos.com/checkisshopdataexists/${shop}`
+ );
+ const data = await resp.json();
+ return data.status === 1; // ✅ true if shop exists, false otherwise
+ } catch (err) {
+ console.error("Error checking shop:", err);
+ return false; // default to false if error
+ }
+}
+
+
+
export const loader = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const { getTurn14AccessTokenFromMetafield } = await import("../utils/turn14Token.server");
@@ -43,9 +61,166 @@ export const loader = async ({ request }) => {
console.error("❌ Failed to parse metafield value:", err);
}
- return json({ brands, accessToken });
+
+ const { session } = await authenticate.admin(request);
+ const shop = session.shop;
+
+ return json({ brands, accessToken, shop });
};
+
+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 { admin } = await authenticate.admin(request);
const formData = await request.formData();
@@ -88,7 +263,7 @@ export const action = async ({ request }) => {
export default function ManageBrandProducts() {
const actionData = useActionData();
- const { brands, accessToken } = useLoaderData();
+ const { shop, brands, accessToken } = useLoaderData();
const [expandedBrand, setExpandedBrand] = useState(null);
const [itemsMap, setItemsMap] = useState({});
const [loadingMap, setLoadingMap] = useState({});
@@ -105,6 +280,27 @@ export default function ManageBrandProducts() {
const [results, setResults] = useState([]);
const [detail, setDetail] = useState("");
+ const [filterregulatstock, setfilterregulatstock] = useState(false)
+
+
+
+ const [Turn14Enabled, setTurn14Enabled] = useState("12345"); // null | true | 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);
@@ -218,28 +414,133 @@ export default function ManageBrandProducts() {
setFilters((prev) => ({ ...prev, [field]: value }));
};
+
+
const applyFitmentFilters = (items) => {
return items.filter((item) => {
const tags = item?.attributes?.fitmmentTags || {};
- return (
- (!filters.make || tags.make?.includes(filters.make)) &&
- (!filters.model || tags.model?.includes(filters.model)) &&
- (!filters.year || tags.year?.includes(filters.year)) &&
- (!filters.drive || tags.drive?.includes(filters.drive)) &&
- (!filters.baseModel || tags.baseModel?.includes(filters.baseModel))
- );
+ const productName = item?.attributes?.product_name || '';
+ const brand = item?.attributes?.brand || '';
+ const partDescription = item?.attributes?.part_description || '';
+ 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));
+ // console.log(`Model check result: ${modelMatch}`);
+
+ 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));
+ /// console.log(`Year check result: ${yearMatch}`);
+
+ 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));
+ // console.log(`Drive check result: ${driveMatch}`);
+
+ 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));
+ // console.log(`Base Model check result: ${baseModelMatch}`);
+
+ // Combine all the conditions
+ var isMatch = makeMatch && modelMatch && yearMatch && driveMatch && baseModelMatch// && item.attributes.regular_stock
+ if (filterregulatstock) {
+ isMatch = isMatch && item?.attributes?.regular_stock
+ }
+
+ return isMatch;
});
};
+
+
const selectedProductIds = []
+
+ 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` },
+ ];
+
+ // If Turn14 is explicitly NOT connected, show a lightweight call-to-action screen
+ if (Turn14Enabled === false) {
+ return (
+
+
+
+
+
+
+
+
+ Turn14 isn’t connected yet
+
+
+
+ This shop hasn’t been configured with Turn14 / Data4Autos. To get started, open Settings and complete the connection.
+
+
+
+ {/* Primary actions */}
+
+
+
+
+ Once connected, you’ll be able to browse brands and sync collections.
+
+
+
+ {/* Secondary links */}
+
+
+
+
+
+
+
+ );
+ }
+
+
return (
-
+
+
+ Turn 14 Status: {" "}
+ {Turn14Enabled === true
+ ? "✅ Turn14 x Shopify Connected!"
+ : Turn14Enabled === false
+ ? "❌ Turn14 x Shopify Connection Doesn't Exists"
+ : "Checking..."}
+
+
{brands.length === 0 ? (
@@ -253,10 +554,11 @@ export default function ManageBrandProducts() {
resourceName={{ singular: "brand", plural: "brands" }}
itemCount={brands.length}
headings={[
- { title: "Brand ID" },
- { title: "Logo" },
- { title: "Action" },
- { title: "Products Count" },
+ { title: Brand ID
},
+ { title: Brand Name
},
+ { title: Brand Logo
},
+ { title: Action
},
+ { title: Products Count
},
]}
selectable={false}
>
@@ -266,6 +568,7 @@ export default function ManageBrandProducts() {
{brand.id}
+ {brand.name}
- toggleBrandItems(brand.id)}>
+ toggleBrandItems(brand.id)} variant="primary">
{expandedBrand === brand.id ? "Hide Products" : "Show Products"}
- {itemsMap[brand.id]?.length || 0}
+
+
+ {itemsMap[brand.id]?.length || 0}
+
+
)
})}
@@ -292,7 +611,7 @@ export default function ManageBrandProducts() {
{brands.map((brand) => {
const filteredItems = applyFitmentFilters(itemsMap[brand.id] || []);
- console.log("Filtered items for brand", brand.id, ":", filteredItems.map((item) => item.id));
+ // console.log("Filtered items for brand", brand.id, ":", filteredItems.map((item) => item.id));
const uniqueTags = {
make: new Set(),
model: new Set(),
@@ -314,8 +633,8 @@ export default function ManageBrandProducts() {
(
-
- {processId && (
+ {processId && (
+
Process ID: {processId}
@@ -359,15 +678,14 @@ export default function ManageBrandProducts() {
{status === 'done' ? 'View Results' : 'Check Status'}
- )}
-
-
+
+ )}
{loadingMap[brand.id] ? (
@@ -380,63 +698,79 @@ export default function ManageBrandProducts() {
value={JSON.stringify(filteredItems.map((item) => item.id))}
/>
- setProductCount(value)}
- autoComplete="off"
- />
-
- Add First {filteredItems.length} Products from {filters.make} to Store
-
+
+ setProductCount(value)}
+ autoComplete="off"
+ />
+
+ { setfilterregulatstock(!filterregulatstock) }}
+ />
+
+ Add First {filteredItems.length} Products from {filters.make} to Store
+
+
+
+
-
-
-
- ({ label: m, value: m }))]}
- onChange={handleFilterChange('make')}
- value={filters.make}
- />
-
-
-
-
-
- {filteredItems.map((item) => (
-
+
+
- ({ label: m, value: m }))]}
+ onChange={handleFilterChange('make')}
+ value={filters.make}
/>
-
-
- Part Number: {item?.attributes?.part_number || 'N/A'}
- Category: {item?.attributes?.category || 'N/A'} > {item?.attributes?.subcategory || 'N/A'}
- Price: ${item?.attributes?.price || '0.00'}
- Description: {item?.attributes?.part_description || 'No description available'}
-
-
+
- ))}
-
+
+
+ {filteredItems.map((item) => (
+
+
+
+
+
+
+
+ Part Number: {item?.attributes?.part_number || 'N/A'}
+ Category: {item?.attributes?.category || 'N/A'} > {item?.attributes?.subcategory || 'N/A'}
+ Price: ${item?.attributes?.price || '0.00'}
+ Description: {item?.attributes?.part_description || 'No description available'}
+
+
+
+
+ ))}
+
)}
diff --git a/app/routes/app.managebrand_130825.jsx b/app/routes/app.managebrand_130825.jsx
new file mode 100644
index 0000000..3517971
--- /dev/null
+++ b/app/routes/app.managebrand_130825.jsx
@@ -0,0 +1,477 @@
+import React, { useEffect, useState } from "react";
+import { json } from "@remix-run/node";
+import { useLoaderData, Form, useActionData } from "@remix-run/react";
+import {
+ Page,
+ Layout,
+ IndexTable,
+ Card,
+ Thumbnail,
+ TextContainer,
+ Spinner,
+ Button,
+ TextField,
+ Banner,
+ InlineError,
+ Toast,
+ Frame,
+ Select,
+ ProgressBar,
+} from "@shopify/polaris";
+import { authenticate } from "../shopify.server";
+import { TitleBar } from "@shopify/app-bridge-react";
+
+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 const action = async ({ request }) => {
+ 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 { brands, accessToken } = 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("");
+
+ useEffect(() => {
+ if (actionData?.processId) {
+ setProcessId(actionData.processId);
+ setStatus(actionData.status || "processing");
+ setToastActive(true);
+ }
+ }, [actionData]);
+
+ const checkStatus = async () => {
+ 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);
+ setProcessedProducts(data.stats.processed);
+ setCurrentProduct(data.current);
+
+ if (data.results) {
+ setResults(data.results);
+ }
+
+ // Continue polling if still processing
+ 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) {
+ toggleAllBrands();
+ setInitialLoad(false);
+ }
+ }, [brands, initialLoad]);
+
+ 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/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 ? (
+