Skip to content

Commit f696011

Browse files
committed
refactor: membership page to support plans and pricign options
1 parent 7856808 commit f696011

File tree

6 files changed

+82
-55
lines changed

6 files changed

+82
-55
lines changed

examples/memberships/src/app/(store)/membership/AddToCart.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ import { addToCart } from "./add-to-cart-action";
66
export function AddToCartButton({
77
offeringId,
88
planId,
9+
pricingOptionId,
910
}: {
1011
offeringId: string;
1112
planId: string;
13+
pricingOptionId: string;
1214
}) {
1315
const [isPending, startTransition] = useTransition();
1416

1517
const handleClick = () => {
1618
startTransition(async () => {
1719
try {
18-
const result = await addToCart({ offeringId, planId });
20+
const result = await addToCart({ offeringId, planId, pricingOptionId });
1921
} catch (error) {
2022
console.error("Error adding to cart:", error);
2123
}

examples/memberships/src/app/(store)/membership/add-to-cart-action.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import { revalidateTag } from "next/cache";
88
export async function addToCart({
99
offeringId,
1010
planId,
11+
pricingOptionId,
1112
}: {
1213
offeringId: string;
1314
planId: string;
15+
pricingOptionId: string;
1416
}) {
1517
const client = createElasticPathClient();
1618
const cartCookie = (await cookies()).get(CART_COOKIE_NAME);
@@ -26,7 +28,7 @@ export async function addToCart({
2628
id: offeringId,
2729
subscription_configuration: {
2830
plan: planId,
29-
pricing_option: planId,
31+
pricing_option: pricingOptionId,
3032
},
3133
},
3234
};

examples/memberships/src/app/(store)/membership/page.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import MembershipTable from "../../../components/membership/MembershipTable";
55
export default async function MembershipPage() {
66
const client = createElasticPathClient();
77

8+
console.log(
9+
"process.env.NEXT_PUBLIC_SUBSCRIPTION_OFFERING_ID: ",
10+
process.env.NEXT_PUBLIC_SUBSCRIPTION_OFFERING_ID,
11+
);
12+
813
const offeringResponse = await getOffering({
914
client: client,
1015
path: {
@@ -15,15 +20,11 @@ export default async function MembershipPage() {
1520
},
1621
});
1722

18-
if (
19-
!offeringResponse?.data?.data
20-
) {
23+
console.log("offeringResponse: ", offeringResponse, client);
24+
25+
if (!offeringResponse?.data?.data) {
2126
return <div>Offering not found</div>;
2227
}
2328

24-
return (
25-
<MembershipTable
26-
offering={offeringResponse.data}
27-
/>
28-
);
29+
return <MembershipTable offering={offeringResponse.data} />;
2930
}

examples/memberships/src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ export default async function RootLayout({
66
}: {
77
children: ReactNode;
88
}) {
9-
return <>{children}</>;
9+
return children;
1010
}

examples/memberships/src/components/membership/MembershipTable.tsx

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,18 @@ const cartErrorOptions = {
3232
} as const;
3333

3434
const MembershipTable: React.FC<IMembershipTableProps> = ({ offering }) => {
35-
const combinedProducts =
36-
(offering.included as { products?: Array<{ id?: string; attributes?: { name?: string; feature_configurations?: Record<string, unknown> }; meta?: { display_price?: { without_tax?: { formatted?: string } } } }> })?.products ?? [];
37-
const pricingOptions = offering.included?.plans ?? [];
35+
const plans = offering.included?.plans ?? [];
36+
const pricingOptions = offering.included?.pricing_options ?? [];
3837

3938
const [selectedPricingOption, setSelectedPricingOption] = useState<string>(
4039
pricingOptions[0]?.id ?? "",
4140
);
4241

42+
// Get the full pricing option object to access prices per plan
43+
const selectedPricingOptionData = pricingOptions.find(
44+
(opt) => opt.id === selectedPricingOption,
45+
);
46+
4347
const features = offering.included?.features;
4448
const tagToIdsMap = new Map<string, Set<string>>();
4549
offering.included?.features?.forEach((feature) => {
@@ -62,18 +66,18 @@ const MembershipTable: React.FC<IMembershipTableProps> = ({ offering }) => {
6266
);
6367
};
6468

65-
console.log("selectedPricingOption: ", selectedPricingOption);
66-
6769
const handleClick = ({
6870
planId,
71+
pricingOptionId,
6972
offeringId,
7073
}: {
7174
planId: string;
75+
pricingOptionId: string;
7276
offeringId: string;
7377
}) => {
7478
startTransition(async () => {
7579
try {
76-
const result = await addToCart({ offeringId, planId });
80+
const result = await addToCart({ offeringId, planId, pricingOptionId });
7781

7882
if (result.error) {
7983
notify({
@@ -118,50 +122,61 @@ const MembershipTable: React.FC<IMembershipTableProps> = ({ offering }) => {
118122
});
119123
};
120124

125+
// Helper to get price for a specific plan from the selected pricing option
126+
const getPriceForPlan = (planId: string) => {
127+
const priceData = selectedPricingOptionData?.meta?.prices?.[planId];
128+
return priceData?.display_price?.without_tax?.formatted;
129+
};
130+
121131
return (
122132
<div className="overflow-x-auto p-4">
123133
<div className="w-full mx-auto py-6 px-[6rem]">
124134
<h2 className="text-4xl font-semibold text-center mb-4">Membership</h2>
125135
<p className="text-xl font-semibold text-center mb-6">
126-
Choose a membership plan thats best for you
136+
Choose a membership plan that's best for you
127137
</p>
128138

129-
<Select
130-
onValueChange={setSelectedPricingOption}
131-
defaultValue={pricingOptions[0]?.id}
132-
>
133-
<SelectTrigger>
134-
<SelectValue placeholder="Select a pricing option" />
135-
</SelectTrigger>
136-
<SelectContent>
137-
{pricingOptions?.map((plan, index) => {
138-
return (
139-
<SelectItem value={plan.id!} key={plan.id!}>
140-
{plan.attributes?.name}
141-
</SelectItem>
142-
);
143-
})}
144-
</SelectContent>
145-
</Select>
139+
<div className="flex gap-4 mb-6 max-w-md mx-auto">
140+
<div className="flex-1">
141+
<label className="block text-sm font-medium mb-2">
142+
Pricing Option
143+
</label>
144+
<Select
145+
onValueChange={setSelectedPricingOption}
146+
defaultValue={pricingOptions[0]?.id}
147+
>
148+
<SelectTrigger>
149+
<SelectValue placeholder="Select a pricing option" />
150+
</SelectTrigger>
151+
<SelectContent>
152+
{pricingOptions?.map((option) => {
153+
return (
154+
<SelectItem value={option.id!} key={option.id!}>
155+
{option.attributes?.name}
156+
</SelectItem>
157+
);
158+
})}
159+
</SelectContent>
160+
</Select>
161+
</div>
162+
</div>
146163

147164
<div className="overflow-x-auto">
148165
<table className="w-full border-collapse">
149166
<thead>
150167
<tr className="">
151168
<th className="p-4 text-left"></th>
152-
{combinedProducts.map((product: typeof combinedProducts[number], index: number) => (
153-
<th key={index} className="p-4 text-center">
154-
<>
155-
<div className="text-lg font-semibold">
156-
{product?.attributes?.name}
157-
</div>
158-
<div className="text-sm font-normal text-[#62687A]">
159-
Standard, 12-month
160-
</div>
161-
<div className="text-base font-medium">
162-
{product?.meta?.display_price?.without_tax?.formatted}
163-
</div>
164-
</>
169+
{plans.map((plan, index: number) => (
170+
<th key={plan.id ?? index} className="p-4 text-center">
171+
<div className="text-lg font-semibold">
172+
{plan.attributes?.name}
173+
</div>
174+
<div className="text-sm font-normal text-[#62687A]">
175+
{selectedPricingOptionData?.attributes?.name ?? ""}
176+
</div>
177+
<div className="text-base font-medium">
178+
{getPriceForPlan(plan.id!) ?? "N/A"}
179+
</div>
165180
</th>
166181
))}
167182
</tr>
@@ -204,16 +219,19 @@ const MembershipTable: React.FC<IMembershipTableProps> = ({ offering }) => {
204219
</div>
205220
)}
206221
</td>
207-
{combinedProducts.map((product: typeof combinedProducts[number], productIndex: number) => (
208-
<td key={productIndex} className="py-2 px-2 text-center">
222+
{plans.map((plan, planIndex: number) => (
223+
<td
224+
key={plan.id ?? planIndex}
225+
className="py-2 px-2 text-center"
226+
>
209227
<div className="flex justify-center items-center">
210228
{feature.attributes.configuration.type === "access" &&
211229
Array.from(
212230
tagToIdsMap.get(
213231
feature.attributes.configuration.tag,
214232
) || [],
215233
).some((featureId) =>
216-
product?.attributes?.feature_configurations?.hasOwnProperty(
234+
plan.attributes?.feature_configurations?.hasOwnProperty(
217235
featureId,
218236
),
219237
) ? (
@@ -228,15 +246,16 @@ const MembershipTable: React.FC<IMembershipTableProps> = ({ offering }) => {
228246
))}
229247
<tr>
230248
<td className="p-4"></td>
231-
{combinedProducts.map((product, productIndex: number) => {
249+
{plans.map((plan) => {
232250
const offeringId = offering?.data?.id;
233251
return (
234-
<td key={productIndex} className="p-4 text-center">
252+
<td key={plan.id} className="p-4 text-center">
235253
<Button
236-
disabled={isPending}
254+
disabled={isPending || !selectedPricingOption}
237255
onClick={() =>
238256
handleClick({
239-
planId: selectedPricingOption!,
257+
planId: plan.id!,
258+
pricingOptionId: selectedPricingOption!,
240259
offeringId: offeringId!,
241260
})
242261
}

examples/memberships/src/lib/middleware/implicit-auth-middleware.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import { COOKIE_PREFIX_KEY } from "../cookie-constants";
1414
export const epccEndpoint = process.env.NEXT_PUBLIC_EPCC_ENDPOINT_URL;
1515
const clientId = process.env.NEXT_PUBLIC_EPCC_CLIENT_ID;
1616

17+
console.log("epccEndpoint: ", epccEndpoint);
18+
console.log("clientId: ", clientId);
19+
1720
export const implicitAuthMiddleware: Middleware = async (
1821
req,
1922
_event,

0 commit comments

Comments
 (0)