209 lines
6.3 KiB
JavaScript
209 lines
6.3 KiB
JavaScript
// 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 (
|
|
<Page title="Data4Autos Turn14 Integration">
|
|
<TitleBar title="Turn14 & Shopify Connect" />
|
|
<Layout>
|
|
<Layout.Section>
|
|
<Card sectioned>
|
|
<TextContainer spacing="tight">
|
|
<p><strong>Shop:</strong> {shopName}</p>
|
|
</TextContainer>
|
|
|
|
{/* —— TURN14 FORM —— */}
|
|
<Form method="post">
|
|
<input type="hidden" name="shop_name" value={shopName} />
|
|
<TextField
|
|
label="Turn14 Client ID"
|
|
name="client_id"
|
|
value={clientId}
|
|
onChange={setClientId}
|
|
autoComplete="off"
|
|
requiredIndicator
|
|
/>
|
|
<TextField
|
|
label="Turn14 Client Secret"
|
|
name="client_secret"
|
|
value={clientSecret}
|
|
onChange={setClientSecret}
|
|
autoComplete="off"
|
|
requiredIndicator
|
|
/>
|
|
<div style={{ marginTop: "1rem" }}>
|
|
<Button submit primary>
|
|
Connect Turn14
|
|
</Button>
|
|
</div>
|
|
</Form>
|
|
|
|
{actionData?.error && (
|
|
<TextContainer spacing="tight" style={{ marginTop: "1rem" }}>
|
|
<InlineError message={actionData.error} fieldID="client_id" />
|
|
</TextContainer>
|
|
)}
|
|
|
|
{connected && (
|
|
<TextContainer spacing="tight" style={{ marginTop: "1.5rem" }}>
|
|
<p style={{ color: "green" }}>✅ Turn14 connected successfully!</p>
|
|
|
|
{/* —— SHOPIFY INSTALL FORM —— */}
|
|
<Form method="post">
|
|
<input type="hidden" name="shop_name" value={shopName} />
|
|
<input type="hidden" name="install_shopify" value="1" />
|
|
<div style={{ marginTop: "1rem" }}>
|
|
<Button submit primary>
|
|
Connect to Shopify
|
|
</Button>
|
|
</div>
|
|
</Form>
|
|
</TextContainer>
|
|
)}
|
|
</Card>
|
|
</Layout.Section>
|
|
</Layout>
|
|
</Page>
|
|
);
|
|
}
|