Skip to content

Commit d13c5b6

Browse files
authored
Merge pull request #2986 from Shopify/06-16-add_editable_cart_property
Add `editable` cart property
2 parents 52b0c82 + 63ada4a commit d13c5b6

File tree

5 files changed

+150
-26
lines changed

5 files changed

+150
-26
lines changed

packages/ui-extensions-react/src/surfaces/point-of-sale/hooks/cart-api.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import type {Cart} from '@shopify/ui-extensions/point-of-sale';
22
import {useApi} from './api';
33
import {useEffect, useRef, useState} from 'react';
4-
import {
5-
StatefulRemoteSubscribable,
6-
makeStatefulSubscribable,
7-
} from '@remote-ui/async-subscription';
4+
import type {StatefulRemoteSubscribable} from '@remote-ui/async-subscription';
5+
import {makeStatefulSubscribable} from '@remote-ui/async-subscription';
86

97
/**
108
* Global instance of the subscribable that is created on the first `useStatefulSubscribableCart` call.
@@ -19,6 +17,34 @@ const isCartApi = (api: any): boolean => {
1917
return 'cart' in api;
2018
};
2119

20+
/**
21+
* A hook for checking if the cart has been determined to be editable.
22+
*
23+
* This hook is stateful and can be used to trigger a re-render when the value of this editable state changes.
24+
*
25+
* If the cart's `editable` property is not set, defaults to `true`.
26+
*
27+
* @returns true if the cart is editable, false otherwise.
28+
*/
29+
export const useCartEditable = (): boolean => {
30+
const statefulSubscribableCart = useStatefulSubscribableCart();
31+
const [editable, setEditable] = useState<boolean>(
32+
statefulSubscribableCart.current.editable ?? true,
33+
);
34+
35+
const unsubscribeRef = useRef<() => void>();
36+
37+
useEffect(() => {
38+
if (!unsubscribeRef.current) {
39+
statefulSubscribableCart.subscribe((cart: Cart) => {
40+
setEditable(cart.editable ?? true);
41+
});
42+
}
43+
}, [statefulSubscribableCart]);
44+
45+
return editable;
46+
};
47+
2248
/**
2349
* A hook utilizing `useState` and the `useStatefulSubscribableCart` function to create a component state.
2450
* @returns this hook returns the latest Cart state which re-renders on change.
@@ -57,6 +83,7 @@ export function useStatefulSubscribableCart(): StatefulRemoteSubscribable<Cart>
5783

5884
return statefulSubscribable;
5985
}
86+
6087
/**
6188
* A function destroying the subscriptions `useStatefulSubscribableCart` has.
6289
*/

packages/ui-extensions-react/src/surfaces/point-of-sale/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export {useApi, useExtensionApi} from './api';
22
export {
33
useCartSubscription,
4+
useCartEditable,
45
useStatefulSubscribableCart,
56
destroyStatefulSubscribableCart,
67
} from './cart-api';

packages/ui-extensions/src/surfaces/point-of-sale/render/api/cart-api/cart-api.ts

Lines changed: 95 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export type CartDiscountType = 'Percentage' | 'FixedAmount' | 'Code';
2121
export type LineItemDiscountType = 'Percentage' | 'FixedAmount';
2222

