Skip to content

Commit 3456601

Browse files
authored
Merge pull request #2906 from Shopify/js-new-loyalty-extension-docs
Loyalty extension example overhaul
2 parents fc7fca5 + aeffd77 commit 3456601

File tree

11 files changed

+308
-381
lines changed

11 files changed

+308
-381
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import React from 'react';
2+
3+
import {
4+
reactExtension,
5+
POSBlock,
6+
Text,
7+
POSBlockRow,
8+
useApi,
9+
Button,
10+
} from '@shopify/ui-extensions-react/point-of-sale';
11+
import {useFetchLoyaltyPoints} from './useFetchLoyaltyPoints';
12+
13+
export const DISCOUNT_NAME = 'Loyalty-Direct';
14+
15+
// [START customer-details.block.component]
16+
// 2. Implement the `CustomerDetailsBlock` component
17+
const CustomerDetailsBlock = () => {
18+
// [END customer-details.block.component]
19+
20+
// [START customer-details.block.api]
21+
// 3. Setup the api
22+
const api = useApi<'pos.customer-details.block.render'>();
23+
// [END customer-details.block.api]
24+
25+
// [START customer-details.block.fetch-loyalty-points]
26+
// 4. Fetch the loyalty points and filter available discounts using the useFetchLoyaltyPoints hook
27+
const {loading, pointsTotal, availableDiscounts} = useFetchLoyaltyPoints(
28+
api.customer.id,
29+
);
30+
// [END customer-details.block.fetch-loyalty-points]
31+
32+
// [START customer-details.block.apply-discount]
33+
// 5. Implement the applyDiscount function
34+
const applyDiscount = (discountValue: number, pointsRequired: number) => {
35+
if (pointsTotal) {
36+
api.cart.applyCartDiscount(
37+
'Percentage',
38+
`${DISCOUNT_NAME}.${pointsTotal - pointsRequired}`,
39+
String(discountValue),
40+
);
41+
42+
api.toast.show(`${discountValue}% off applied`);
43+
} else {
44+
api.toast.show(`Error applying discount`);
45+
}
46+
};
47+
// [END customer-details.block.apply-discount]
48+
49+
// [START customer-details.block.loading-error]
50+
// 7. Handle error and loading states
51+
if (loading) {
52+
return (
53+
<POSBlock>
54+
<POSBlockRow>
55+
<Text>Loading...</Text>
56+
</POSBlockRow>
57+
</POSBlock>
58+
);
59+
}
60+
61+
if (pointsTotal === null) {
62+
return (
63+
<POSBlock>
64+
<POSBlockRow>
65+
<Text color="TextWarning">Unable to fetch loyalty points.</Text>
66+
</POSBlockRow>
67+
</POSBlock>
68+
);
69+
}
70+
// [END customer-details.block.loading-error]
71+
72+
// [START customer-details.block.render-implementation]
73+
// 6. Render the CustomerDetailsBlock component to display available points and discounts
74+
return (
75+
<POSBlock>
76+
<POSBlockRow>
77+
<Text
78+
variant="body"
79+
color={pointsTotal > 0 ? 'TextSuccess' : 'TextWarning'}
80+
>
81+
Point balance: {pointsTotal}
82+
</Text>
83+
</POSBlockRow>
84+
{availableDiscounts.length > 0 ? (
85+
<POSBlockRow>
86+
<Text variant="body">Available Discounts:</Text>
87+
{availableDiscounts.map((tier, index) => (
88+
<POSBlockRow key={`${tier.pointsRequired}-${index}`}>
89+
<Button
90+
title={`Redeem $${tier.discountValue} discount (${tier.pointsRequired} points)`}
91+
type="primary"
92+
onPress={() =>
93+
applyDiscount(tier.discountValue, tier.pointsRequired)
94+
}
95+
/>
96+
</POSBlockRow>
97+
))}
98+
</POSBlockRow>
99+
) : (
100+
<POSBlockRow>
101+
<Text variant="body" color="TextWarning">
102+
No available discounts.
103+
</Text>
104+
</POSBlockRow>
105+
)}
106+
</POSBlock>
107+
);
108+
// [END customer-details.block.render-implementation]
109+
};
110+
111+
// [START customer-details.block.render-extension]
112+
// 1. Render the CustomerDetailsBlock component at the `pos.customer-details.block.render` target
113+
export default reactExtension('pos.customer-details.block.render', () => (
114+
<CustomerDetailsBlock />
115+
));
116+
// [END customer-details.block.render-extension]

