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:
parent
aacd9a8e06
commit
ee8056efc2
@ -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",
|
||||
|
||||
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user