Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Checkout UI extensions] Add Localized Fields API to Unstable #2547

Draft
wants to merge 3 commits into
base: unstable
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fluffy-rules-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/ui-extensions-react': minor
'@shopify/ui-extensions': minor
---

Adds the Localized Fields API and `useLocalizedFields` hook.
1 change: 1 addition & 0 deletions packages/ui-extensions-react/src/surfaces/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,4 @@ export {useDeliverySelectionGroups} from './checkout/hooks/delivery-selection-gr
export {useCheckoutToken} from './checkout/hooks/checkout-token';
export {useCustomerPrivacy} from './checkout/hooks/customer-privacy';
export {useInstructions} from './checkout/hooks/instructions';
export {useLocalizedFields} from './checkout/hooks/localized-fields';
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type {
LocalizedField,
LocalizedFieldKey,
RenderExtensionTarget,
} from '@shopify/ui-extensions/checkout';

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

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

/**
* Returns the current localized fields and
* re-renders your component if the values change.
*/
export function useLocalizedFields<
Target extends RenderExtensionTarget = RenderExtensionTarget,
>(keys: LocalizedFieldKey[]): LocalizedField[] | undefined {
const {localizedFields} = useApi<Target>();

if (!localizedFields) {
throw new ScopeNotGrantedError(
'Using localized fields requires having personal customer data permissions granted to your app.',
);
}

return useSubscription(localizedFields)?.filter(({key}) =>
keys.includes(key),
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type {LocalizedField} from '@shopify/ui-extensions/checkout';

import {useLocalizedFields} from '../localized-fields';

import {mount, createMockStatefulRemoteSubscribable} from './mount';

describe('useLocalizedFields', () => {
it('throws an error if localized fields are not available', () => {
const extensionApi = {
localizedFields: undefined,
};

expect(() => {
mount.hook(() => useLocalizedFields(['TAX_CREDENTIAL_BR']), {
extensionApi,
});
}).toThrow(
'Using localized fields requires having personal customer data permissions granted to your app.',
);
});

it('returns empty if no localized fields match the passed keys', () => {
const localizedFields: LocalizedField[] = [
{
key: 'TAX_CREDENTIAL_BR',
title: 'CPF/CNPJ',
value: 'test-value',
},
{
key: 'SHIPPING_CREDENTIAL_BR',
title: 'CPF/CNPJ',
value: 'test-value',
},
];

const extensionApi = {
localizedFields: createMockStatefulRemoteSubscribable(localizedFields),
};

const {value} = mount.hook(
() => useLocalizedFields(['TAX_CREDENTIAL_ES']),
{extensionApi},
);

expect(value).toStrictEqual([]);
});

it('returns an array of localized fields that match the passsed keys', () => {
const localizedFields: LocalizedField[] = [
{
key: 'TAX_CREDENTIAL_MX',
title: 'Tax credential MX',
value: 'test-value',
},
{
key: 'SHIPPING_CREDENTIAL_MX',
title: 'Shipping credential MX',
value: 'test-value',
},
{
key: 'TAX_CREDENTIAL_USE_MX',
title: 'Tax credential use MX',
value: 'test-value',
},
];

const extensionApi = {
localizedFields: createMockStatefulRemoteSubscribable(localizedFields),
};

const {value} = mount.hook(
() => useLocalizedFields(['TAX_CREDENTIAL_MX', 'TAX_CREDENTIAL_USE_MX']),
{extensionApi},
);

expect(value).toMatchObject([localizedFields[0], localizedFields[2]]);
});
});
42 changes: 42 additions & 0 deletions packages/ui-extensions/src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,48 @@ export type CountryCode =
| 'ZW'
| 'ZZ';

/**
* A union of keys for the localized fields that are required by certain countries.
*/
export type LocalizedFieldKey =
| 'SHIPPING_CREDENTIAL_BR'
| 'SHIPPING_CREDENTIAL_CL'
| 'SHIPPING_CREDENTIAL_CN'
| 'SHIPPING_CREDENTIAL_CO'
| 'SHIPPING_CREDENTIAL_CR'
| 'SHIPPING_CREDENTIAL_EC'
| 'SHIPPING_CREDENTIAL_ES'
| 'SHIPPING_CREDENTIAL_GT'
| 'SHIPPING_CREDENTIAL_ID'
| 'SHIPPING_CREDENTIAL_KR'
| 'SHIPPING_CREDENTIAL_MY'
| 'SHIPPING_CREDENTIAL_MX'
| 'SHIPPING_CREDENTIAL_PE'
| 'SHIPPING_CREDENTIAL_PT'
| 'SHIPPING_CREDENTIAL_PY'
| 'SHIPPING_CREDENTIAL_TR'
| 'SHIPPING_CREDENTIAL_TW'
| 'SHIPPING_CREDENTIAL_TYPE_CO'
| 'TAX_CREDENTIAL_BR'
| 'TAX_CREDENTIAL_CL'
| 'TAX_CREDENTIAL_CO'
| 'TAX_CREDENTIAL_CR'
| 'TAX_CREDENTIAL_EC'
| 'TAX_CREDENTIAL_ES'
| 'TAX_CREDENTIAL_GT'
| 'TAX_CREDENTIAL_ID'
| 'TAX_CREDENTIAL_IT'
| 'TAX_CREDENTIAL_MX'
| 'TAX_CREDENTIAL_MY'
| 'TAX_CREDENTIAL_PE'
| 'TAX_CREDENTIAL_PT'
| 'TAX_CREDENTIAL_PY'
| 'TAX_CREDENTIAL_TR'
| 'TAX_CREDENTIAL_TYPE_CO'
| 'TAX_CREDENTIAL_TYPE_MX'
| 'TAX_CREDENTIAL_USE_MX'
| 'TAX_EMAIL_IT';

/**
* Union of supported storefront API versions
*/
Expand Down
1 change: 1 addition & 0 deletions packages/ui-extensions/src/surfaces/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export type {
Market,
Language,
Localization,
LocalizedField,
DeliveryGroup,
DeliveryGroupType,
DeliveryGroupDetails,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
Timezone,
GraphQLError,
StorefrontApiVersion,
LocalizedFieldKey,
} from '../../../../shared';

export type {ApiVersion, Capability} from '../../../../shared';
Expand Down Expand Up @@ -396,6 +397,12 @@ export interface Localization {
market: StatefulRemoteSubscribable<Market | undefined>;
}

export interface LocalizedField {
key: LocalizedFieldKey;
title: string;
value: string;
}

/**
* Provides details on the buyer's progression through the checkout.
*/
Expand Down Expand Up @@ -734,6 +741,12 @@ export interface StandardApi<Target extends ExtensionTarget = ExtensionTarget> {
* {% include /apps/checkout/privacy-icon.md %} Requires access to [protected customer data](/docs/apps/store/data-protection/protected-customer-data).
*/
applyTrackingConsentChange: ApplyTrackingConsentChangeType;

/**
* The API for reading additional fields that are required in checkout under certain circumstances.
* For example, some countries require additional fields for customs information or tax identification numbers.
*/
localizedFields?: StatefulRemoteSubscribable<LocalizedField[] | undefined>;
}

export interface Ui {
Expand Down
1 change: 1 addition & 0 deletions packages/ui-extensions/src/surfaces/checkout/targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -899,4 +899,5 @@ export interface CustomerAccountStandardApi<
| 'version'
| 'customerPrivacy'
| 'applyTrackingConsentChange'
| 'localizedFields'
> {}
Loading