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 <noreply@anthropic.com>
This commit is contained in:
metatroncubeswdev 2026-10-15 13:15:36 -04:00
parent aacd9a8e06
commit ee8056efc2
4 changed files with 13 additions and 4 deletions

View File

@ -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 (
<s-page heading={`Today, ${niceDate(d.today, true)}`}>
<s-button slot="primary-action" href="/app/bookings">View bookings</s-button>
<SetupGuide setup={d.setup} themeEditor={d.themeEditor} />
<SetupGuide setup={d.setup} themeEditor={d.themeEditor} shippingSettings={d.shippingSettings} />
{overLimit && (
<s-banner tone="warning" heading="You've reached this month's Starter limit">
<s-paragraph>
@ -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",

View File

@ -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;
});
}

View File

@ -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));
}

View File

@ -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; }