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 { authenticate } from "../shopify.server"; import { gql } from "../lib/shop.server"; import { fetchActiveSubscription, partnerApiConfigured } from "../lib/partner.server"; 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 reinstall after the old one was cancelled) // goes to Shopify's plan selection page. 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 { shop } = await gql(admin, `#graphql query OfferlystShopId { shop { id } }`); sub = await fetchActiveSubscription(shop.id, { fresh }); } catch (e) { console.error("subscription check failed", e); } if (sub === null) { const store = session.shop.replace(".myshopify.com", ""); const handle = process.env.SHOPIFY_APP_HANDLE || "offerlyst"; throw redirect(`https://admin.shopify.com/store/${store}/charges/${handle}/pricing_plans`, { target: "_top" }); } } // eslint-disable-next-line no-undef return { apiKey: process.env.SHOPIFY_API_KEY || "" }; }; export default function App() { const { apiKey } = useLoaderData(); return ( Home Offers Festival templates Analytics Settings Plans ); } // Shopify needs React Router to catch some thrown responses, so that their headers are included in the response. export function ErrorBoundary() { return boundary.error(useRouteError()); } export const headers: HeadersFunction = (headersArgs) => { return boundary.headers(headersArgs); };