fix: use client-side navigation for nav cards and auto-refresh expired Turn14 tokens
- NavCard now uses useNavigate() instead of <a href> 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 <noreply@anthropic.com>
This commit is contained in:
parent
d165883d9c
commit
d54bda3179
@ -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 (
|
||||
<a href={link} style={{ textDecoration: "none", display: "block" }}>
|
||||
<div 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 = ""; }}
|
||||
>
|
||||
<div style={{ fontSize: 28 }}>{icon}</div>
|
||||
<div style={{ fontWeight: 700, fontSize: 15, color: c.icon }}>{title}</div>
|
||||
<div style={{ fontSize: 13, color: "#6b7280", lineHeight: 1.5 }}>{desc}</div>
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
onClick={() => 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 = ""; }}
|
||||
>
|
||||
<div style={{ fontSize: 28 }}>{icon}</div>
|
||||
<div style={{ fontWeight: 700, fontSize: 15, color: c.icon }}>{title}</div>
|
||||
<div style={{ fontSize: 13, color: "#6b7280", lineHeight: 1.5 }}>{desc}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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" },
|
||||
];
|
||||
|
||||
|
||||
@ -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 });
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user