import type { HeadersFunction, LoaderFunctionArgs } from "react-router"; import { Outlet, useLoaderData, useRouteError } from "react-router"; import { boundary } from "@shopify/shopify-app-react-router/server"; import { AppProvider } from "@shopify/shopify-app-react-router/react"; import prisma from "../db.server"; import { authenticate } from "../shopify.server"; import { fetchActiveSubscription, partnerApiConfigured, pricingUrl } from "../lib/partner.server"; import { bumpConfig } from "../services/config.server"; import { planFromSubscription } from "../services/plans"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { admin, redirect, session } = await authenticate.admin(request); // Shopify App Pricing: a shop without a plan (new install), or whose subscription was cancelled (uninstall, // then reinstall), goes to Shopify's plan selection page to approve a plan again. Otherwise the stored plan // follows the subscription. A Partner API failure never redirects a paying merchant. if (partnerApiConfigured()) { // the welcome link adds plan_handle after a plan is approved: skip the cache so the new plan shows at once const fresh = new URL(request.url).searchParams.has("plan_handle"); let sub: Awaited> | undefined; try { const r = await admin.graphql(`#graphql query DatewiseShopId { shop { id } }`); const { data } = (await r.json()) as { data: { shop: { id: string } } }; sub = await fetchActiveSubscription(data.shop.id, { fresh }); } catch (e) { console.error("[datewise] subscription check failed", e); } if (sub === null || sub?.cancelled) throw redirect(pricingUrl(session.shop), { target: "_top" }); if (sub) { const plan = planFromSubscription(sub.handles.join(" ")); const row = await prisma.shop.findUnique({ where: { domain: session.shop }, select: { id: true, plan: true } }); if (row && row.plan !== plan) { await prisma.shop.update({ where: { id: row.id }, data: { plan } }); await bumpConfig(row.id, session.shop); } } } return { apiKey: process.env.SHOPIFY_API_KEY || "" }; }; export default function App() { const { apiKey } = useLoaderData(); return ( Today Bookings Locations & hours Product rules Fresh stock Rush control Driver run sheet Why unavailable? Settings Plans ); } // Shopify needs React Router to catch some thrown responses so headers are included. export function ErrorBoundary() { return boundary.error(useRouteError()); } export const headers: HeadersFunction = (headersArgs) => boundary.headers(headersArgs);