import { useEffect, useState } from "react"; import type { ActionFunctionArgs, HeadersFunction, LoaderFunctionArgs } from "react-router"; import { useActionData, useLoaderData, useNavigation, useSubmit } from "react-router"; import { useAppBridge } from "@shopify/app-bridge-react"; import { boundary } from "@shopify/shopify-app-react-router/server"; import { authenticate } from "../shopify.server"; import db from "../db.server"; import { ensureShop } from "../lib/shop.server"; import { updateCombinations } from "../lib/publish.server"; import { CONFIG_MAX_BYTES, settingsOf, type ShopSettings } from "../lib/compile"; type Settings = ReturnType & { menuLinked?: boolean }; export const loader = async ({ request }: LoaderFunctionArgs) => { const { admin, session } = await authenticate.admin(request); const shop = await ensureShop(admin, session.shop); const raw = (shop.settings || {}) as ShopSettings & { menuLinked?: boolean }; return { settings: { ...settingsOf(raw), menuLinked: !!raw.menuLinked } as Settings, configBytes: shop.configBytes, storefrontBytes: shop.storefrontBytes, max: CONFIG_MAX_BYTES, }; }; export const action = async ({ request }: ActionFunctionArgs) => { const { admin, session } = await authenticate.admin(request); const shop = await ensureShop(admin, session.shop); const s = JSON.parse(String((await request.formData()).get("payload"))) as Settings; const clean: Settings = { excludeTag: (s.excludeTag || "").trim().slice(0, 40), excludeSubscriptions: !!s.excludeSubscriptions, excludeGiftCards: !!s.excludeGiftCards, bxgyAllocation: s.bxgyAllocation === "get_units_only" ? "get_units_only" : "all_units", combineProduct: !!s.combineProduct, combineOrder: !!s.combineOrder, combineShipping: !!s.combineShipping, afterAdd: s.afterAdd === "drawer" ? "drawer" : "cart", accent: /^#[0-9a-fA-F]{6}$/.test(s.accent || "") ? s.accent : "", radius: Math.min(Math.max(Math.round(Number(s.radius) || 0), 0), 24), menuLinked: !!s.menuLinked, }; await db.shop.update({ where: { id: shop.id }, data: { settings: clean } }); const outcome = await updateCombinations(admin, session.shop); return { ok: outcome.ok, error: outcome.error }; }; export default function SettingsPage() { const { settings, configBytes, storefrontBytes, max } = useLoaderData(); const result = useActionData(); const [s, setS] = useState(settings); const submit = useSubmit(); const nav = useNavigation(); const shopify = useAppBridge(); useEffect(() => { if (result?.ok) shopify.toast.show("Settings saved"); if (result?.error) shopify.toast.show(result.error, { isError: true }); }, [result, shopify]); const set = (k: K, v: Settings[K]) => setS((x) => ({ ...x, [k]: v })); const save = () => { const fd = new FormData(); fd.set("payload", JSON.stringify(s)); submit(fd, { method: "post" }); }; const pct = Math.round((configBytes / max) * 100); return ( Save set("excludeTag", e.currentTarget.value)} /> set("excludeSubscriptions", e.currentTarget.checked)} /> set("excludeGiftCards", e.currentTarget.checked)} /> set("combineProduct", e.currentTarget.checked)} /> set("combineOrder", e.currentTarget.checked)} /> set("combineShipping", e.currentTarget.checked)} /> set("bxgyAllocation", (e.currentTarget.values[0] as Settings["bxgyAllocation"]) || "all_units")}> Spread over all items of the deal (same total, matches the original module) Only on the "get" items (the free item shows as free) set("afterAdd", (e.currentTarget.values[0] as Settings["afterAdd"]) || "cart")}> Go to the cart page Open the theme's cart drawer (if the theme has one) set("accent", e.currentTarget.value)} /> set("radius", Number(e.currentTarget.value))} /> set("menuLinked", e.currentTarget.checked)} /> {configBytes.toLocaleString()} of {max.toLocaleString()} bytes ({pct}%)
85 ? "#c70a24" : "#29845a" }} />
All running offers share one Shopify discount so each item counts once. Storefront data: {storefrontBytes.toLocaleString()} bytes. ); } export const headers: HeadersFunction = (headersArgs) => boundary.headers(headersArgs);