Skip to content

Commit

Permalink
Merge pull request #2552 from Shopify/rc/2025-01-with-private-package…
Browse files Browse the repository at this point in the history
…-changes

Updates `2025-01` release candidate with private package changes
  • Loading branch information
rcaplanshopify authored Jan 6, 2025
2 parents 7f64b0d + 426f367 commit fc2ebf1
Show file tree
Hide file tree
Showing 22 changed files with 71 additions and 269 deletions.
6 changes: 6 additions & 0 deletions .changeset/rude-suns-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/ui-extensions-react': minor
'@shopify/ui-extensions': minor
---

Removed customer account targets as valid targets for checkout UI extensions. Use [customer account UI extensions](https://shopify.dev/docs/api/customer-account-ui-extensions) instead.
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,7 @@ export function useBuyerJourney<
>(): BuyerJourney {
const api = useApi<Target>();

if ('buyerJourney' in api) {
return api.buyerJourney;
}

throw new ExtensionHasNoMethodError(
'applyAttributeChange',
api.extension.target,
);
return api.buyerJourney;
}

/**
Expand All @@ -38,12 +31,7 @@ export function useBuyerJourneyCompleted<
Target extends RenderExtensionTarget = RenderExtensionTarget,
>(): boolean {
const api = useApi<Target>();

if ('buyerJourney' in api) {
return useSubscription(api.buyerJourney.completed);
}

throw new ExtensionHasNoMethodError('buyerJourney', api.extension.target);
return useSubscription(api.buyerJourney.completed);
}

/**
Expand All @@ -60,10 +48,6 @@ export function useBuyerJourneyIntercept<
>(interceptor: Interceptor): void {
const api = useApi<Target>();

if (!('buyerJourney' in api)) {
throw new ExtensionHasNoMethodError('buyerJourney', api.extension.target);
}

const interceptorRef = useRef(interceptor);
interceptorRef.current = interceptor;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,12 @@ import {useSubscription} from './subscription';
* - `purchase.cart-line-item.line-components.render`
* - `purchase.checkout.cart-line-item.render-after`
* - `purchase.thank-you.cart-line-item.render-after`
* - 'customer-account.order-status.cart-line-item.render-after'
*/
export function useCartLineTarget(): CartLine {
const api = useApi<
| 'purchase.cart-line-item.line-components.render'
| 'purchase.checkout.cart-line-item.render-after'
| 'purchase.thank-you.cart-line-item.render-after'
| 'customer-account.order-status.cart-line-item.render-after'
>();
if (!api.target) {
throw new ExtensionHasNoTargetError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import type {
RenderExtensionTarget,
} from '@shopify/ui-extensions/checkout';

import {ExtensionHasNoMethodError} from '../errors';

import {useApi} from './api';
import {useSubscription} from './subscription';

Expand All @@ -16,10 +14,5 @@ export function useDeliveryGroups<
Target extends RenderExtensionTarget = RenderExtensionTarget,
>(): DeliveryGroup[] {
const api = useApi<Target>();

if (!('deliveryGroups' in api)) {
throw new ExtensionHasNoMethodError('deliveryGroups', api.extension.target);
}

return useSubscription(api.deliveryGroups);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import type {
SelectedPaymentOption,
} from '@shopify/ui-extensions/checkout';

import {ExtensionHasNoMethodError} from '../errors';

import {useApi} from './api';
import {useSubscription} from './subscription';

Expand All @@ -16,15 +14,7 @@ export function useAvailablePaymentOptions<
Target extends RenderExtensionTarget = RenderExtensionTarget,
>(): PaymentOption[] {
const api = useApi<Target>();

if ('availablePaymentOptions' in api) {
return useSubscription(api.availablePaymentOptions);
}

throw new ExtensionHasNoMethodError(
'availablePaymentOptions',
api.extension.target,
);
return useSubscription(api.availablePaymentOptions);
}

/**
Expand All @@ -34,13 +24,5 @@ export function useSelectedPaymentOptions<
Target extends RenderExtensionTarget = RenderExtensionTarget,
>(): SelectedPaymentOption[] {
const api = useApi<Target>();

if ('selectedPaymentOptions' in api) {
return useSubscription(api.selectedPaymentOptions);
}

throw new ExtensionHasNoMethodError(
'selectedPaymentOptions',
api.extension.target,
);
return useSubscription(api.selectedPaymentOptions);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class ExtensionHasNoTargetError extends Error {

/**
* Returns the cart line the extension is attached to. This hook can only be used by extensions in the
* `purchase.cart-line-item.line-components.render`, `purchase.checkout.cart-line-item.render-after`,
* `purchase.thank-you.cart-line-item.render-after`, and `customer-account.order-status.cart-line-item.render-after`
* `purchase.cart-line-item.line-components.render`, `purchase.checkout.cart-line-item.render-after`, and
* `purchase.thank-you.cart-line-item.render-after`
* extension targets. Until version `2023-04`, this hook returned a `PresentmentCartLine` object.
*
* > Caution: Deprecated as of version `2023-10`, use `useCartLineTarget()` instead.
Expand All @@ -28,7 +28,6 @@ export function useTarget(): CartLine {
| 'purchase.cart-line-item.line-components.render'
| 'purchase.checkout.cart-line-item.render-after'
| 'purchase.thank-you.cart-line-item.render-after'
| 'customer-account.order-status.cart-line-item.render-after'
>();
if (!api.target) {
throw new ExtensionHasNoTargetError(api.extension.target);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,6 @@ describe('buyerJourney Hooks', () => {
});

describe('useBuyerJourney()', () => {
it('raises an exception when buyerJourney api is not available', () => {
expect(() => {
mount.hook(() => useBuyerJourney(), {
extensionApi: {
extension: {
target: 'purchase.checkout.header.render-after',
},
},
});
}).toThrow(ExtensionHasNoMethodError);
});

it('returns the buyer journey when the api is available', () => {
const hook = mount.hook(() => useBuyerJourney(), {
extensionApi: {
Expand All @@ -178,18 +166,6 @@ describe('buyerJourney Hooks', () => {
});

describe('useBuyerJourneyCompleted()', () => {
it('raises an exception when buyerJourney api is not available', () => {
expect(() => {
mount.hook(() => useBuyerJourneyCompleted(), {
extensionApi: {
extension: {
target: 'purchase.checkout.header.render-after',
},
},
});
}).toThrow(ExtensionHasNoMethodError);
});

it.each([true, false])(
'returns the buyer journey completed value: %s',
(completed) => {
Expand All @@ -210,24 +186,6 @@ describe('buyerJourney Hooks', () => {
});

describe('useBuyerJourneyIntercept()', () => {
it('raises an exception when buyerJourney api is not available', () => {
expect(() => {
mount.hook(
() =>
useBuyerJourneyIntercept(() => ({
behavior: 'allow',
})),
{
extensionApi: {
extension: {
target: 'purchase.checkout.header.render-after',
},
},
},
);
}).toThrow(ExtensionHasNoMethodError);
});

it('calls the interceptor function', () => {
const mockIntercept = jest.fn(() =>
Promise.resolve({behavior: 'allow'} as const),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import {

export default extension(
'purchase.checkout.block.render',
(root, {shop}) => {
const apiVersion = 'unstable';
(root) => {
const getProductsQuery = {
query: `query ($first: Int!) {
products(first: $first) {
Expand All @@ -20,16 +19,13 @@ export default extension(
variables: {first: 5},
};

fetch(
`${shop.storefrontUrl}api/${apiVersion}/graphql.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(getProductsQuery),
fetch('shopify:storefront/api/graphql.json', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
)
body: JSON.stringify(getProductsQuery),
})
.then((response) => response.json())
.then(({data}) => {
const listItems =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,17 @@ function Extension() {
variables: {first: 5},
};

const apiVersion = 'unstable';

fetch(
`${shop.storefrontUrl}api/${apiVersion}/graphql.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(getProductsQuery),
fetch('shopify:storefront/api/graphql.json', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
)
body: JSON.stringify(getProductsQuery),
})
.then((response) => response.json())
.then(({data, errors}) => setData(data))
.catch(console.error);
}, [shop]);
});

return (
<List>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,12 @@ Ensure your extension can use this API by [enabling the \`api_access\` capabilit
description: `
You can access the [Storefront GraphQL API](/docs/api/storefront) using global \`fetch()\`.
Ensure your extension can access the Storefront API via the [\`api_access\` capability](/docs/api/checkout-ui-extensions/configuration#api-access).
The \`shopify:storefront\` protocol will automatically infer your Storefront URL and API version declared in your extension config.
By omitting the API version (recommended), Shopify will use your API version configured in \`shopify.extension.toml\`. To change the API version, simply add it to the URL like \`shopify:storefront/api/2024-04/graphql.json\`.
See [Storefront GraphQL API endpoints](/docs/api/storefront#endpoints) for more information.
`,
codeblock: {
title: 'Accessing the Storefront API with fetch()',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ Defines the [capabilities](/docs/api/checkout-ui-extensions/apis/standardapi#pro
{
name: 'API access examples',
subtitle: 'See',
url: '/docs/api/checkout-ui-extensions/apis/standardapi#example-storefront-api-access',
url: '/docs/api/checkout-ui-extensions/apis/storefront-api#examples',
type: 'blocks',
},
],
Expand All @@ -170,7 +170,7 @@ Defines the [capabilities](/docs/api/checkout-ui-extensions/apis/standardapi#pro
},
{
title: 'Methods for accessing the Storefront API',
sectionContent: `Enabling the \`api_access\` capability allows you to use the Standard API [\`query\`](/docs/api/checkout-ui-extensions/apis/standardapi#properties-propertydetail-query) method and the global \`fetch\` to retrieve data from the [Storefront API](/api/storefront) without manually managing token aquisition and refresh.
sectionContent: `Enabling the \`api_access\` capability allows you to use the Standard API [\`query\`](/docs/api/checkout-ui-extensions/apis/storefront-api) method and the global \`fetch\` to retrieve data from the [Storefront API](/api/storefront) without manually managing token aquisition and refresh.
\`query\` lets you request a single GraphQL response from the Storefront API.
Expand All @@ -194,6 +194,12 @@ Your extensions will have the following unauthenticated access scopes to the Sto
- <code>unauthenticated_read_metaobjects</code>
`,
},
{
title: 'Protocol Links',
sectionContent: `
Protocol links are an easy way for Shopify to infer the type of request you are trying to make. If you would like to make a request to the [Storefront GraphQL API](/docs/api/storefront), you can use our [Storefront Protocol](/docs/api/checkout-ui-extensions/unstable/apis/storefront-api#examples) to infer your Storefront URL and API version.
`,
},
],
},
{
Expand Down Expand Up @@ -434,7 +440,7 @@ You retrieve these metafields in your extension by reading [\`appMetafields\`](/
{
title: 'Validation options',
sectionContent:
'Each setting can include validation options. Validation options enable you to apply additional constraints to the data that a setting can store, such as a minimum or maximum value, or a regular expression. The setting\'s `type` determines the available validation options. \n\n You can include a validation option for a setting using the validation `name` and a corresponding `value`. The appropriate value depends on the setting type to which the validation applies.\n\n The following table outlines the available validation options with supported types for applying constraints to a setting:\n\n | Validation option | Description | Supported types | Example |\n|---|---|---|---|\n| Minimum length | The minimum length of a text value. | <ul><li><code>single_line_text_field</code></li><li><code>multi_line_text_field</code></li></ul> | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "8"</pre> |\n| Maximum length | The maximum length of a text value. | <ul><li><code>single_line_text_field</code></li><li><code>multi_line_text_field</code></li></ul> | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "25"</pre> |\n| Regular expression | A regular expression. Shopify supports [RE2](https://github.com/google/re2/wiki/Syntax). | <ul><li><code>single_line_text_field</code></li><li><code>multi_line_text_field</code></li></ul> | <pre>[[extensions.settings.fields.validations]]<br> name = "regex"<br> value = "(@)(.+)$"</pre> |\n| Choices | A list of up to 128 predefined options that limits the values allowed for the metafield. | `single_line_text_field` | <pre>[[extensions.settings.fields.validations]]<br> name = "choices"<br> value = "[&#92"red&#92", &#92"green&#92", &#92"blue&#92"]"</pre> |\n| Minimum date | The minimum date in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. | `date` | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "2022-01-01"</pre> |\n| Maximum date | The maximum date in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. | `date` | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "2022-03-03"</pre> |\n| Minimum datetime | The minimum date and time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. | `date_time` | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "2022-03-03T16:30:00"</pre> |\n| Maximum datetime | The maximum date and time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. | `date_time` | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "2022-03-03T17:30:00"</pre> |\n| Minimum integer | The minimum integer number. | `number_integer` | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "9"</pre> |\n| Maximum integer | The maximum integer number. | `number_integer` | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "15"</pre> |\n| Minimum decimal | The minimum decimal number. | `number_decimal` | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "0.5"</pre> |\n| Maximum decimal | The maximum decimal number. | `number_decimal` | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "1.99"</pre> |\n| Maximum precision | The maximum number of decimal places to store for a decimal number. | `number_decimal` | <pre>[[extensions.settings.fields.validations]]<br> name = "max_precision"<br> value = "2"</pre> |',
'Each setting can include validation options. Validation options enable you to apply additional constraints to the data that a setting can store, such as a minimum or maximum value, or a regular expression. The setting\'s `type` determines the available validation options. \n\n You can include a validation option for a setting using the validation `name` and a corresponding `value`. The appropriate value depends on the setting type to which the validation applies.\n\n The following table outlines the available validation options with supported types for applying constraints to a setting:\n\n | Validation option | Description | Supported types | Example |\n|---|---|---|---|\n| Minimum length | The minimum length of a text value. | <ul><li><code>single_line_text_field</code></li><li><code>multi_line_text_field</code></li></ul> | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "8"</pre> |\n| Maximum length | The maximum length of a text value. | <ul><li><code>single_line_text_field</code></li><li><code>multi_line_text_field</code></li></ul> | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "25"</pre> |\n| Regular expression | A regular expression. Shopify supports [RE2](https://github.com/google/re2/wiki/Syntax). | <ul><li><code>single_line_text_field</code></li><li><code>multi_line_text_field</code></li></ul> | <pre>[[extensions.settings.fields.validations]]<br> name = "regex"<br> value = "(@)(.+)$"</pre> |\n| Choices | A list of up to 128 predefined options that limits the values allowed for the metafield. | `single_line_text_field` | <pre>[[extensions.settings.fields.validations]]<br> name = "choices"<br> value = "["red", "green", "blue"]"</pre> |\n| Minimum date | The minimum date in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. | `date` | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "2022-01-01"</pre> |\n| Maximum date | The maximum date in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. | `date` | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "2022-03-03"</pre> |\n| Minimum datetime | The minimum date and time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. | `date_time` | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "2022-03-03T16:30:00"</pre> |\n| Maximum datetime | The maximum date and time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. | `date_time` | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "2022-03-03T17:30:00"</pre> |\n| Minimum integer | The minimum integer number. | `number_integer` | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "9"</pre> |\n| Maximum integer | The maximum integer number. | `number_integer` | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "15"</pre> |\n| Minimum decimal | The minimum decimal number. | `number_decimal` | <pre>[[extensions.settings.fields.validations]]<br> name = "min"<br> value = "0.5"</pre> |\n| Maximum decimal | The maximum decimal number. | `number_decimal` | <pre>[[extensions.settings.fields.validations]]<br> name = "max"<br> value = "1.99"</pre> |\n| Maximum precision | The maximum number of decimal places to store for a decimal number. | `number_decimal` | <pre>[[extensions.settings.fields.validations]]<br> name = "max_precision"<br> value = "2"</pre> |',
},
],
},
Expand Down Expand Up @@ -473,6 +479,7 @@ For specific targets, you must provide the URL of assets or pages loaded by UI c
The \`chat\` property specifies the URL for the iframe used in this extension target. The URL can be absolute or relative. Relative URLs are resolved against the app URL defined in the app configuration.
For example,
* if the app URL is \`https://example.com\` and \`chat = "/my-chat-application"\`, the resolved URL will be \`https://example.com/my-chat-application\`.
* if \`chat = "https://my-chat-application.com"\`, the resolved URL will be \`https://my-chat-application.com\`.
`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {LandingTemplateSchema} from '@shopify/generate-docs';
const exampleCodePath = '../reference/examples/cart-instructions';

const data: LandingTemplateSchema = {
title: 'Updating to 2024-10',
title: 'Updating to 2025-01',
description: `
Some checkouts may be created with [cart instructions](/docs/api/checkout-ui-extensions/apis/cart-instructions) that prevent buyers from making certain changes to their checkout.
Expand Down Expand Up @@ -38,7 +38,7 @@ As of version \`2024-07\`, UI extensions must check for instructions before call
sectionContent: `
You will need to check for cart instructions before calling the following APIs:
| Extension API | As of July 2024 |
| Extension API | As of January 2025 |
| ---- | ----- |
| applyAttributeChange() | Attributes cannot be modified on draft order checkouts. |
| applyShippingAddressChange() | Buyers cannot change the address on a draft order checkout if it has fixed shipping rates. |
Expand Down
Loading

0 comments on commit fc2ebf1

Please sign in to comment.