backend_shopify_data4autos/app/routes/app.billingsuccess.jsx
2025-10-06 03:49:49 +00:00

378 lines
12 KiB
JavaScript
Raw 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.

// app/routes/app.billing.success.jsx
import React, { useMemo } from "react";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import {
Page,
Layout,
Card,
BlockStack,
InlineStack,
Text,
Badge,
Divider,
Button,
Banner,
Box,
} from "@shopify/polaris";
import { TitleBar } from "@shopify/app-bridge-react";
import { authenticate } from "../shopify.server";
/** ===========================
* LOADER
* =========================== */
export const loader = async ({ request }) => {
const { admin, session } = await authenticate.admin(request);
const shop = session.shop;
const shopDomain = shop.split(".")[0];
// Pull richer subscription details: interval + price
const resp = await admin.graphql(`
query ActiveSubForSuccess {
currentAppInstallation {
activeSubscriptions {
id
name
status
trialDays
createdAt
currentPeriodEnd
test
lineItems {
plan {
appRecurringPricingDetails {
interval
price { amount currencyCode }
}
}
}
}
}
}
`);
const result = await resp.json();
const subscription =
result?.data?.currentAppInstallation?.activeSubscriptions?.[0] || null;
// Detect recent activation (today or last 2 days)
let recentActivation = false;
if (subscription?.createdAt) {
const created = new Date(subscription.createdAt);
const now = new Date();
const diffMs = now.getTime() - created.getTime();
const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1000;
recentActivation =
diffMs >= 0 &&
diffMs <= TWO_DAYS_MS &&
(subscription.status === "ACTIVE" || subscription.status === "TRIAL");
}
return json({ subscription, shop, shopDomain, recentActivation });
};
/** ===========================
* HELPERS
* =========================== */
function formatDate(d) {
if (!d) return "N/A";
return new Date(d).toLocaleString(undefined, {
year: "numeric",
month: "short",
day: "numeric",
});
}
function getRecurringLine(subscription) {
const items = subscription?.lineItems || [];
for (const li of items) {
const r = li?.plan?.appRecurringPricingDetails;
if (r) return r;
}
return null;
}
function intervalToLabel(interval) {
switch (interval) {
case "ANNUAL":
return "Annual";
case "EVERY_30_DAYS":
return "Monthly";
default:
return interval || "N/A";
}
}
/** ===========================
* PAGE
* =========================== */
export default function BillingSuccess() {
const { subscription, shop, shopDomain, recentActivation } = useLoaderData();
const recurring = getRecurringLine(subscription);
const cadenceLabel = intervalToLabel(recurring?.interval);
const priceText =
recurring?.price?.amount && recurring?.price?.currencyCode
? `${recurring.price.amount} ${recurring.price.currencyCode}`
: "N/A";
// Trial end
const trialEndStr = useMemo(() => {
if (!subscription?.trialDays || !subscription?.createdAt) return "N/A";
const start = new Date(subscription.createdAt);
const end = new Date(start);
end.setDate(end.getDate() + subscription.trialDays);
return formatDate(end);
}, [subscription?.trialDays, subscription?.createdAt]);
// Trial days left (if still on TRIAL)
const trialDaysLeft = useMemo(() => {
if (
subscription?.status !== "TRIAL" ||
!subscription?.trialDays ||
!subscription?.createdAt
)
return null;
const start = new Date(subscription.createdAt);
const trialEnd = new Date(start);
trialEnd.setDate(trialEnd.getDate() + subscription.trialDays);
const now = new Date();
const left = Math.ceil(
(trialEnd.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
);
return Math.max(0, left);
}, [subscription?.status, subscription?.trialDays, subscription?.createdAt]);
const showError = !subscription;
const nextRenewal = formatDate(subscription?.currentPeriodEnd);
const createdAt = formatDate(subscription?.createdAt);
return (
<Page>
<TitleBar title="Payment & Subscription" />
<Layout>
<Layout.Section>
{showError ? (
<Banner title="No active subscription found" tone="critical">
<p>
We couldnt find an active subscription for this shop. If you just
approved billing, it may not be visible yet. You can proceed to the
billing screen to create or refresh your subscription.
</p>
<InlineStack gap="400" wrap={false}>
<Button url={`/app`} variant="primary">
Go to Dashboard
</Button>
<Button url={`/app`} variant="secondary">
Proceed to Billing
</Button>
</InlineStack>
</Banner>
) : recentActivation ? (
<Banner title="🎉 Subscription Activated" tone="success">
<p>
Congratulations! Your plan is now active. Youre all set to sync brands,
build collections, and automate your Turn14 catalog.
</p>
<p style={{ marginTop: 8 }}>
Activated: <strong>{createdAt}</strong> &nbsp;&nbsp; Status:{" "}
<Badge tone="success">{subscription.status}</Badge>
</p>
<InlineStack gap="400" wrap={false}>
<Button
url={`https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app`}
target="_blank"
external
variant="primary"
>
Open App Dashboard
</Button>
<Button
url={`https://admin.shopify.com/store/${shopDomain}/settings/billing`}
target="_blank"
external
variant="secondary"
>
View Billing & Invoices
</Button>
</InlineStack>
</Banner>
) : (
<Banner title="Your plan is active" tone="info">
<p>
Your subscription is active. Below are the full details of your plan,
trial, and renewal.
</p>
</Banner>
)}
</Layout.Section>
<Layout.Section>
<Card padding="500">
<BlockStack gap="500">
<Text variant="headingLg" as="h2">
Plan Overview
</Text>
<Box
padding="400"
style={{
display: "grid",
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
gap: "1rem",
}}
>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="200">
<Text as="p" tone="subdued">
Plan Name
</Text>
<Text as="p" variant="headingMd" fontWeight="bold">
{subscription?.name || "Starter Sync"}
</Text>
</BlockStack>
</Card>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="200">
<Text as="p" tone="subdued">
Billing Cadence
</Text>
<Text as="p" variant="headingMd" fontWeight="bold">
{cadenceLabel}
</Text>
</BlockStack>
</Card>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="200">
<Text as="p" tone="subdued">
Price
</Text>
<Text as="p" variant="headingMd" fontWeight="bold">
{priceText}
</Text>
</BlockStack>
</Card>
</Box>
<Divider />
<Text variant="headingMd" as="h3">
Billing & Trial
</Text>
<Box
padding="400"
style={{
display: "grid",
gridTemplateColumns: "repeat(4, minmax(0, 1fr))",
gap: "1rem",
}}
>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="150">
<Text tone="subdued">Status</Text>
<InlineStack gap="200">
<Badge
tone={subscription?.status === "ACTIVE" ? "success" : "attention"}
>
{subscription?.status || "N/A"}
</Badge>
{subscription?.test && <Badge tone="warning">Test</Badge>}
</InlineStack>
</BlockStack>
</Card>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="150">
<Text tone="subdued">Trial</Text>
<Text fontWeight="bold">
{subscription?.trialDays ? `${subscription.trialDays} days` : "N/A"}
{trialDaysLeft != null && <span> {trialDaysLeft} day(s) left</span>}
</Text>
</BlockStack>
</Card>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="150">
<Text tone="subdued">Trial Ends</Text>
<Text fontWeight="bold">{trialEndStr}</Text>
</BlockStack>
</Card>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="150">
<Text tone="subdued">Next Renewal / Period End</Text>
<Text fontWeight="bold">{nextRenewal}</Text>
</BlockStack>
</Card>
</Box>
<Divider />
<Text variant="headingMd" as="h3">
Subscription Metadata
</Text>
<Box
padding="400"
style={{
display: "grid",
gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
gap: "1rem",
}}
>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="150">
<Text tone="subdued">Subscription ID</Text>
<Text fontWeight="bold" breakWord>
{subscription?.id || "N/A"}
</Text>
</BlockStack>
</Card>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="150">
<Text tone="subdued">Created / Activated</Text>
<Text fontWeight="bold">{createdAt}</Text>
</BlockStack>
</Card>
<Card padding="400" background="bg-surface-secondary">
<BlockStack gap="150">
<Text tone="subdued">Shop</Text>
<Text fontWeight="bold">{shop}</Text>
</BlockStack>
</Card>
</Box>
<Divider />
<InlineStack gap="400" align="center">
<Button
url={`https://admin.shopify.com/store/${shopDomain}/apps/d4a-turn14/app`}
target="_blank"
external
variant="primary"
>
Go to App Dashboard
</Button>
<Button
url={`https://admin.shopify.com/store/${shopDomain}/settings/billing`}
target="_blank"
external
variant="secondary"
>
Manage Billing / Invoices
</Button>
<Button url="mailto:support@data4autos.com" variant="tertiary">
Contact Support
</Button>
</InlineStack>
</BlockStack>
</Card>
</Layout.Section>
</Layout>
</Page>
);
}