Skip to content

Commit 94f4f66

Browse files
committed
Add code examples of POS subscriptions UI extension
1 parent 78318cc commit 94f4f66

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {render} from 'preact';
2+
import {useState, useEffect} from 'preact/hooks';
3+
4+
// [START modal.fetch]
5+
async function fetchSellingPlans(variantId) {
6+
const requestBody = {
7+
query: `#graphql
8+
query GetSellingPlans($variantId: ID!) {
9+
productVariant(id: $variantId) {
10+
sellingPlanGroups(first: 10) {
11+
nodes {
12+
name
13+
sellingPlans(first: 10) {
14+
nodes {
15+
id
16+
name
17+
category
18+
}
19+
}
20+
}
21+
}
22+
}
23+
}
24+
`,
25+
variables: { variantId: `gid://shopify/ProductVariant/${variantId}`},
26+
};
27+
28+
const res = await fetch('shopify:admin/api/graphql.json', {
29+
method: 'POST',
30+
body: JSON.stringify(requestBody),
31+
});
32+
return res.json();
33+
}
34+
// [END modal.fetch]
35+
36+
export default function extension() {
37+
render(<Modal />, document.body);
38+
}
39+
40+
function Modal() {
41+
// For this sample, we'll just use the first selling plan item
42+
const sellingPlanItem = shopify.cart.current.value.lineItems
43+
.find(lineItem => lineItem.hasSellingPlanGroups === true)
44+
45+
const [response, setResponse] = useState(undefined)
46+
47+
useEffect(() => {
48+
async function getSellingPlans() {
49+
setResponse(await fetchSellingPlans(sellingPlanItem?.variantId))
50+
}
51+
getSellingPlans()
52+
}, [sellingPlanItem])
53+
54+
// [START modal.handle-click]
55+
const handleClick = (plan) => {
56+
shopify.cart.addLineItemSellingPlan({
57+
lineItemUuid: sellingPlanItem.uuid,
58+
// convert from GID to ID
59+
sellingPlanId: Number(plan.id.split('/').pop()),
60+
sellingPlanName: plan.name,
61+
})
62+
window.close()
63+
}
64+
// [END modal.handle-click]
65+
66+
return (
67+
<s-page heading='POS modal'>
68+
<s-scroll-box>
69+
<s-box padding="small">
70+
{response?.data.productVariant.sellingPlanGroups.nodes.map(group => {
71+
return (
72+
<s-section key={`${group.name}-section`} heading={group.name}>
73+
{group.sellingPlans.nodes.map(plan => {
74+
return (
75+
<s-clickable key={`${plan.name}-clickable`} onClick={() => {
76+
handleClick(plan)
77+
}}>
78+
<s-text key={`${plan.name}-text`}>{plan.name}</s-text>
79+
</s-clickable>
80+
)
81+
})}
82+
</s-section>
83+
)
84+
})}
85+
</s-box>
86+
</s-scroll-box>
87+
</s-page>
88+
);
89+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {render} from 'preact';
2+
import {useState} from 'preact/hooks';
3+
4+
export default function extension() {
5+
render(<Tile />, document.body);
6+
}
7+
8+
function Tile() {
9+
const [sellingPlanEligible, setSellingPlanEligible] = useState(false);
10+
11+
// [START tile.subscribe]
12+
shopify.cart.current.subscribe(cart => {
13+
const sellingPlanEligibleLineItems = cart.lineItems.filter(lineItem => lineItem.hasSellingPlanGroups === true)
14+
15+
setSellingPlanEligible(sellingPlanEligibleLineItems.length > 0)
16+
})
17+
// [END tile.subscribe]
18+
19+
return (
20+
<s-tile
21+
heading={"Subscriptions"}
22+
subheading={sellingPlanEligible
23+
? "Subscriptions available"
24+
: "Subscriptions not available"
25+
}
26+
// [START tile.disabled]
27+
disabled={!sellingPlanEligible}
28+
// [END tile.disabled]
29+
onClick={() => shopify.action.presentModal()}
30+
/>
31+
);
32+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
api_version = "2025-10"
2+
3+
[[extensions]]
4+
type = "ui_extension"
5+
name = "Subscription Tutorial"
6+
handle = "subscription-tutorial"
7+
description = "POS UI extension subscription tutorial"
8+
9+
[[extensions.targeting]]
10+
module = "./src/Tile.jsx"
11+
target = "pos.home.tile.render"
12+
13+
[[extensions.targeting]]
14+
module = "./src/Modal.jsx"
15+
target = "pos.home.modal.render"

0 commit comments

Comments
 (0)