packages/ui-extensions/docs/surfaces/point-of-sale/mdxExamples/loyalty-example/LoyaltyPointsBlock.tsx

Lines changed: 0 additions & 106 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import {extension} from '@shopify/ui-extensions/point-of-sale';
2+
import {DISCOUNT_NAME} from './CustomerDetailsBlock';
3+
import {fetchLoyaltyPoints} from './useFetchLoyaltyPoints';
4+
5+
// [START transaction-complete.event.observe.implementation]
6+
// 1. Implement the `pos.transaction-complete.event.observe` extension target
7+
export default extension(
8+
'pos.transaction-complete.event.observe',
9+
async (data) => {
10+
// [END transaction-complete.event.observe.implementation]
11+
12+
// [START transaction-complete.event.observe.find-loyalty-discount]
13+
// 3. Look for any loyalty discounts in the transaction and redeem the points
14+
const loyaltyDiscount = data.transaction.discounts?.find((discount) =>
15+
discount.discountDescription?.includes(DISCOUNT_NAME),
16+
);
17+
18+
const parsePointsLeft = (discountName: string) => {
19+
const pointsLeft = discountName.split('.')[1];
20+
return parseInt(pointsLeft);
21+
};
22+
23+
if (
24+
loyaltyDiscount &&
25+
loyaltyDiscount.discountDescription &&
26+
data.transaction.customer
27+
) {
28+
const pointsLeft = parsePointsLeft(loyaltyDiscount.discountDescription);
29+
updateLoyaltyPoints(data.transaction.customer.id, pointsLeft);
30+
}
31+
// [END transaction-complete.event.observe.find-loyalty-discount]
32+
33+
// [START transaction-complete.event.observe.update-points-earned]
34+
// 4. If no loyalty discount is found, apply the points earned
35+
if (!loyaltyDiscount && data.transaction.customer) {
36+
const pointsEarned = Math.ceil(data.transaction.subtotal.amount);
37+
const currentPoints =
38+
(await fetchLoyaltyPoints(data.transaction.customer.id)) ?? 0;
39+
updateLoyaltyPoints(
40+
data.transaction.customer.id,
41+
currentPoints + pointsEarned,
42+
);
43+
}
44+
// [END transaction-complete.event.observe.update-points-earned]
45+
return {};
46+
},
47+
);
48+
49+
// [START transaction-complete.event.observe.direct-api]
50+
// 2. Implement the updateLoyaltyPoints function using Direct API graphql mutation
51+
const updateLoyaltyPoints = async (
52+
customerId: number,
53+
newPointsBalance: number,
54+
) => {
55+
await fetch('shopify:admin/api/graphql.json', {
56+
method: 'POST',
57+
body: JSON.stringify({
58+
query: `
59+
mutation MetafieldsSet($metafields: [MetafieldsSetInput!]!) {
60+
metafieldsSet(metafields: $metafields) {
61+
metafields {
62+
key
63+
namespace
64+
value
65+
createdAt
66+
updatedAt
67+
}
68+
}
69+
}
70+
`,
71+
variables: {
72+
metafields: [
73+
{
74+
key: 'loyalty_direct_points',
75+
namespace: 'custom',
76+
ownerId: `gid://shopify/Customer/${customerId}`,
77+
value: `${newPointsBalance}`,
78+
type: 'number_integer',
79+
},
80+
],
81+
},
82+
}),
83+
});
84+
};
85+
// [END transaction-complete.event.observe.direct-api]

packages/ui-extensions/docs/surfaces/point-of-sale/mdxExamples/loyalty-example/applyDiscount.tsx

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)