// app/routes/store-credentials.jsx import { json, redirect } from "@remix-run/node"; import { useLoaderData, useActionData, Form } from "@remix-run/react"; import { useEffect, useState } from "react"; import { Page, Layout, Card, TextField, Button, TextContainer, InlineError, } from "@shopify/polaris"; import { TitleBar } from "@shopify/app-bridge-react"; import { authenticate } from "../../shopify.server"; const SCOPES = [ "read_inventory", "read_products", "write_inventory", "write_products", "read_publications", "write_publications", ].join(","); const REDIRECT_URI = "https://backend.dine360.ca/auth/callback"; const CLIENT_ID = "b7534c980967bad619cfdb9d3f837cfa"; export const loader = async ({ request }) => { const { admin } = await authenticate.admin(request); const resp = await admin.graphql(` { shop { id name metafield(namespace: "turn14", key: "credentials") { value } } } `); const { data } = await resp.json(); let creds = {}; if (data.shop.metafield?.value) { try { creds = JSON.parse(data.shop.metafield.value); } catch { } } creds = {}; return json({ shopName: data.shop.name, shopId: data.shop.id, savedCreds: creds, }); }; export const action = async ({ request }) => { const formData = await request.formData(); const { admin } = await authenticate.admin(request); // ——— Handle Shopify-install trigger ——— if (formData.get("install_shopify") === "1") { const shopName = formData.get("shop_name"); const stateNonce = Math.random().toString(36).slice(2); const installUrl = `https://${shopName}.myshopify.com/admin/oauth/authorize` + `?client_id=${CLIENT_ID}` + `&scope=${SCOPES}` + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` + `&state=${stateNonce}` + `&grant_options%5B%5D=per-user`; // return the URL instead of redirecting return json({ confirmationUrl: installUrl }); } // ——— Otherwise handle Turn14 token exchange ——— const clientId = formData.get("client_id"); const clientSecret = formData.get("client_secret"); const shopInfo = await admin.graphql(`{ shop { id } }`); const shopId = (await shopInfo.json()).data.shop.id; let tokenData; try { const tokenRes = await fetch("https://turn14.data4autos.com/v1/auth/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ grant_type: "client_credentials", client_id: clientId, client_secret: clientSecret, }), }); tokenData = await tokenRes.json(); if (!tokenRes.ok) { throw new Error(tokenData.error || "Failed to fetch Turn14 token"); } } catch (err) { return json({ success: false, error: err.message }); } // upsert as Shopify metafield const creds = { clientId, clientSecret, accessToken: tokenData.access_token, expiresAt: new Date(Date.now() + 3600 * 1000).toISOString(), }; const mutation = ` mutation { metafieldsSet(metafields: [{ ownerId: "${shopId}", namespace: "turn14", key: "credentials", type: "json", value: "${JSON.stringify(creds).replace(/"/g, '\\"')}" }]) { userErrors { message } } } `; const saveRes = await admin.graphql(mutation); const saveJson = await saveRes.json(); const errs = saveJson.data.metafieldsSet.userErrors; if (errs.length) { return json({ success: false, error: errs[0].message }); } return json({ success: true, creds }); }; export default function StoreCredentials() { const { shopName, shopId, savedCreds } = useLoaderData(); const actionData = useActionData(); useEffect(() => { if (actionData?.confirmationUrl) { window.open(actionData.confirmationUrl, "_blank", "noopener,noreferrer"); } }, [actionData?.confirmationUrl]); const [clientId, setClientId] = useState(actionData?.creds?.clientId || savedCreds.clientId || ""); const [clientSecret, setClientSecret] = useState(actionData?.creds?.clientSecret || savedCreds.clientSecret || ""); const connected = actionData?.success || Boolean(savedCreds.accessToken); return (

Shop: {shopName}

{/* —— TURN14 FORM —— */}
{actionData?.error && ( )} {connected && (

✅ Turn14 connected successfully!

{/* —— SHOPIFY INSTALL FORM —— */}
)}
); }