2323
export interface CartApiContent {
24-
/** Provides a subscription to POS cart changes.
24+
/**
25+
* Provides a subscription to POS cart changes.
2526
* Provides an initial value and a callback to subscribe to value changes. Currently supports only one subscription.
2627
* You can utilize `makeStatefulSubscribable` on a `RemoteSubscribable` to implement multiple subscriptions.
2728
* Using `makeStatefulSubscribable` or the corresponding hooks counts as a subscription.
@@ -31,99 +32,151 @@ export interface CartApiContent {
3132
/** Bulk update the cart
3233
* @param cartState the cart state to set
3334
* @returns the updated cart
35+
*
36+
* @throws CartNotEditableError if the cart is not currently editable.
3437
*/
3538
bulkCartUpdate(cartState: CartUpdateInput): Promise<Cart>;
3639

3740
/** Apply a cart level discount
3841
* @param type the type of discount applied (example: 'Percentage')
3942
* @param title the title attributed with the discount
4043
* @param amount the percentage or fixed monetary amount deducted with the discount. Pass in `undefined` if using discount codes.
44+
*
45+
* @throws CartNotEditableError if the cart is not currently editable.
4146
*/
4247
applyCartDiscount(
4348
type: CartDiscountType,
4449
title: string,
4550
amount?: string,
4651
): Promise<void>;
4752

48-
/** Add a code discount to the cart
53+
/**
54+
* Add a code discount to the cart
4955
* @param code the code for the discount to add to the cart
56+
*
57+
* @throws CartNotEditableError if the cart is not currently editable.
5058
*/
5159
addCartCodeDiscount(code: string): Promise<void>;
5260

53-
/** Remove the cart discount */
61+
/**
62+
* Remove the cart discount
63+
*
64+
* @throws CartNotEditableError if the cart is not currently editable.
65+
*/
5466
removeCartDiscount(): Promise<void>;
5567

56-
/** Remove all cart and line item discounts
68+
/**
69+
* Remove all cart and line item discounts
5770
* @param disableAutomaticDiscounts Whether or not automatic discounts should be enabled after removing the discounts.
71+
*
72+
* @throws CartNotEditableError if the cart is not currently editable.
5873
*/
5974
removeAllDiscounts(disableAutomaticDiscounts: boolean): Promise<void>;
6075

61-
/** Clear the cart */
76+
/**
77+
* Clear the cart
78+
*
79+
* @throws CartNotEditableError if the cart is not currently editable.
80+
*/
6281
clearCart(): Promise<void>;
6382

64-
/** Set the customer in the cart
83+
/**
84+
* Set the customer in the cart
6585
* @param customer the customer object to add to the cart
86+
*
87+
* @throws CartNotEditableError if the cart is not currently editable.
6688
*/
6789
setCustomer(customer: Customer): Promise<void>;
6890

69-
/** Remove the current customer from the cart */
91+
/**
92+
* Remove the current customer from the cart
93+
*
94+
* @throws CartNotEditableError if the cart is not currently editable.
95+
*/
7096
removeCustomer(): Promise<void>;
7197

72-
/** Add a custom sale to the cart
98+
/**
99+
* Add a custom sale to the cart
73100
* @param customSale the custom sale object to add to the cart
74101
* @returns {string} the uuid of the line item added
102+
*
103+
* @throws CartNotEditableError if the cart is not currently editable.
75104
*/
76105
addCustomSale(customSale: CustomSale): Promise<string>;
77106

78-
/** Add a line item by variant ID to the cart
107+
/**
108+
* Add a line item by variant ID to the cart
79109
* @param variantId the product variant's numeric ID to add to the cart
80110
* @param quantity the number of this variant to add to the cart
81111
* @returns {string} the uuid of the line item added
112+
*
113+
* @throws CartNotEditableError if the cart is not currently editable.
82114
*/
83115
addLineItem(variantId: number, quantity: number): Promise<string>;
84116

85-
/** Remove the line item at this uuid from the cart
117+
/**
118+
* Remove the line item at this uuid from the cart
86119
* @param uuid the uuid of the line item that should be removed
120+
*
121+
* @throws CartNotEditableError if the cart is not currently editable.
87122
*/
88123
removeLineItem(uuid: string): Promise<void>;
89124

90-
/** Adds custom properties to the cart
125+
/**
126+
* Adds custom properties to the cart
91127
* @param properties the custom key to value object to attribute to the cart
128+
*
129+
* @throws CartNotEditableError if the cart is not currently editable.
92130
*/
93131
addCartProperties(properties: Record<string, string>): Promise<void>;
94132

95-
/** Removes the specified cart properties
133+
/**
134+
* Removes the specified cart properties
96135
* @param keys the collection of keys to be removed from the cart properties
136+
*
137+
* @throws CartNotEditableError if the cart is not currently editable.
97138
*/
98139
removeCartProperties(keys: string[]): Promise<void>;
99140

100-
/** Adds custom properties to the specified line item
141+
/**
142+
* Adds custom properties to the specified line item
101143
* @param uuid the uuid of the line item to which the properties should be stringd
102144
* @param properties the custom key to value object to attribute to the line item
145+
*
146+
* @throws CartNotEditableError if the cart is not currently editable.
103147
*/
104148
addLineItemProperties(
105149
uuid: string,
106150
properties: Record<string, string>,
107151
): Promise<void>;
108152

109-
/** Adds custom properties to multiple line items at the same time.
153+
/**
154+
* Adds custom properties to multiple line items at the same time.
110155
* @param lineItemProperties the collection of custom line item properties to apply to their respective line items.
156+
*
157+
* @throws CartNotEditableError if the cart is not currently editable.
111158
*/
112159
bulkAddLineItemProperties(
113160
lineItemProperties: SetLineItemPropertiesInput[],
114161
): Promise<void>;
115162

116-
/** Removes the specified line item properties
163+
/**
164+
* Removes the specified line item properties
117165
* @param uuid the uuid of the line item to which the properties should be removed
118166
* @param keys the collection of keys to be removed from the line item properties
167+
*
168+
* @throws CartNotEditableError if the cart is not currently editable.
119169
*/
120170
removeLineItemProperties(uuid: string, keys: string[]): Promise<void>;
121171

122-
/** Add a discount on a line item to the cart
172+
/**
173+
* Add a discount on a line item to the cart
123174
* @param uuid the uuid of the line item that should receive a discount
124175
* @param type the type of discount applied (example: 'Percentage')
125176
* @param title the title attributed with the discount
126177
* @param amount the percentage or fixed monetary amount deducted with the discout
178+
*
179+
* @throws CartNotEditableError if the cart is not currently editable.
127180
*/
128181
setLineItemDiscount(
129182
uuid: string,
@@ -132,45 +185,65 @@ export interface CartApiContent {
132185
amount: string,
133186
): Promise<void>;
134187

135-
/** Set line item discounts to multiple line items at the same time.
188+
/**
189+
* Set line item discounts to multiple line items at the same time.
136190
* @param lineItemDiscounts a map of discounts to add. They key is the uuid of the line item you want to add the discount to. The value is the discount input.
191+
*
192+
* @throws CartNotEditableError if the cart is not currently editable.
137193
*/
138194
bulkSetLineItemDiscounts(
139195
lineItemDiscounts: SetLineItemDiscountInput[],
140196
): Promise<void>;
141197

142-
/** Sets an attributed staff to all line items in the cart.
198+
/**
199+
* Sets an attributed staff to all line items in the cart.
143200
* @param staffId the ID of the staff. Providing undefined will clear the attributed staff from all line items.
201+
*
202+
* @throws CartNotEditableError if the cart is not currently editable.
144203
*/
145204
setAttributedStaff(staffId: number | undefined): Promise<void>;
146205

147-
/** Sets an attributed staff to a specific line items in the cart.
206+
/**
207+
* Sets an attributed staff to a specific line items in the cart.
148208
* @param staffId the ID of the staff. Providing undefined will clear the attributed staff on the line item.
149209
* @param lineItemUuid the UUID of the line item.
210+
*
211+
* @throws CartNotEditableError if the cart is not currently editable.
150212
*/
151213
setAttributedStaffToLineItem(
152214
staffId: number | undefined,
153215
lineItemUuid: string,
154216
): Promise<void>;
155217

156-
/** Remove all discounts from a line item
218+
/**
219+
* Remove all discounts from a line item
157220
* @param uuid the uuid of the line item whose discounts should be removed
221+
*
222+
* @throws CartNotEditableError if the cart is not currently editable.
158223
*/
159224
removeLineItemDiscount(uuid: string): Promise<void>;
160225

161-
/** Add an address to the customer (Customer must be present)
226+
/**
227+
* Add an address to the customer (Customer must be present)
162228
* @param address the address object to add to the customer in cart
229+
*
230+
* @throws CartNotEditableError if the cart is not currently editable.
163231
*/
164232
addAddress(address: Address): Promise<void>;
165233

166234
/**
167235
* Delete an address from the customer (Customer must be present)
168236
* @param addressId the address ID to delete
237+
*
238+
* @throws CartNotEditableError if the cart is not currently editable.
169239
*/
170240
deleteAddress(addressId: number): Promise<void>;
171241

172-
/** Update the default address for the customer (Customer must be present)
242+
/**
243+
* Update the default address for the customer (Customer must be present)
173244
* @param addressId the address ID to set as the default address
245+
*
246+
* @throws CartNotEditableError if the cart is not currently editable.
174247
*/
175248
updateDefaultAddress(addressId: number): Promise<void>;
176249
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {CartApiContent} from './cart-api';
2+
3+
type CartApiMethods = Omit<CartApiContent, 'subscribable'>;
4+
5+
/**
6+
* An error thrown when an attempt to edit the cart is made while it is not editable.
7+
*/
8+
export class CartNotEditableError extends Error {
9+
constructor(
10+
/** The name of the method that attempted to modify the cart */
11+
public methodName: keyof CartApiMethods,
12+
) {
13+
super(
14+
`attempted to modify cart when not editable (calling "${methodName}")`,
15+
);
16+
}
17+
}

packages/ui-extensions/src/surfaces/point-of-sale/types/cart.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import {CountryCode} from './country-code';
22
import {TaxLine} from './tax-line';
33

44
export interface Cart {
5+
/**
6+
* Indicates whether the cart is currently editable.
7+
*
8+
* An undefined value should be treated as `true` for backward compatibility.
9+
*/
10+
editable?: boolean;
511
subtotal: string;
612
taxTotal: string;
713
grandTotal: string;

0 commit comments

Comments
 (0)