import { useState } from "react"; import { type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node"; import { Form, useActionData, useLoaderData, useNavigation } from "@remix-run/react"; import { Page, Card, BlockStack, Text, Button, ChoiceList, TextField, Banner } from "@shopify/polaris"; import { TitleBar } from "@shopify/app-bridge-react"; import { authenticate } from "../shopify.server"; import db from "../db.server"; import { writeCheckoutSnapshot } from "../services/checkout-snapshot.server"; // Checkout enforcement scope for the Cart/Checkout Validation Function // (study ยง3.5 โ€” enforcement should be scopable, not all-or-nothing). The // Function can't read this DB, so every save re-writes the shop-metafield // snapshot it does read (checkout-snapshot.server.ts). const MODE_CHOICES = [ { label: "Every order must pick a slot", value: "all" }, { label: "Only orders containing a tagged product", value: "tagged" }, { label: "Never block checkout (widget still collects)", value: "off" }, ]; export const loader = async ({ request }: LoaderFunctionArgs) => { const { session } = await authenticate.admin(request); const shop = await db.shop.findUnique({ where: { shopDomain: session.shop }, select: { enforcementMode: true, enforcementTag: true }, }); return { mode: shop?.enforcementMode ?? "all", tag: shop?.enforcementTag ?? "" }; }; export const action = async ({ request }: ActionFunctionArgs) => { const { session, admin } = await authenticate.admin(request); const formData = await request.formData(); const mode = String(formData.get("mode") || "all"); const tag = String(formData.get("enforcementTag") || "").trim() || null; const errors: Record = {}; if (!["all", "tagged", "off"].includes(mode)) errors.mode = "Pick a valid option"; if (mode === "tagged" && !tag) errors.enforcementTag = "Enter the product tag that requires scheduling"; if (Object.keys(errors).length > 0) { return { ok: false as const, errors }; } await db.shop.upsert({ where: { shopDomain: session.shop }, create: { shopDomain: session.shop, enforcementMode: mode, enforcementTag: tag }, update: { enforcementMode: mode, enforcementTag: tag }, }); // Push the new config straight into the metafield the Function reads. await writeCheckoutSnapshot(admin, session.shop); return { ok: true as const, errors: {} as Record }; }; export default function EnforcementSettings() { const { mode: initialMode, tag: initialTag } = useLoaderData(); const actionData = useActionData(); const navigation = useNavigation(); const isSubmitting = navigation.state === "submitting"; const [mode, setMode] = useState(initialMode); const [tag, setTag] = useState(initialTag); return ( {actionData?.ok && ( )}
The storefront widget only collects a slot. The Cart & Checkout Validation Function is what actually blocks an order from completing without a valid one. This controls when it blocks. setMode(v[0])} /> {mode === "tagged" && ( )}
Live slot re-validation Every save here, plus every order and every slot/blackout edit, refreshes a capacity snapshot the Function reads at checkout. If a slot has filled up or been blacked out since the shopper picked it, checkout is blocked with a "no longer available" message โ€” not just when the slot attribute is missing.
); }