429 lines
11 KiB
JavaScript
429 lines
11 KiB
JavaScript
import { json } from "@remix-run/node";
|
|
import { useLoaderData, useActionData, Form } from "@remix-run/react";
|
|
import { 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";
|
|
|
|
export const loader = async ({ request }) => {
|
|
const { admin } = await authenticate.admin(request);
|
|
|
|
// Fetch shop info and stored credentials
|
|
const gqlResponse = await admin.graphql(`
|
|
{
|
|
shop {
|
|
id
|
|
name
|
|
metafield(namespace: "turn14", key: "credentials") {
|
|
value
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
const shopData = await gqlResponse.json();
|
|
|
|
const shopName = shopData?.data?.shop?.name || "Unknown Shop";
|
|
const metafieldRaw = shopData?.data?.shop?.metafield?.value;
|
|
|
|
let creds = {};
|
|
if (metafieldRaw) {
|
|
try {
|
|
creds = JSON.parse(metafieldRaw);
|
|
} catch (err) {
|
|
console.error("Failed to parse stored credentials:", err);
|
|
}
|
|
}
|
|
|
|
return json({ shopName, creds });
|
|
};
|
|
|
|
export const action = async ({ request }) => {
|
|
const formData = await request.formData();
|
|
const clientId = formData.get("client_id") || "";
|
|
const clientSecret = formData.get("client_secret") || "";
|
|
|
|
const { admin } = await authenticate.admin(request);
|
|
|
|
// Fetch shop ID
|
|
const shopInfo = await admin.graphql(`{ shop { id } }`);
|
|
const shopId = (await shopInfo.json())?.data?.shop?.id;
|
|
|
|
// Get Turn14 token
|
|
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,
|
|
}),
|
|
});
|
|
|
|
const tokenData = await tokenRes.json();
|
|
|
|
if (!tokenRes.ok) {
|
|
return json({
|
|
success: false,
|
|
error: tokenData.error || "Failed to fetch access token",
|
|
});
|
|
}
|
|
|
|
const accessToken = tokenData.access_token;
|
|
const expiresAt = new Date(Date.now() + 3600 * 1000).toISOString();
|
|
|
|
const credentials = {
|
|
clientId,
|
|
clientSecret,
|
|
accessToken,
|
|
expiresAt,
|
|
};
|
|
|
|
// Upsert as metafield in Shopify
|
|
const mutation = `
|
|
mutation {
|
|
metafieldsSet(metafields: [
|
|
{
|
|
ownerId: "${shopId}"
|
|
namespace: "turn14"
|
|
key: "credentials"
|
|
type: "json"
|
|
value: "${JSON.stringify(credentials).replace(/"/g, '\\"')}"
|
|
}
|
|
]) {
|
|
metafields {
|
|
key
|
|
value
|
|
}
|
|
userErrors {
|
|
field
|
|
message
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
|
|
|
|
const saveRes = await admin.graphql(mutation);
|
|
const result = await saveRes.json();
|
|
|
|
if (result?.data?.metafieldsSet?.userErrors?.length) {
|
|
return json({
|
|
success: false,
|
|
error: result.data.metafieldsSet.userErrors[0].message,
|
|
});
|
|
}
|
|
|
|
return json({
|
|
success: true,
|
|
clientId,
|
|
clientSecret,
|
|
accessToken,
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error("Turn14 token fetch failed:", err);
|
|
return json({
|
|
success: false,
|
|
error: "Network or unexpected error occurred",
|
|
});
|
|
}
|
|
};
|
|
|
|
export default function SettingsPage({ standalone = true }) {
|
|
const loaderData = useLoaderData();
|
|
const actionData = useActionData();
|
|
|
|
const savedCreds = loaderData?.creds || {};
|
|
const shopName = loaderData?.shopName || "Shop";
|
|
|
|
const [clientId, setClientId] = useState(actionData?.clientId || savedCreds.clientId || "");
|
|
const [clientSecret, setClientSecret] = useState(actionData?.clientSecret || savedCreds.clientSecret || "");
|
|
const displayToken = actionData?.accessToken || savedCreds.accessToken;
|
|
|
|
return (
|
|
<Page title="Data4Autos Turn14 API Settings">
|
|
<TitleBar title="Data4Autos Turn14 Integration" />
|
|
<Layout>
|
|
<Layout.Section>
|
|
<Card sectioned>
|
|
<TextContainer spacing="tight">
|
|
<p><strong>Connected Shop:</strong> {shopName}</p>
|
|
</TextContainer>
|
|
|
|
<Form method="post">
|
|
<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
|
|
</Button>
|
|
</div>
|
|
</Form>
|
|
|
|
{actionData?.error && (
|
|
<TextContainer spacing="tight" style={{ marginTop: "1rem" }}>
|
|
<InlineError message={`❌ ${actionData.error}`} fieldID="client_id" />
|
|
</TextContainer>
|
|
)}
|
|
|
|
{displayToken && (
|
|
<TextContainer spacing="tight" style={{ marginTop: "1rem" }}>
|
|
<p style={{ color: "green" }}>✅ Connection Successful</p>
|
|
{/* <code style={{
|
|
background: "#f4f4f4",
|
|
padding: "10px",
|
|
display: "block",
|
|
marginTop: "8px",
|
|
wordWrap: "break-word"
|
|
}}>
|
|
{displayToken}
|
|
</code> */}
|
|
</TextContainer>
|
|
)}
|
|
</Card>
|
|
</Layout.Section>
|
|
</Layout>
|
|
</Page>
|
|
);
|
|
}
|
|
|
|
/*
|
|
import { json } from "@remix-run/node";
|
|
import { useLoaderData, useActionData, Form } from "@remix-run/react";
|
|
import { useState } from "react";
|
|
import {
|
|
Page,
|
|
Layout,
|
|
Card,
|
|
TextField,
|
|
Button,
|
|
InlineError,
|
|
BlockStack,
|
|
Text,
|
|
} from "@shopify/polaris";
|
|
import { authenticate } from "../shopify.server";
|
|
|
|
export const loader = async ({ request }) => {
|
|
const { admin } = await authenticate.admin(request);
|
|
|
|
const gqlResponse = await admin.graphql(`
|
|
{
|
|
shop {
|
|
id
|
|
name
|
|
metafield(namespace: "turn14", key: "credentials") {
|
|
value
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
|
|
const shopData = await gqlResponse.json();
|
|
const shopName = shopData?.data?.shop?.name || "Unknown Shop";
|
|
const metafieldRaw = shopData?.data?.shop?.metafield?.value;
|
|
|
|
let creds = {};
|
|
if (metafieldRaw) {
|
|
try {
|
|
creds = JSON.parse(metafieldRaw);
|
|
} catch (err) {
|
|
console.error("Failed to parse stored credentials:", err);
|
|
}
|
|
}
|
|
|
|
return json({ shopName, creds });
|
|
};
|
|
|
|
export const action = async ({ request }) => {
|
|
const formData = await request.formData();
|
|
const clientId = formData.get("client_id") || "";
|
|
const clientSecret = formData.get("client_secret") || "";
|
|
|
|
const { admin } = await authenticate.admin(request);
|
|
|
|
const shopInfo = await admin.graphql(`{ shop { id } }`);
|
|
const shopId = (await shopInfo.json())?.data?.shop?.id;
|
|
|
|
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,
|
|
}),
|
|
});
|
|
|
|
const tokenData = await tokenRes.json();
|
|
|
|
if (!tokenRes.ok) {
|
|
return json({
|
|
success: false,
|
|
error: tokenData.error || "Failed to fetch access token",
|
|
});
|
|
}
|
|
|
|
const accessToken = tokenData.access_token;
|
|
const expiresAt = new Date(Date.now() + 3600 * 1000).toISOString();
|
|
|
|
const credentials = {
|
|
clientId,
|
|
clientSecret,
|
|
accessToken,
|
|
expiresAt,
|
|
};
|
|
|
|
const mutation = `
|
|
mutation {
|
|
metafieldsSet(metafields: [
|
|
{
|
|
ownerId: "${shopId}"
|
|
namespace: "turn14"
|
|
key: "credentials"
|
|
type: "json"
|
|
value: "${JSON.stringify(credentials).replace(/"/g, '\\"')}"
|
|
}
|
|
]) {
|
|
metafields {
|
|
key
|
|
value
|
|
}
|
|
userErrors {
|
|
field
|
|
message
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
const saveRes = await admin.graphql(mutation);
|
|
const result = await saveRes.json();
|
|
|
|
if (result?.data?.metafieldsSet?.userErrors?.length) {
|
|
return json({
|
|
success: false,
|
|
error: result.data.metafieldsSet.userErrors[0].message,
|
|
});
|
|
}
|
|
|
|
return json({
|
|
success: true,
|
|
clientId,
|
|
clientSecret,
|
|
accessToken,
|
|
});
|
|
} catch (err) {
|
|
console.error("Turn14 token fetch failed:", err);
|
|
return json({
|
|
success: false,
|
|
error: "Network or unexpected error occurred",
|
|
});
|
|
}
|
|
};
|
|
|
|
export default function SettingsPage({ standalone = true }) {
|
|
const loaderData = useLoaderData();
|
|
const actionData = useActionData();
|
|
|
|
const savedCreds = loaderData?.creds || {};
|
|
const shopName = loaderData?.shopName || "Shop";
|
|
|
|
const [clientId, setClientId] = useState(
|
|
actionData?.clientId || savedCreds.clientId || ""
|
|
);
|
|
const [clientSecret, setClientSecret] = useState(
|
|
actionData?.clientSecret || savedCreds.clientSecret || ""
|
|
);
|
|
const displayToken = actionData?.accessToken || savedCreds.accessToken;
|
|
|
|
const content = (
|
|
<Layout>
|
|
<Layout.Section>
|
|
<Card sectioned>
|
|
<BlockStack gap="300">
|
|
<Text variant="bodyMd"><strong>Connected Shop:</strong> {shopName}</Text>
|
|
|
|
<Form method="post">
|
|
<BlockStack gap="200">
|
|
<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
|
|
/>
|
|
<Button submit primary>
|
|
Generate Access Token
|
|
</Button>
|
|
</BlockStack>
|
|
</Form>
|
|
|
|
{actionData?.error && (
|
|
<InlineError
|
|
message={`❌ ${actionData.error}`}
|
|
fieldID="client_id"
|
|
/>
|
|
)}
|
|
|
|
{displayToken && (
|
|
<BlockStack gap="100">
|
|
<Text variant="bodySm" fontWeight="semibold" tone="success">
|
|
✅ Access token:
|
|
</Text>
|
|
<code
|
|
style={{
|
|
background: "#f4f4f4",
|
|
padding: "10px",
|
|
display: "block",
|
|
wordWrap: "break-word",
|
|
borderRadius: "4px",
|
|
fontFamily: "monospace",
|
|
}}
|
|
>
|
|
{displayToken}
|
|
</code>
|
|
</BlockStack>
|
|
)}
|
|
</BlockStack>
|
|
</Card>
|
|
</Layout.Section>
|
|
</Layout>
|
|
);
|
|
|
|
return standalone ? <Page title="Turn14 API Settings">{content}</Page> : content;
|
|
}
|
|
*/ |