metatrondelivery/app/routes/apps.scheduling.locations.tsx
metatroncubeswdev b29962bf9c
Some checks failed
CI / Lint, Unit & Integration Tests (push) Has been cancelled
feat: finish multi-pin pickup end-to-end; fix checkout-datetime followups
Multi-pin pickup selection (study §3.4) — previously a standalone unused
endpoint + TODO comments:
- app/services/pickup-locations.server.ts: pure filterPickupLocationIds +
  I/O resolvePickupLocations (active + has a PICKUP slot template +
  ProductRule allowedLocationIds + inventory exclusion).
- resolveAvailabilityRequest returns pickupLocations[] on method=PICKUP and
  defaults the active location to the first eligible pickup point.
- apps.scheduling.locations.tsx refactored onto the shared resolver
  (was a second copy of the logic).
- Storefront widget: pickup-location chooser (name + address, optional
  multi-pin Google map) when >1 eligible point and none block-configured;
  picking one re-requests availability for that location. New styles +
  widget.choose_pickup_location locale (en + fr).
- Checkout extension Checkout.jsx: same chooser before the date list.
- tests/unit/pickup-locations.test.ts (5 cases); suite 161 green.

checkout-datetime follow-up fixes from the 808a3b7 review:
- typescript devDep ^7.0.2 -> ^5.6.3 (there is no typescript@7 on npm).
- Deleted dead shopify.d.ts (Preact-global shim, unused after the React
  rewrite) and dropped it from tsconfig include.

Also stages the CLI-written `uid` lines in the checkout-datetime and
payment-customization extension tomls.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 16:47:59 -04:00

49 lines
2.1 KiB
TypeScript

import type { LoaderFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import db from "../db.server";
import {
resolveProductRuleConstraints,
resolveProductRefs,
productRefsFromCartLines,
} from "../services/product-rules.server";
import { parseCartLinesParam, parseProductIdsParam, parseGidListParam } from "../lib/cart-rule-params";
import { resolvePickupLocations } from "../services/pickup-locations.server";
// Standalone list of eligible pickup points (study §3.4 multi-pin pickup).
// The same data is also returned inline on `method=PICKUP` availability
// responses (`pickupLocations`) — this endpoint stays for callers that just
// want the list without asking for a specific location's calendar. Shared
// resolution logic lives in pickup-locations.server.ts so the two paths
// can't drift.
export const loader = async ({ request }: LoaderFunctionArgs) => {
const { session, admin } = await authenticate.public.appProxy(request);
if (!session) {
return Response.json({ error: "Shop not found" }, { status: 404 });
}
const url = new URL(request.url);
const productIds = parseProductIdsParam(url.searchParams.get("productIds"));
const productRefs = admin && productIds.length > 0 ? await resolveProductRefs(admin, productIds) : [];
const cartProductRefs = [
...productRefs,
...productRefsFromCartLines(parseCartLinesParam(url.searchParams.get("cartLines"))),
];
const productRules = await db.productRule.findMany({ where: { shopDomain: session.shop, active: true } });
const ruleConstraints = resolveProductRuleConstraints(productRules, cartProductRefs);
// A rule that forbids PICKUP for this cart ⇒ no pickup points at all.
if (ruleConstraints.allowedMethods != null && !ruleConstraints.allowedMethods.includes("PICKUP")) {
return Response.json({ locations: [] });
}
const locations = await resolvePickupLocations(session.shop, {
allowedLocationIds: ruleConstraints.allowedLocationIds,
admin: admin ?? undefined,
productVariantGids: parseGidListParam(url.searchParams.get("variantIds")),
});
return Response.json({ locations });
};