From d54bda31796d2a443f75b1bff2913c76f2de9d5c Mon Sep 17 00:00:00 2001 From: MOHAN Date: Thu, 2 Jul 2026 23:19:05 +0530 Subject: [PATCH] fix: use client-side navigation for nav cards and auto-refresh expired Turn14 tokens - NavCard now uses useNavigate() instead of to prevent full-page reloads that trigger Shopify re-auth (login page prompt) - Browse Brands and Manage Brands links changed from absolute admin.shopify.com URLs (caused iframe 'refused to connect') to relative /app/* paths - turn14Token.server.js: add forceRefresh param to skip cached token check - app.brands.jsx: detect 401/internal_error from Turn14 and retry once with a forced token refresh to recover from server-side token expiry Co-Authored-By: Claude Sonnet 4.6 --- app/routes/app._index.jsx | 25 +++++++++++++------------ app/routes/app.brands.jsx | 10 +++++++++- app/utils/turn14Token.server.js | 4 ++-- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/app/routes/app._index.jsx b/app/routes/app._index.jsx index 1b8368c..0dc3ef6 100644 --- a/app/routes/app._index.jsx +++ b/app/routes/app._index.jsx @@ -138,6 +138,7 @@ export const action = async ({ request }) => { /* ─── NavCard ────────────────────────────────────────────────────────────────── */ function NavCard({ icon, title, desc, link, accent }) { + const navigate = useNavigate(); const colors = { blue: { bg: "#eff6ff", border: "#bfdbfe", icon: "#2563eb" }, green: { bg: "#f0fdf4", border: "#bbf7d0", icon: "#16a34a" }, @@ -146,16 +147,16 @@ function NavCard({ icon, title, desc, link, accent }) { }; const c = colors[accent] || colors.blue; return ( - -
{ e.currentTarget.style.transform = "translateY(-2px)"; e.currentTarget.style.boxShadow = "0 8px 20px rgba(0,0,0,0.08)"; }} - onMouseLeave={(e) => { e.currentTarget.style.transform = ""; e.currentTarget.style.boxShadow = ""; }} - > -
{icon}
-
{title}
-
{desc}
-
-
+
navigate(link)} + style={{ background: c.bg, border: `1px solid ${c.border}`, borderRadius: 12, padding: "18px 20px", cursor: "pointer", transition: "transform 0.15s, box-shadow 0.15s", height: "100%", display: "flex", flexDirection: "column", gap: 10 }} + onMouseEnter={(e) => { e.currentTarget.style.transform = "translateY(-2px)"; e.currentTarget.style.boxShadow = "0 8px 20px rgba(0,0,0,0.08)"; }} + onMouseLeave={(e) => { e.currentTarget.style.transform = ""; e.currentTarget.style.boxShadow = ""; }} + > +
{icon}
+
{title}
+
{desc}
+
); } @@ -203,8 +204,8 @@ export default function Index() { const navItems = [ { icon: "⚙️", title: "Settings", desc: "Connect Turn14 API & configure pricing", link: `/app/settings`, accent: "purple" }, - { icon: "🏷️", title: "Browse Brands", desc: "Select brands to sync from Turn14", link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/brands`, accent: "blue" }, - { icon: "📦", title: "Manage Brands", desc: "Import products from selected brands", link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/managebrand`, accent: "green" }, + { icon: "🏷️", title: "Browse Brands", desc: "Select brands to sync from Turn14", link: `/app/brands`, accent: "blue" }, + { icon: "📦", title: "Manage Brands", desc: "Import products from selected brands", link: `/app/managebrand`, accent: "green" }, { icon: "📊", title: "Dashboard", desc: "Track live import progress & stats", link: `/app/dashboard`, accent: "amber" }, ]; diff --git a/app/routes/app.brands.jsx b/app/routes/app.brands.jsx index a197fd9..31b2685 100644 --- a/app/routes/app.brands.jsx +++ b/app/routes/app.brands.jsx @@ -120,8 +120,16 @@ export const loader = async ({ request }) => { let brandJson; try { - const brandRes = await fetch("https://turn14.data4autos.com/v1/brands", { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }); + let brandRes = await fetch("https://turn14.data4autos.com/v1/brands", { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }); brandJson = await brandRes.json(); + // Token expired on Turn14's side — force refresh and retry once + if (!brandRes.ok && (brandRes.status === 401 || brandJson?.error === "internal_error" || brandJson?.error === "invalid_token")) { + try { + accessToken = await getTurn14AccessTokenFromMetafield(request, true); + brandRes = await fetch("https://turn14.data4autos.com/v1/brands", { headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" } }); + brandJson = await brandRes.json(); + } catch {} + } 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 }); diff --git a/app/utils/turn14Token.server.js b/app/utils/turn14Token.server.js index cf60f2c..b479736 100644 --- a/app/utils/turn14Token.server.js +++ b/app/utils/turn14Token.server.js @@ -1,6 +1,6 @@ import { authenticate } from "../shopify.server"; -export async function getTurn14AccessTokenFromMetafield(request) { +export async function getTurn14AccessTokenFromMetafield(request, forceRefresh = false) { const { admin, session } = await authenticate.admin(request); const shop = session.shop; @@ -35,7 +35,7 @@ export async function getTurn14AccessTokenFromMetafield(request) { const expiresAt = new Date(creds.expiresAt); const isExpired = now > expiresAt; - if (!isExpired && creds.accessToken) { + if (!forceRefresh && !isExpired && creds.accessToken) { return creds.accessToken; }