2025-08-07 06:22:27 +00:00

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;
}
*/