2025-10-06 03:49:49 +00:00

365 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from "react";
import { json } from "@remix-run/node";
import { useLoaderData, useActionData, useSubmit } from "@remix-run/react";
import {
Page,
Layout,
Card,
BlockStack,
Text,
Badge,
InlineStack,
Image,
Divider,
Button,
Modal,
TextField,
Box,
Banner,
} from "@shopify/polaris";
import { TitleBar } from "@shopify/app-bridge-react";
import data4autosLogo from "../assets/data4autos_logo.png"; // ensure this exists
import turn14DistributorLogo from "../assets/turn14-logo.png";
import { authenticate } from "../shopify.server";
import { Form } from "@remix-run/react";
/* ===========================
LOADER: check subscription
=========================== */
export const loader = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const resp = await admin.graphql(`
query {
currentAppInstallation {
activeSubscriptions {
id
status
trialDays
createdAt
currentPeriodEnd
}
}
}
`);
const result = await resp.json();
const subscription =
result?.data?.currentAppInstallation?.activeSubscriptions?.[0] || null;
const { session } = await authenticate.admin(request);
const shop = session.shop;
if (!subscription) {
return json({ redirectToBilling: true, subscription: null, shop });
}
if (subscription.status !== "ACTIVE" && subscription.status !== "TRIAL") {
return json({ redirectToBilling: true, subscription, shop });
}
return json({ redirectToBilling: false, subscription, shop });
};
/* ===========================
ACTION: create subscription
=========================== */
export const action = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const createRes = await admin.graphql(`
mutation {
appSubscriptionCreate(
name: "Pro Plan"
returnUrl: "https://your-app.com/after-billing"
lineItems: [
{
plan: {
appRecurringPricingDetails: {
price: { amount: 79, currencyCode: USD }
interval: EVERY_30_DAYS
}
}
}
]
trialDays: 7
test: true
) {
confirmationUrl
appSubscription { id status trialDays }
userErrors { field message }
}
}
`);
const data = await createRes.json();
const url = data?.data?.appSubscriptionCreate?.confirmationUrl;
const userErrors = data?.data?.appSubscriptionCreate?.userErrors || [];
const topLevelErrors = data?.errors || [];
if (!url || userErrors.length || topLevelErrors.length) {
return json(
{
errors: [
"Failed to create subscription.",
...userErrors.map((e) => e.message),
...topLevelErrors.map((e) => e.message || String(e)),
],
},
{ status: 400 }
);
}
return json({ confirmationUrl: url });
};
/* ===========================
PAGE
=========================== */
export default function Index() {
const actionData = useActionData();
const loaderData = useLoaderData();
const submit = useSubmit();
const [activeModal, setActiveModal] = useState(false);
const subscription = loaderData?.subscription;
const shop = loaderData?.shop;
const shopDomain = (shop || "").split(".")[0];
const items = [
{
icon: "⚙️",
text: "Manage API settings",
link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/settings`,
},
{
icon: "🏷️",
text: "Browse and import available brands",
link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/brands`,
},
{
icon: "📦",
text: "Sync brand collections to Shopify",
link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/managebrand`,
},
{
icon: "🔐",
text: "Handle secure Turn14 login credentials",
link: `https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app/help`,
},
];
const openModal = () => setActiveModal(true);
const closeModal = () => setActiveModal(false);
const hasConfirmationUrl = Boolean(actionData?.confirmationUrl);
const errors = actionData?.errors || [];
return (
<Page>
<TitleBar title="Data4Autos Turn14 Integration" />
<Layout>
<Layout.Section>
<Card padding="500">
<BlockStack gap="400">
<BlockStack gap="400" align="center">
<Text variant="headingLg" as="h1" alignment="center">
Welcome to your Turn14 Dashboard
</Text>
<InlineStack gap="800" align="center" blockAlign="center">
<Image source={data4autosLogo} alt="Data4Autos Logo" width={120} />
<Image
source={turn14DistributorLogo}
alt="Turn14 Distributors Logo"
width={200}
/>
</InlineStack>
</BlockStack>
<Divider />
<BlockStack gap="800">
<Text variant="headingMd" as="h3">
🚀 Data4Autos Turn14 Integration gives you the power to sync
product brands, manage collections, and automate catalog setup
directly from Turn14 to your Shopify store.
</Text>
<InlineStack gap="400">
<Text as="h3" variant="headingLg" fontWeight="medium">
Use the left sidebar to:
</Text>
<Box
paddingBlockStart="800"
paddingBlockEnd="800"
style={{
display: "grid",
gridTemplateColumns: "repeat(4, 1fr)",
gap: "1rem",
}}
>
{items.map((item, index) => (
<Card key={index} padding="500" background="bg-surface-secondary">
<BlockStack align="center" gap="200">
<Text
as="p"
fontWeight="bold"
alignment="center"
tone="subdued"
variant="bodyMd"
>
<span style={{ fontSize: "2rem" }}>{item.icon}</span>
</Text>
{/* Real anchor to avoid double-URL issues */}
<a
href={item.link}
target="_blank"
rel="noopener noreferrer"
style={{ textDecoration: "none" }}
>
<Text as="h6" alignment="center" fontWeight="bold" variant="headingMd">
{item.text}
</Text>
</a>
</BlockStack>
</Card>
))}
</Box>
</InlineStack>
</BlockStack>
<Divider />
<BlockStack gap="400">
{/* <InlineStack align="center" gap="400">
<Badge tone="success">Status: Connected</Badge>
<Text tone="subdued">Shopify × Turn14</Text>
</InlineStack> */}
<Text tone="subdued" alignment="center">
Need help? Contact us at{" "}
<a href="mailto:support@data4autos.com">support@data4autos.com</a>
</Text>
<Button size="large" variant="primary" onClick={openModal} fullWidth>
{loaderData?.redirectToBilling
? "Proceed to Billing"
: "View Subscription Details"}
</Button>
</BlockStack>
</BlockStack>
</Card>
</Layout.Section>
</Layout>
{/* ===========================
MODAL
=========================== */}
<Modal
open={activeModal}
onClose={closeModal}
title="Subscription Details"
primaryAction={
hasConfirmationUrl
? undefined // once we have URL, we swap primary action out (we show a link button instead)
: {
content: "Proceed to Billing",
onAction: () => {
const form = document.getElementById("billing-form");
submit(form);
},
}
}
secondaryActions={[{ content: "Close", onAction: closeModal }]}
>
<Form id="billing-form" method="post">
<Modal.Section>
<BlockStack gap="300">
{errors.length > 0 && (
<Banner title="Couldnt create subscription" tone="critical">
<ul style={{ margin: 0, paddingLeft: "1rem" }}>
{errors.map((e, i) => (
<li key={i}>{e}</li>
))}
</ul>
</Banner>
)}
{!hasConfirmationUrl && (
<>
<TextField
label="Subscription Status"
value={subscription ? subscription.status : "No active subscription"}
disabled
/>
<TextField
label="Trial Days Left"
value={
subscription?.trialDays
? `${subscription.trialDays} days left`
: "No trial available"
}
disabled
/>
<TextField
label="Subscription Plan"
value={subscription?.id ? "Pro Plan" : "Not subscribed"}
disabled
/>
<TextField
label="Trial Expiration Date"
value={
subscription?.currentPeriodEnd
? new Date(subscription.currentPeriodEnd).toLocaleDateString()
: "N/A"
}
disabled
/>
</>
)}
{hasConfirmationUrl && (
<BlockStack gap="300">
<Banner title="Almost there!" tone="success">
Click the button below to open Shopifys billing confirmation in a
new tab. If it doesnt open, copy the link and open it manually.
</Banner>
{/* Polaris Button-as-link opens absolute URL in a new tab */}
<Button
url={actionData.confirmationUrl}
target="_blank"
external
onClick={() => {
// optional: close the modal after user clicks
setActiveModal(false);
}}
variant="primary"
size="large"
>
Open Billing Confirmation
</Button>
{/* Plain anchor fallback (optional) */}
<a
href={actionData.confirmationUrl}
target="_blank"
rel="noopener noreferrer"
style={{ wordBreak: "break-all" }}
>
{actionData.confirmationUrl}
</a>
</BlockStack>
)}
</BlockStack>
</Modal.Section>
</Form>
</Modal>
</Page>
);
}