From b125c69b4af2675bdf9fd1e8468e326b2e396762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc?= Date: Sat, 18 Jul 2026 13:23:46 -0400 Subject: [PATCH 1/7] Add shopify/Discount app intent link snippets for React Router tutorial Generated from Shopify/discount-functions-testing#1408. Adds the coderef sources for the new 'Let Sidekick create and edit your discount' step in the build-ui-with-react-router tutorial: - docs/reference/shopify/{javascript,rust}/shopify.extension.toml: create + edit admin.app.intent.link extensions under the discount-function.app-intent-link tag - docs/reference/shopify/{javascript,rust}/create-intent-schema.json, edit-intent-schema.json, instructions.md Scoped to just these files; unrelated regen drift (formatting/deps) omitted. Assisted-By: devx/544990ff-9f89-4bd0-8c23-777a2f097188 --- .../javascript/create-intent-schema.json | 23 +++++++++++ .../javascript/edit-intent-schema.json | 28 +++++++++++++ .../shopify/javascript/instructions.md | 5 +++ .../shopify/javascript/shopify.extension.toml | 39 +++++++++++++++++++ .../shopify/rust/create-intent-schema.json | 23 +++++++++++ .../shopify/rust/edit-intent-schema.json | 28 +++++++++++++ docs/reference/shopify/rust/instructions.md | 5 +++ .../shopify/rust/shopify.extension.toml | 39 +++++++++++++++++++ 8 files changed, 190 insertions(+) create mode 100644 docs/reference/shopify/javascript/create-intent-schema.json create mode 100644 docs/reference/shopify/javascript/edit-intent-schema.json create mode 100644 docs/reference/shopify/javascript/instructions.md create mode 100644 docs/reference/shopify/rust/create-intent-schema.json create mode 100644 docs/reference/shopify/rust/edit-intent-schema.json create mode 100644 docs/reference/shopify/rust/instructions.md diff --git a/docs/reference/shopify/javascript/create-intent-schema.json b/docs/reference/shopify/javascript/create-intent-schema.json new file mode 100644 index 0000000..baf7d18 --- /dev/null +++ b/docs/reference/shopify/javascript/create-intent-schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify-intent.json", + "inputSchema": { + "type": "object", + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount.json", + "additionalProperties": true, + "properties": { + "functionId": { + "type": "string", + "description": "The deployed Function ID for this metafield-configured discount function.", + "matchValue": "YOUR_FUNCTION_ID" + } + } + }, + "outputSchema": { + "type": "object", + "properties": { + "id": { + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount/gid.json" + } + } + } +} diff --git a/docs/reference/shopify/javascript/edit-intent-schema.json b/docs/reference/shopify/javascript/edit-intent-schema.json new file mode 100644 index 0000000..a1cbdea --- /dev/null +++ b/docs/reference/shopify/javascript/edit-intent-schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify-intent.json", + "value": { + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount/gid.json", + "mapTo": "param", + "fieldName": "id" + }, + "inputSchema": { + "type": "object", + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount.json", + "additionalProperties": true, + "properties": { + "functionId": { + "type": "string", + "description": "The deployed Function ID for this metafield-configured discount function.", + "matchValue": "YOUR_FUNCTION_ID" + } + } + }, + "outputSchema": { + "type": "object", + "properties": { + "id": { + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount/gid.json" + } + } + } +} diff --git a/docs/reference/shopify/javascript/instructions.md b/docs/reference/shopify/javascript/instructions.md new file mode 100644 index 0000000..5a42c43 --- /dev/null +++ b/docs/reference/shopify/javascript/instructions.md @@ -0,0 +1,5 @@ +Use this app when the merchant wants to create or edit a metafield-configured discount powered by this app's Shopify Function. + +When invoking `create:shopify/Discount`, pass `functionId: "YOUR_FUNCTION_ID"` so Shopify resolves directly to this app's create flow. + +When invoking `edit:shopify/Discount`, pass the discount's GID (default to the canonical `gid://shopify/DiscountNode/{id}`) and `functionId: "YOUR_FUNCTION_ID"` so Shopify resolves the edit directly to this app. diff --git a/docs/reference/shopify/javascript/shopify.extension.toml b/docs/reference/shopify/javascript/shopify.extension.toml index e94d045..6dc704e 100644 --- a/docs/reference/shopify/javascript/shopify.extension.toml +++ b/docs/reference/shopify/javascript/shopify.extension.toml @@ -38,3 +38,42 @@ handle = "ui-multiclass-metafield-js" create = "/app/discount/:functionId/new" details = "/app/discount/:functionId/:id" # [END discount-function.ui-paths] + +# [START discount-function.app-intent-link] +# Register app intent links so Sidekick can create and edit discounts backed by +# this app's Function. Each admin.app.intent.link target maps a shopify/Discount +# action (create or edit) to a page in your app. +[[extensions]] +name = "Create discount" +description = "Create a metafield-configured discount powered by this app's Shopify Function." +handle = "discount-app-intent-link-create" +type = "admin_link" + + [[extensions.targeting]] + target = "admin.app.intent.link" + # Hardcode your deployed Function's ID (see "Set your Function ID" below). + url = "/app/discount/YOUR_FUNCTION_ID/new" + instructions = "./instructions.md" + + [[extensions.targeting.intents]] + type = "shopify/Discount" + action = "create" + schema = "./create-intent-schema.json" + +[[extensions]] +name = "Edit discount" +description = "Edit a metafield-configured discount powered by this app's Shopify Function." +handle = "discount-app-intent-link-edit" +type = "admin_link" + + [[extensions.targeting]] + target = "admin.app.intent.link" + # The discount's GID fills {id}; the Function ID stays hardcoded. + url = "/app/discount/YOUR_FUNCTION_ID/{id}" + instructions = "./instructions.md" + + [[extensions.targeting.intents]] + type = "shopify/Discount" + action = "edit" + schema = "./edit-intent-schema.json" +# [END discount-function.app-intent-link] diff --git a/docs/reference/shopify/rust/create-intent-schema.json b/docs/reference/shopify/rust/create-intent-schema.json new file mode 100644 index 0000000..baf7d18 --- /dev/null +++ b/docs/reference/shopify/rust/create-intent-schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify-intent.json", + "inputSchema": { + "type": "object", + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount.json", + "additionalProperties": true, + "properties": { + "functionId": { + "type": "string", + "description": "The deployed Function ID for this metafield-configured discount function.", + "matchValue": "YOUR_FUNCTION_ID" + } + } + }, + "outputSchema": { + "type": "object", + "properties": { + "id": { + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount/gid.json" + } + } + } +} diff --git a/docs/reference/shopify/rust/edit-intent-schema.json b/docs/reference/shopify/rust/edit-intent-schema.json new file mode 100644 index 0000000..a1cbdea --- /dev/null +++ b/docs/reference/shopify/rust/edit-intent-schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify-intent.json", + "value": { + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount/gid.json", + "mapTo": "param", + "fieldName": "id" + }, + "inputSchema": { + "type": "object", + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount.json", + "additionalProperties": true, + "properties": { + "functionId": { + "type": "string", + "description": "The deployed Function ID for this metafield-configured discount function.", + "matchValue": "YOUR_FUNCTION_ID" + } + } + }, + "outputSchema": { + "type": "object", + "properties": { + "id": { + "$ref": "https://extensions.shopifycdn.com/shopifycloud/schemas/v1/shopify/discount/gid.json" + } + } + } +} diff --git a/docs/reference/shopify/rust/instructions.md b/docs/reference/shopify/rust/instructions.md new file mode 100644 index 0000000..5a42c43 --- /dev/null +++ b/docs/reference/shopify/rust/instructions.md @@ -0,0 +1,5 @@ +Use this app when the merchant wants to create or edit a metafield-configured discount powered by this app's Shopify Function. + +When invoking `create:shopify/Discount`, pass `functionId: "YOUR_FUNCTION_ID"` so Shopify resolves directly to this app's create flow. + +When invoking `edit:shopify/Discount`, pass the discount's GID (default to the canonical `gid://shopify/DiscountNode/{id}`) and `functionId: "YOUR_FUNCTION_ID"` so Shopify resolves the edit directly to this app. diff --git a/docs/reference/shopify/rust/shopify.extension.toml b/docs/reference/shopify/rust/shopify.extension.toml index 306442e..214479f 100644 --- a/docs/reference/shopify/rust/shopify.extension.toml +++ b/docs/reference/shopify/rust/shopify.extension.toml @@ -33,3 +33,42 @@ key = "function-configuration" create = "/app/discount/:functionId/new" details = "/app/discount/:functionId/:id" # [END discount-function.ui-paths] + +# [START discount-function.app-intent-link] +# Register app intent links so Sidekick can create and edit discounts backed by +# this app's Function. Each admin.app.intent.link target maps a shopify/Discount +# action (create or edit) to a page in your app. +[[extensions]] +name = "Create discount" +description = "Create a metafield-configured discount powered by this app's Shopify Function." +handle = "discount-app-intent-link-create" +type = "admin_link" + + [[extensions.targeting]] + target = "admin.app.intent.link" + # Hardcode your deployed Function's ID (see "Set your Function ID" below). + url = "/app/discount/YOUR_FUNCTION_ID/new" + instructions = "./instructions.md" + + [[extensions.targeting.intents]] + type = "shopify/Discount" + action = "create" + schema = "./create-intent-schema.json" + +[[extensions]] +name = "Edit discount" +description = "Edit a metafield-configured discount powered by this app's Shopify Function." +handle = "discount-app-intent-link-edit" +type = "admin_link" + + [[extensions.targeting]] + target = "admin.app.intent.link" + # The discount's GID fills {id}; the Function ID stays hardcoded. + url = "/app/discount/YOUR_FUNCTION_ID/{id}" + instructions = "./instructions.md" + + [[extensions.targeting.intents]] + type = "shopify/Discount" + action = "edit" + schema = "./edit-intent-schema.json" +# [END discount-function.app-intent-link] From 831b65faac26d1ecfa10305df791b2777dc4377b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc?= Date: Mon, 27 Jul 2026 10:19:19 -0400 Subject: [PATCH 2/7] Correct edit-intent url comment: {id} receives the discount ID The {id} URL segment is filled with the discount's ID at runtime; only the Function ID stays hardcoded. Keeps the comment implementation-agnostic. Synced from discount-functions-testing. --- docs/reference/shopify/javascript/shopify.extension.toml | 2 +- docs/reference/shopify/rust/shopify.extension.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/shopify/javascript/shopify.extension.toml b/docs/reference/shopify/javascript/shopify.extension.toml index 6dc704e..5e39e61 100644 --- a/docs/reference/shopify/javascript/shopify.extension.toml +++ b/docs/reference/shopify/javascript/shopify.extension.toml @@ -68,7 +68,7 @@ type = "admin_link" [[extensions.targeting]] target = "admin.app.intent.link" - # The discount's GID fills {id}; the Function ID stays hardcoded. + # {id} receives the discount ID; the Function ID stays hardcoded. url = "/app/discount/YOUR_FUNCTION_ID/{id}" instructions = "./instructions.md" diff --git a/docs/reference/shopify/rust/shopify.extension.toml b/docs/reference/shopify/rust/shopify.extension.toml index 214479f..babdbc5 100644 --- a/docs/reference/shopify/rust/shopify.extension.toml +++ b/docs/reference/shopify/rust/shopify.extension.toml @@ -63,7 +63,7 @@ type = "admin_link" [[extensions.targeting]] target = "admin.app.intent.link" - # The discount's GID fills {id}; the Function ID stays hardcoded. + # {id} receives the discount ID; the Function ID stays hardcoded. url = "/app/discount/YOUR_FUNCTION_ID/{id}" instructions = "./instructions.md" From 125ac58250ded5dd954dd328be6bbac9de846976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc?= Date: Mon, 27 Jul 2026 12:59:43 -0400 Subject: [PATCH 3/7] Simplify functionId intent property to matchValue only The functionId property only needs matchValue for intent disambiguation. Drop the JSON-Schema type and description so the snippet matches the shape we actually ship. Synced from discount-functions-testing. --- docs/reference/shopify/javascript/create-intent-schema.json | 2 -- docs/reference/shopify/javascript/edit-intent-schema.json | 2 -- docs/reference/shopify/rust/create-intent-schema.json | 2 -- docs/reference/shopify/rust/edit-intent-schema.json | 2 -- 4 files changed, 8 deletions(-) diff --git a/docs/reference/shopify/javascript/create-intent-schema.json b/docs/reference/shopify/javascript/create-intent-schema.json index baf7d18..5a9e012 100644 --- a/docs/reference/shopify/javascript/create-intent-schema.json +++ b/docs/reference/shopify/javascript/create-intent-schema.json @@ -6,8 +6,6 @@ "additionalProperties": true, "properties": { "functionId": { - "type": "string", - "description": "The deployed Function ID for this metafield-configured discount function.", "matchValue": "YOUR_FUNCTION_ID" } } diff --git a/docs/reference/shopify/javascript/edit-intent-schema.json b/docs/reference/shopify/javascript/edit-intent-schema.json index a1cbdea..6714d2f 100644 --- a/docs/reference/shopify/javascript/edit-intent-schema.json +++ b/docs/reference/shopify/javascript/edit-intent-schema.json @@ -11,8 +11,6 @@ "additionalProperties": true, "properties": { "functionId": { - "type": "string", - "description": "The deployed Function ID for this metafield-configured discount function.", "matchValue": "YOUR_FUNCTION_ID" } } diff --git a/docs/reference/shopify/rust/create-intent-schema.json b/docs/reference/shopify/rust/create-intent-schema.json index baf7d18..5a9e012 100644 --- a/docs/reference/shopify/rust/create-intent-schema.json +++ b/docs/reference/shopify/rust/create-intent-schema.json @@ -6,8 +6,6 @@ "additionalProperties": true, "properties": { "functionId": { - "type": "string", - "description": "The deployed Function ID for this metafield-configured discount function.", "matchValue": "YOUR_FUNCTION_ID" } } diff --git a/docs/reference/shopify/rust/edit-intent-schema.json b/docs/reference/shopify/rust/edit-intent-schema.json index a1cbdea..6714d2f 100644 --- a/docs/reference/shopify/rust/edit-intent-schema.json +++ b/docs/reference/shopify/rust/edit-intent-schema.json @@ -11,8 +11,6 @@ "additionalProperties": true, "properties": { "functionId": { - "type": "string", - "description": "The deployed Function ID for this metafield-configured discount function.", "matchValue": "YOUR_FUNCTION_ID" } } From 6eb40d8c63fb208870fe8ceba6d082e7f0d185ba Mon Sep 17 00:00:00 2001 From: Ian Phipps Date: Tue, 11 Aug 2026 15:14:53 -0400 Subject: [PATCH 4/7] Resolve discount intents after save or cancel --- .../react-router-app/app/graphql/discounts.ts | 6 +++ .../app/models/discounts.server.ts | 9 ++++- .../routes/app.discount.$functionId.$id.tsx | 39 ++++++++++++++----- .../routes/app.discount.$functionId.new.tsx | 25 +++++++++--- .../react-router-app/app/utils/navigation.ts | 30 ++++++++++++-- examples/react-router-app/package.json | 8 +++- examples/react-router-app/tsconfig.json | 7 +++- 7 files changed, 103 insertions(+), 21 deletions(-) diff --git a/examples/react-router-app/app/graphql/discounts.ts b/examples/react-router-app/app/graphql/discounts.ts index 0aa6f8f..afcbce8 100644 --- a/examples/react-router-app/app/graphql/discounts.ts +++ b/examples/react-router-app/app/graphql/discounts.ts @@ -50,6 +50,9 @@ export const GET_DISCOUNT = ` export const UPDATE_CODE_DISCOUNT = ` mutation UpdateCodeDiscount($id: ID!, $discount: DiscountCodeAppInput!) { discountUpdate: discountCodeAppUpdate(id: $id, codeAppDiscount: $discount) { + codeAppDiscount { + discountId + } userErrors { code message @@ -68,6 +71,9 @@ export const UPDATE_AUTOMATIC_DISCOUNT = ` id: $id automaticAppDiscount: $discount ) { + automaticAppDiscount { + discountId + } userErrors { code message diff --git a/examples/react-router-app/app/models/discounts.server.ts b/examples/react-router-app/app/models/discounts.server.ts index 6577e07..36a5a87 100644 --- a/examples/react-router-app/app/models/discounts.server.ts +++ b/examples/react-router-app/app/models/discounts.server.ts @@ -73,7 +73,8 @@ export async function createCodeDiscount( return { errors: responseJson.data.discountCreate?.userErrors as UserError[], - discount: responseJson.data.discountCreate?.codeAppDiscount, + discountId: responseJson.data.discountCreate?.codeAppDiscount + ?.discountId as string | undefined, }; } @@ -108,6 +109,8 @@ export async function createAutomaticDiscount( return { errors: responseJson.data.discountCreate?.userErrors as UserError[], + discountId: responseJson.data.discountCreate?.automaticAppDiscount + ?.discountId as string | undefined, }; } @@ -161,6 +164,8 @@ export async function updateCodeDiscount( const responseJson = await response.json(); return { errors: responseJson.data.discountUpdate?.userErrors as UserError[], + discountId: responseJson.data.discountUpdate?.codeAppDiscount + ?.discountId as string | undefined, }; } @@ -207,6 +212,8 @@ export async function updateAutomaticDiscount( const responseJson = await response.json(); return { errors: responseJson.data.discountUpdate?.userErrors as UserError[], + discountId: responseJson.data.discountUpdate?.automaticAppDiscount + ?.discountId as string | undefined, }; } diff --git a/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx b/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx index 248a94a..e34ee6f 100644 --- a/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx +++ b/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx @@ -1,4 +1,5 @@ import { Collection, DiscountClass } from "app/types/admin.types"; +import { useEffect, useRef } from "react"; import { type ActionFunctionArgs, type LoaderFunctionArgs, @@ -16,6 +17,10 @@ import { updateCodeDiscount, } from "../models/discounts.server"; import { DiscountMethod } from "../types/types"; +import { + completeDiscountWorkflow, + returnToDiscounts, +} from "../utils/navigation"; interface ActionData { errors?: { @@ -24,6 +29,7 @@ interface ActionData { field?: string[]; }[]; success?: boolean; + discountId?: string; } interface LoaderData { @@ -116,7 +122,10 @@ export const action = async ({ params, request }: ActionFunctionArgs) => { if (result.errors?.length > 0) { return { errors: result.errors }; } - return { success: true }; + if (!result.discountId) { + throw new Error("No discount ID returned"); + } + return { success: true, discountId: result.discountId }; }; export const loader = async ({ params, request }: LoaderFunctionArgs) => { @@ -148,6 +157,17 @@ export default function VolumeEdit() { ...error, field: error.field || [], })) || []; + const completedDiscountId = useRef(null); + + // [START build-the-ui.resolve-edit-intent] + useEffect(() => { + const discountId = actionData?.discountId; + if (!discountId || completedDiscountId.current === discountId) return; + + completedDiscountId.current = discountId; + void completeDiscountWorkflow(discountId); + }, [actionData?.discountId]); + // [END build-the-ui.resolve-edit-intent] if (!rawDiscount) { return ; @@ -178,15 +198,14 @@ export default function VolumeEdit() { }; return ( - - - Discounts - - + + Discounts + + } + > { // Initially load with empty collections since none are selected yet @@ -76,7 +80,10 @@ export const action = async ({ params, request }: ActionFunctionArgs) => { if (result.errors?.length > 0) { return { errors: result.errors }; } - return { success: true }; + if (!result.discountId) { + throw new Error("No discount ID returned"); + } + return { success: true, discountId: result.discountId }; }; // [END build-the-ui.add-action] @@ -87,6 +94,7 @@ interface ActionData { field: string[]; }[]; success?: boolean; + discountId?: string; } interface LoaderData { @@ -99,10 +107,17 @@ export default function VolumeNew() { const navigation = useNavigation(); const isLoading = navigation.state === "submitting"; const submitErrors = actionData?.errors || []; + const completedDiscountId = useRef(null); - if (actionData?.success) { - returnToDiscounts(); - } + // [START build-the-ui.resolve-create-intent] + useEffect(() => { + const discountId = actionData?.discountId; + if (!discountId || completedDiscountId.current === discountId) return; + + completedDiscountId.current = discountId; + void completeDiscountWorkflow(discountId); + }, [actionData?.discountId]); + // [END build-the-ui.resolve-create-intent] const initialData = { title: "", diff --git a/examples/react-router-app/app/utils/navigation.ts b/examples/react-router-app/app/utils/navigation.ts index 8aa1997..d80f22b 100644 --- a/examples/react-router-app/app/utils/navigation.ts +++ b/examples/react-router-app/app/utils/navigation.ts @@ -1,5 +1,29 @@ -export function returnToDiscounts() { - if (typeof window !== "undefined") { - window.open("shopify://admin/discounts", "_top"); +// [START build-the-ui.resolve-intent] +export async function completeDiscountWorkflow(discountId: string) { + if (shopify.intents.request.value) { + await getIntentResponse().ok({ id: discountId }); + return; } + + openDiscountsPage(); +} + +export async function returnToDiscounts() { + if (shopify.intents.request.value) { + await getIntentResponse().closed(); + return; + } + + openDiscountsPage(); +} +// [END build-the-ui.resolve-intent] + +function getIntentResponse() { + const response = shopify.intents.response; + if (!response) throw new Error("Intent response API unavailable"); + return response; +} + +function openDiscountsPage() { + window.open("shopify://admin/discounts", "_top"); } diff --git a/examples/react-router-app/package.json b/examples/react-router-app/package.json index 4ddec10..b5818f3 100644 --- a/examples/react-router-app/package.json +++ b/examples/react-router-app/package.json @@ -28,7 +28,8 @@ "@react-router/fs-routes": "7.9.3", "@react-router/node": "7.9.3", "@react-router/serve": "7.9.3", - "@shopify/app-bridge-react": "4.2.7", + "@shopify/app-bridge-react": "4.2.10", + "@shopify/app-bridge-types": "0.7.2", "@shopify/cli": "3.87.4", "@shopify/shopify-app-react-router": "1.0.2", "@shopify/shopify-app-session-storage-prisma": "7.0.1", @@ -64,6 +65,11 @@ "extensions/*" ] }, + "pnpm": { + "overrides": { + "@shopify/app-bridge-types": "0.7.2" + } + }, "trustedDependencies": [ "@shopify/plugin-cloudflare" ] diff --git a/examples/react-router-app/tsconfig.json b/examples/react-router-app/tsconfig.json index 95775f0..68030b5 100644 --- a/examples/react-router-app/tsconfig.json +++ b/examples/react-router-app/tsconfig.json @@ -23,7 +23,12 @@ "moduleResolution": "Bundler", "target": "ES2022", "baseUrl": ".", - "types": ["@react-router/node", "vite/client", "@shopify/polaris-types"], + "types": [ + "@react-router/node", + "vite/client", + "@shopify/app-bridge-types", + "@shopify/polaris-types" + ], "rootDirs": [".", "./.react-router/types"] } } From ae1d2d46d9bd5a1fa671b99a8354769e4d6f542b Mon Sep 17 00:00:00 2001 From: Ian Phipps Date: Tue, 25 Aug 2026 11:08:21 -0400 Subject: [PATCH 5/7] Render discount breadcrumbs through the slot --- .../app/routes/app.discount.$functionId.$id.tsx | 17 +++++++++-------- .../app/routes/app.discount.$functionId.new.tsx | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx b/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx index e34ee6f..d5e29b1 100644 --- a/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx +++ b/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx @@ -198,14 +198,15 @@ export default function VolumeEdit() { }; return ( - - Discounts - - } - > + + - Discounts - - } - > + + Date: Wed, 2 Sep 2026 15:50:46 -0400 Subject: [PATCH 6/7] Add required Sidekick extension summary --- examples/react-router-app/shopify.app.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/react-router-app/shopify.app.toml b/examples/react-router-app/shopify.app.toml index 7dce42d..2e5bb6f 100644 --- a/examples/react-router-app/shopify.app.toml +++ b/examples/react-router-app/shopify.app.toml @@ -3,6 +3,12 @@ # [START react-router-app.scopes] scopes = "write_discounts,read_products" # [END react-router-app.scopes] + +# [START react-router-app.sidekick-summary] +[sidekick] +extensions_summary = "Create and edit discounts powered by Shopify Functions" +# [END react-router-app.sidekick-summary] + [webhooks] api_version = "2024-10" From 175a4699c3d0f10405af4e8844374a21eb8933e8 Mon Sep 17 00:00:00 2001 From: Ian Phipps Date: Mon, 21 Sep 2026 17:19:48 -0400 Subject: [PATCH 7/7] Handle missing discount IDs as form errors --- .../app/models/discounts.server.ts | 14 ++++++-------- .../app/routes/app.discount.$functionId.$id.tsx | 4 +++- .../app/routes/app.discount.$functionId.new.tsx | 4 +++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/react-router-app/app/models/discounts.server.ts b/examples/react-router-app/app/models/discounts.server.ts index 36a5a87..ddc591b 100644 --- a/examples/react-router-app/app/models/discounts.server.ts +++ b/examples/react-router-app/app/models/discounts.server.ts @@ -73,8 +73,7 @@ export async function createCodeDiscount( return { errors: responseJson.data.discountCreate?.userErrors as UserError[], - discountId: responseJson.data.discountCreate?.codeAppDiscount - ?.discountId as string | undefined, + discountId: responseJson.data.discountCreate?.codeAppDiscount?.discountId, }; } @@ -109,8 +108,8 @@ export async function createAutomaticDiscount( return { errors: responseJson.data.discountCreate?.userErrors as UserError[], - discountId: responseJson.data.discountCreate?.automaticAppDiscount - ?.discountId as string | undefined, + discountId: + responseJson.data.discountCreate?.automaticAppDiscount?.discountId, }; } @@ -164,8 +163,7 @@ export async function updateCodeDiscount( const responseJson = await response.json(); return { errors: responseJson.data.discountUpdate?.userErrors as UserError[], - discountId: responseJson.data.discountUpdate?.codeAppDiscount - ?.discountId as string | undefined, + discountId: responseJson.data.discountUpdate?.codeAppDiscount?.discountId, }; } @@ -212,8 +210,8 @@ export async function updateAutomaticDiscount( const responseJson = await response.json(); return { errors: responseJson.data.discountUpdate?.userErrors as UserError[], - discountId: responseJson.data.discountUpdate?.automaticAppDiscount - ?.discountId as string | undefined, + discountId: + responseJson.data.discountUpdate?.automaticAppDiscount?.discountId, }; } diff --git a/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx b/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx index d5e29b1..e0b6d1a 100644 --- a/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx +++ b/examples/react-router-app/app/routes/app.discount.$functionId.$id.tsx @@ -123,7 +123,9 @@ export const action = async ({ params, request }: ActionFunctionArgs) => { return { errors: result.errors }; } if (!result.discountId) { - throw new Error("No discount ID returned"); + return { + errors: [{ message: "Unable to save discount.", field: [] }], + }; } return { success: true, discountId: result.discountId }; }; diff --git a/examples/react-router-app/app/routes/app.discount.$functionId.new.tsx b/examples/react-router-app/app/routes/app.discount.$functionId.new.tsx index c88d103..d006841 100644 --- a/examples/react-router-app/app/routes/app.discount.$functionId.new.tsx +++ b/examples/react-router-app/app/routes/app.discount.$functionId.new.tsx @@ -81,7 +81,9 @@ export const action = async ({ params, request }: ActionFunctionArgs) => { return { errors: result.errors }; } if (!result.discountId) { - throw new Error("No discount ID returned"); + return { + errors: [{ message: "Unable to save discount.", field: [] }], + }; } return { success: true, discountId: result.discountId }; };