import React, { useMemo, useState } from "react"; import { json } from "@remix-run/node"; import { useLoaderData, useActionData, useSubmit, Form } from "@remix-run/react"; import { Page, Layout, Card, BlockStack, Text, InlineStack, Image, Divider, Button, Modal, TextField, Box, Banner, ChoiceList, } 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"; /* =========================== PRICING (single source of truth) =========================== */ const PLAN_NAME = "Starter Sync"; const MONTHLY_AMOUNT = 79; // USD const ANNUAL_AMOUNT = 790; // USD (≈ 2 months off) const TRIAL_DAYS = 14; /* =========================== 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 && result.data && result.data.currentAppInstallation && result.data.currentAppInstallation.activeSubscriptions && 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 (Monthly or Annual) =========================== */ export const action = async ({ request }) => { const { admin } = await authenticate.admin(request); const form = await request.formData(); const rawCadence = form.get("cadence"); const cadence = rawCadence === "ANNUAL" ? "ANNUAL" : "MONTHLY"; const interval = cadence === "ANNUAL" ? "ANNUAL" : "EVERY_30_DAYS"; const amount = cadence === "ANNUAL" ? ANNUAL_AMOUNT : MONTHLY_AMOUNT; const createRes = await admin.graphql(` mutation { appSubscriptionCreate( name: "${PLAN_NAME}" returnUrl: "https://your-app.com/after-billing" lineItems: [ { plan: { appRecurringPricingDetails: { price: { amount: ${amount}, currencyCode: USD } interval: ${interval} } } } ] trialDays: ${TRIAL_DAYS} test: false ) { confirmationUrl appSubscription { id status trialDays } userErrors { field message } } } `); const data = await createRes.json(); const url = data && data.data && data.data.appSubscriptionCreate ? data.data.appSubscriptionCreate.confirmationUrl : null; const userErrors = (data && data.data && data.data.appSubscriptionCreate && 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 || null; const shop = loaderData?.shop || ""; // Cadence selection for the billing action const [cadence, setCadence] = useState("MONTHLY"); const hasConfirmationUrl = Boolean(actionData && actionData.confirmationUrl); const errors = (actionData && actionData.errors) || []; 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); /* =========================== Helpers & preview model =========================== */ const formatDate = (d) => new Date(d).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); // We show "preview" values while the user is picking a cadence and before they confirm billing. // After approval + reload, we show Shopify's real values from loaderData. const isPreview = !subscription || loaderData?.redirectToBilling || !hasConfirmationUrl; // Preview trial end is TRIAL_DAYS from "now" (or from subscription.createdAt if present) const previewBase = subscription?.createdAt ? new Date(subscription.createdAt) : new Date(); const previewTrialEnd = new Date(previewBase); previewTrialEnd.setDate(previewTrialEnd.getDate() + TRIAL_DAYS); // Top fields (readOnly) will reflect selection in preview mode const displayStatus = subscription ? subscription.status : "Not active — will be created at checkout"; const displayPlan = `${PLAN_NAME} — ${cadence === "ANNUAL" ? "Annual" : "Monthly"}`; const displayNextRenewal = isPreview ? `${formatDate(previewTrialEnd)} (after ${TRIAL_DAYS}-day trial)` : (subscription?.currentPeriodEnd ? formatDate(subscription.currentPeriodEnd) : "N/A"); // Compute trial days left accurately (if TRIAL) const trialDaysLeft = useMemo(() => { if (!subscription?.trialDays || !subscription?.createdAt) return null; const created = new Date(subscription.createdAt); const trialEnd = new Date(created); trialEnd.setDate(trialEnd.getDate() + subscription.trialDays); const now = new Date(); const msLeft = trialEnd.getTime() - now.getTime(); const daysLeft = Math.ceil(msLeft / (1000 * 60 * 60 * 24)); return Math.max(0, daysLeft); }, [subscription?.trialDays, subscription?.createdAt]); return ( Welcome to your Turn14 Dashboard Data4Autos Logo Turn14 Distributors Logo 🚀 Data4Autos Turn14 Integration gives you the power to sync product brands, manage collections, and automate catalog setup directly from Turn14 to your Shopify store. Use the left sidebar to: {items.map((item, index) => ( {item.icon} {item.text} ))} Need help? Contact us at{" "} support@data4autos.com {/* =========================== MODAL =========================== */} { const form = document.getElementById("billing-form"); if (form && typeof form.submit === "function") { const hidden = document.getElementById("cadence-field"); if (hidden) hidden.value = cadence; submit(form); } }, } } secondaryActions={[{ content: "Close", onAction: closeModal }]} >
{/* Keep hidden input for server action compatibility */} {errors.length > 0 && (
    {errors.map((e, i) => (
  • {e}
  • ))}
)} {!hasConfirmationUrl && ( <> {/* ===== Top read-only fields (now preview-aware) ===== */} {/* ===== Live preview of the exact price that will apply after trial ===== */} Your selection {/* ===== Radio-style plan selector ===== */} { const next = Array.isArray(selected) && selected[0] ? selected[0] : "MONTHLY"; setCadence(next); const hidden = document.getElementById("cadence-field"); if (hidden) hidden.value = next; }} allowMultiple={false} /> )} {hasConfirmationUrl && ( Click the button below to open Shopify’s billing confirmation in a new tab. If it doesn’t open, copy the link and open it manually. {actionData.confirmationUrl} )}
); }