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`,
|
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`,
|
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 (
|
return (
|
||||||
<s-page heading={`Today, ${niceDate(d.today, true)}`}>
|
<s-page heading={`Today, ${niceDate(d.today, true)}`}>
|
||||||
<s-button slot="primary-action" href="/app/bookings">View bookings</s-button>
|
<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 && (
|
{overLimit && (
|
||||||
<s-banner tone="warning" heading="You've reached this month's Starter limit">
|
<s-banner tone="warning" heading="You've reached this month's Starter limit">
|
||||||
<s-paragraph>
|
<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 };
|
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 = [
|
const steps = [
|
||||||
{
|
{
|
||||||
id: "location",
|
id: "location",
|
||||||
done: setup.anyLocation,
|
done: setup.anyLocation,
|
||||||
title: "Turn on a pickup or delivery location",
|
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" },
|
action: { label: "Open Locations & hours", href: "/app/locations" },
|
||||||
secondary: null,
|
secondary: { label: "Shopify pickup settings", href: shippingSettings },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "widget",
|
id: "widget",
|
||||||
|
|||||||
@ -128,6 +128,7 @@ export async function ensureShop(domain: string, admin: AdminGraphql) {
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
const weekdays = Object.fromEntries([1, 2, 3, 4, 5, 6].map((d) => [d, [DEFAULT_HOURS]]));
|
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({
|
return prisma.shop.create({
|
||||||
data: {
|
data: {
|
||||||
domain,
|
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
|
await admin
|
||||||
.graphql(`#graphql
|
.graphql(`#graphql
|
||||||
mutation($id: ID!, $tags: [String!]!) { tagsAdd(id: $id, tags: $tags) { userErrors { message } } }`, { variables: { id, tags } })
|
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));
|
.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 } } }`, {
|
mutation($m: [MetafieldsSetInput!]!) { metafieldsSet(metafields: $m) { userErrors { message } } }`, {
|
||||||
variables: { m: [{ ownerId, namespace: "datewise", key: "booking", type: "json", value: JSON.stringify(value) }] },
|
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));
|
.catch((e) => console.error("metafieldsSet failed", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,8 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
text-align: left;
|
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--drawer { margin: 0 0 12px; padding: 14px; font-size: 0.9em; gap: 12px; }
|
||||||
.datewise * { box-sizing: border-box; }
|
.datewise * { box-sizing: border-box; }
|
||||||
.datewise button { font: inherit; color: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
.datewise button { font: inherit; color: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user