Skip to content

Commit

Permalink
add new pickup-location-option-item extension target
Browse files Browse the repository at this point in the history
  • Loading branch information
elskhn committed Jan 30, 2024
1 parent db70875 commit 79a64d1
Show file tree
Hide file tree
Showing 19 changed files with 346 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export {useCartLines, useApplyCartLinesChange} from './cart-lines';
export {useCartLineTarget} from './cart-line-target';
export {useTarget} from './target';
export {useShippingOptionTarget} from './shipping-option-target';
export {usePickupLocationOptionTarget} from './pickup-location-option-target';
export {useAppMetafields} from './app-metafields';
export {useShop} from './shop';
export {useStorage} from './storage';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type {PickupLocationOption} from '@shopify/ui-extensions/checkout';
import {useMemo} from 'react';

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

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

/**
* Returns the pickup location option the extension is attached to. This hook can only be used by extensions in the following
* extension target:
* - `purchase.checkout.pickup-location-option-item.render-after`
*/
export function usePickupLocationOptionTarget(): {
pickupLocationOptionTarget: PickupLocationOption;
isTargetSelected: boolean;
} {
const api =
useApi<'purchase.checkout.pickup-location-option-item.render-after'>();
if (!api.target || api.isTargetSelected === undefined) {
throw new ExtensionHasNoTargetError(
'usePickupLocationOptionTarget',
api.extension.target,
);
}

const pickupLocationOptionTarget = useSubscription(api.target);
const isTargetSelected = useSubscription(api.isTargetSelected);

const pickupLocationOption = useMemo(() => {
return {
pickupLocationOptionTarget,
isTargetSelected,
};
}, [pickupLocationOptionTarget, isTargetSelected]);

return pickupLocationOption;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type {
ExtensionTarget,
PickupLocationOption,
} from '@shopify/ui-extensions/checkout';

import {usePickupLocationOptionTarget} from '../pickup-location-option-target';
import {ExtensionHasNoTargetError} from '../../errors';

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

describe('usePickupLocationOptionTarget', () => {
const pickupLocationOption = {
handle: 'pickup_method_1',
title: 'Pickup method 1',
description: 'something',
type: 'pickup',
code: 'pickup_method_1',
location: {
address: {
address1: '123 Fake St',
address2: null,
city: 'Ottawa',
company: null,
country: 'Canada',
firstName: 'Bob',
lastName: 'Bobsen',
phone: '555-555-5555',
province: 'Ontario',
zip: 'K2P0V6',
},
},
} as PickupLocationOption;

it('throws if extension target has no api.target', async () => {
const runner = async () => {
const target: ExtensionTarget = 'purchase.checkout.block.render';
return mount.hook(() => usePickupLocationOptionTarget(), {
extensionApi: {
extension: {target},
target: undefined,
isTargetSelected: createMockStatefulRemoteSubscribable(true),
},
});
};

await expect(runner).rejects.toThrow(ExtensionHasNoTargetError);
});

it('throws if extension target has no api.isTargetSelected', async () => {
const runner = async () => {
const target: ExtensionTarget = 'purchase.checkout.block.render';
return mount.hook(() => usePickupLocationOptionTarget(), {
extensionApi: {
extension: {target},
target: createMockStatefulRemoteSubscribable(pickupLocationOption),
isTargetSelected: undefined,
},
});
};

await expect(runner).rejects.toThrow(ExtensionHasNoTargetError);
});

it('returns the pickup location option target if it exists', async () => {
const target: ExtensionTarget =
'purchase.checkout.pickup-location-option-item.render-after';
const {value} = mount.hook(() => usePickupLocationOptionTarget(), {
extensionApi: {
extension: {target},
target: createMockStatefulRemoteSubscribable(pickupLocationOption),
isTargetSelected: createMockStatefulRemoteSubscribable(true),
},
});

expect(value).toStrictEqual({
pickupLocationOptionTarget: pickupLocationOption,
isTargetSelected: true,
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const data: ReferenceEntityTemplateSchema = {
{
title: 'useShippingOptionTarget',
description:
'Returns the shipping option for the `purchase.checkout.shipping-option-item.render-after` and `purchase.checkout.shipping-option-item.details.render` attached extensions',
'Returns the shipping option for the `purchase.checkout.shipping-option-item.render-after` and `purchase.checkout.shipping-option-item.details.render` attached extensions.',
type: 'UseShippingOptionTargetGeneratedType',
},
{
Expand All @@ -73,13 +73,29 @@ const data: ReferenceEntityTemplateSchema = {
'This API object is provided to extensions registered for the `purchase.checkout.pickup-location-list.render-after` and `purchase.checkout.pickup-location-list.render-after` extension target.',
type: 'PickupLocationListApi',
},
{
title: 'PickupLocationItemApi',
description:
'The API object provided to the `purchase.checkout.pickup-location-option-item.render-after` extension target.',
type: 'PickupLocationItemApi',
},
{
title: 'usePickupLocationOptionTarget',
description:
'Returns the pickup location option for `purchase.checkout.pickup-location-option-item.render-after` attached extensions.',
type: 'UsePickupLocationOptionTargetGeneratedType',
},
],
defaultExample: getHookExample('delivery-group'),
examples: {
description:
'Learn how to use the API with JavaScript (JS) and React. See [React Hooks](../react-hooks) for all available hooks.',
examples: [
getExample('shipping-option-item/default', ['jsx', 'js']),
getExample(
'purchase.checkout.pickup-location-option-item.render-after/default',
['jsx', 'js'],
),
getExample('pickup-point-list/default', ['jsx', 'js']),
getHookExample('delivery-groups'),
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {extension} from '@shopify/ui-extensions/checkout';

export default extension(
'purchase.checkout.pickup-location-option-item.render-after',
(root, {isTargetSelected, target}) => {
const titleText = root.createText(
target.current.title,
);
root.appendChild(titleText);

target.subscribe((updatedTarget) => {
titleText.updateText(
updatedTarget.title || '',
);
});

const selectedText = root.createText(
getSelectedText(isTargetSelected),
);
root.appendChild(selectedText);

isTargetSelected.subscribe(
(updatedSelected) => {
selectedText.updateText(
getSelectedText(updatedSelected),
);
},
);

function getSelectedText(selected) {
return selected
? 'Selected'
: 'Not selected';
}
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
reactExtension,
Text,
usePickupLocationOptionTarget,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
'purchase.checkout.pickup-location-option-item.render-after',
() => <Extension />,
);

function Extension() {
const {
isTargetSelected,
pickupLocationOptionTarget,
} = usePickupLocationOptionTarget();

const title = pickupLocationOptionTarget?.title;

if (isTargetSelected) {
return <Text>{title}</Text>;
}

return null;
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
import {
extension,
Checkbox,
} from '@shopify/ui-extensions/checkout';
import {extension} from '@shopify/ui-extensions/checkout';

// 1. Choose an extension target
export default extension(
'purchase.checkout.shipping-option-item.details.render',
(root, api) => {
// 2. Render a UI
root.appendChild(
root.createComponent(
Checkbox,
{
onChange: onCheckboxChange,
},
'I would like to receive a free gift with my order',
),
(root, {target, isTargetSelected}) => {
const titleText = root.createText(
`Shipping method title: ${target.current.title}`,
);
root.appendChild(titleText);

// 3. Call API methods to modify the checkout
async function onCheckboxChange(isChecked) {
const result =
await api.applyAttributeChange({
key: 'requestedFreeGift',
type: 'updateAttribute',
value: isChecked ? 'yes' : 'no',
});
console.log(
'applyAttributeChange result',
result,
target.subscribe((updatedTarget) => {
titleText.updateText(
`Shipping method title: ${updatedTarget.title}`,
);
});

const selectedText = root.createText(
getSelectedText(isTargetSelected),
);
root.appendChild(selectedText);

isTargetSelected.subscribe(
(updatedSelected) => {
selectedText.updateText(
getSelectedText(updatedSelected),
);
},
);

function getSelectedText(selected) {
return selected
? 'Selected'
: 'Not selected';
}
},
);
Original file line number Diff line number Diff line change
@@ -1,37 +1,23 @@
import {
reactExtension,
Checkbox,
useApplyAttributeChange,
Text,
useShippingOptionTarget,
} from '@shopify/ui-extensions-react/checkout';

// 1. Choose an extension target
export default reactExtension(
'purchase.checkout.shipping-option-item.details.render',
() => <Extension />,
);

function Extension() {
const applyAttributeChange =
useApplyAttributeChange();
const {shippingOptionTarget, isTargetSelected} =
useShippingOptionTarget();
const title = shippingOptionTarget.title;

// 2. Render a UI
return (
<Checkbox onChange={onCheckboxChange}>
I would like to receive a free gift with my
order
</Checkbox>
<Text>
Shipping method: {title} is{' '}
{isTargetSelected ? '' : 'not'} selected.
</Text>
);

// 3. Call API methods to modify the checkout
async function onCheckboxChange(isChecked) {
const result = await applyAttributeChange({
key: 'requestedFreeGift',
type: 'updateAttribute',
value: isChecked ? 'yes' : 'no',
});
console.log(
'applyAttributeChange result',
result,
);
}
}

0 comments on commit 79a64d1

Please sign in to comment.