From ee8056efc2b989f2bcf26b2c52dbed9b1cd0409a Mon Sep 17 00:00:00 2001 From: metatroncubeswdev Date: Thu, 15 Oct 2026 13:15:36 -0400 Subject: [PATCH] fix: install race, silent Admin API errors, widget width; pickup setup hint - ensureShop: concurrent installs reuse the row created by the other request (P2002) - order tagging and booking metafield log Shopify userErrors instead of failing silently - widget keeps a readable 760px width in full-width theme sections - setup guide links to Shopify's pickup settings so checkout offers pickup Co-Authored-By: Claude Opus 5.5 --- app/routes/app._index.tsx | 9 +++++---- app/services/config.server.ts | 4 ++++ app/services/orders.server.ts | 2 ++ extensions/datewise-widget/assets/datewise.css | 2 ++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/routes/app._index.tsx b/app/routes/app._index.tsx index beb3703..64767ee 100644 --- a/app/routes/app._index.tsx +++ b/app/routes/app._index.tsx @@ -60,6 +60,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { embed: `https://${cfg.shop.domain}/admin/themes/current/editor?context=apps&activateAppId=${process.env.SHOPIFY_API_KEY}/embed`, block: `https://${cfg.shop.domain}/admin/themes/current/editor?template=cart&addAppBlockId=${process.env.SHOPIFY_API_KEY}/scheduler&target=newAppsSection`, }, + shippingSettings: `https://${cfg.shop.domain}/admin/settings/shipping`, }; }; @@ -69,7 +70,7 @@ export default function Dashboard() { return ( View bookings - + {overLimit && ( @@ -144,15 +145,15 @@ function Kpi({ label, value, href, alert }: { label: string; value: number; href type Setup = { anyLocation: boolean; widgetLive: boolean; guardOn: boolean }; -function SetupGuide({ setup, themeEditor }: { setup: Setup; themeEditor: { embed: string; block: string } }) { +function SetupGuide({ setup, themeEditor, shippingSettings }: { setup: Setup; themeEditor: { embed: string; block: string }; shippingSettings: string }) { const steps = [ { id: "location", done: setup.anyLocation, title: "Turn on a pickup or delivery location", - body: "Your Shopify locations were imported switched off. Check the opening hours, time slots and capacity, then turn one on.", + body: "Your Shopify locations were imported switched off. Check the opening hours, time slots and capacity, then turn one on. For store pickup, also turn on Local pickup for that location in Shopify's Shipping and delivery settings, so checkout offers it.", action: { label: "Open Locations & hours", href: "/app/locations" }, - secondary: null, + secondary: { label: "Shopify pickup settings", href: shippingSettings }, }, { id: "widget", diff --git a/app/services/config.server.ts b/app/services/config.server.ts index ed2223d..e42901f 100644 --- a/app/services/config.server.ts +++ b/app/services/config.server.ts @@ -128,6 +128,7 @@ export async function ensureShop(domain: string, admin: AdminGraphql) { return existing; } const weekdays = Object.fromEntries([1, 2, 3, 4, 5, 6].map((d) => [d, [DEFAULT_HOURS]])); + // Two requests can arrive together right after install; if the other one created the row first, use it. return prisma.shop.create({ data: { domain, @@ -154,5 +155,8 @@ export async function ensureShop(domain: string, admin: AdminGraphql) { })), }, }, + }).catch(async (e: unknown) => { + if ((e as { code?: string })?.code === "P2002") return prisma.shop.findUniqueOrThrow({ where: { domain } }); + throw e; }); } diff --git a/app/services/orders.server.ts b/app/services/orders.server.ts index 2ec15c4..3b0c8c8 100644 --- a/app/services/orders.server.ts +++ b/app/services/orders.server.ts @@ -138,6 +138,7 @@ async function tagOrder(admin: AdminGraphql | undefined, id: string, tags: strin await admin .graphql(`#graphql mutation($id: ID!, $tags: [String!]!) { tagsAdd(id: $id, tags: $tags) { userErrors { message } } }`, { variables: { id, tags } }) + .then(async (r) => { const j = (await r.json()) as { data?: { tagsAdd?: { userErrors: { message: string }[] } } }; const ue = j.data?.tagsAdd?.userErrors ?? []; if (ue.length) console.error("tagsAdd userErrors", id, ue); }) .catch((e) => console.error("tagsAdd failed", e)); } @@ -148,6 +149,7 @@ async function writeBookingMetafield(admin: AdminGraphql | undefined, ownerId: s mutation($m: [MetafieldsSetInput!]!) { metafieldsSet(metafields: $m) { userErrors { message } } }`, { variables: { m: [{ ownerId, namespace: "datewise", key: "booking", type: "json", value: JSON.stringify(value) }] }, }) + .then(async (r) => { const j = (await r.json()) as { data?: { metafieldsSet?: { userErrors: { message: string }[] } } }; const ue = j.data?.metafieldsSet?.userErrors ?? []; if (ue.length) console.error("metafieldsSet userErrors", ownerId, ue); }) .catch((e) => console.error("metafieldsSet failed", e)); } diff --git a/extensions/datewise-widget/assets/datewise.css b/extensions/datewise-widget/assets/datewise.css index 84f4735..46561cb 100644 --- a/extensions/datewise-widget/assets/datewise.css +++ b/extensions/datewise-widget/assets/datewise.css @@ -25,6 +25,8 @@ min-width: 0; text-align: left; } +/* Some themes put app blocks in a full-width section: keep the scheduler a readable width. */ +.datewise:not(.datewise--drawer) { max-width: 760px; margin-inline: auto; } .datewise--drawer { margin: 0 0 12px; padding: 14px; font-size: 0.9em; gap: 12px; } .datewise * { box-sizing: border-box; } .datewise button { font: inherit; color: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }