Skip to content

Commit 324f17a

Browse files
committed
wire up certificate upgrade link to actual API endpoints
1 parent ad4aa8b commit 324f17a

4 files changed

Lines changed: 115 additions & 1 deletion

File tree

frontends/api/src/mitxonline/clients.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
B2bApi,
3+
BasketsApi,
34
CoursesApi,
45
CourseCertificatesApi,
56
EnrollmentsApi,
@@ -29,6 +30,7 @@ const BASE_PATH =
2930
const usersApi = new UsersApi(undefined, BASE_PATH, axiosInstance)
3031
const countriesApi = new CountriesApi(undefined, BASE_PATH, axiosInstance)
3132
const b2bApi = new B2bApi(undefined, BASE_PATH, axiosInstance)
33+
const basketsApi = new BasketsApi(undefined, BASE_PATH, axiosInstance)
3234
const programsApi = new ProgramsApi(undefined, BASE_PATH, axiosInstance)
3335
const programCollectionsApi = new ProgramCollectionsApi(
3436
undefined,
@@ -70,6 +72,7 @@ export {
7072
usersApi,
7173
countriesApi,
7274
b2bApi,
75+
basketsApi,
7376
courseRunEnrollmentsApi,
7477
programEnrollmentsApi,
7578
programsApi,
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { basketQueries } from "./queries"
2+
import { useMutation, useQueryClient } from "@tanstack/react-query"
3+
import { basketsApi } from "../../clients"
4+
import type { BasketWithProduct } from "@mitodl/mitxonline-api-axios/v2"
5+
6+
/**
7+
* Hook to add a product to the user's basket.
8+
* Creates or updates the basket, adding the specified product.
9+
* On success, automatically redirects to the checkout page.
10+
*/
11+
const useAddToBasket = () => {
12+
const queryClient = useQueryClient()
13+
return useMutation({
14+
mutationFn: async (productId: number): Promise<BasketWithProduct> => {
15+
const response = await basketsApi.basketsCreateFromProductCreate({
16+
product_id: productId,
17+
})
18+
return response.data
19+
},
20+
onSuccess: async () => {
21+
// Invalidate checkout query to ensure fresh data
22+
queryClient.invalidateQueries({
23+
queryKey: basketQueries.checkout().queryKey,
24+
})
25+
26+
// Get checkout data and submit form to CyberSource
27+
try {
28+
const checkoutResponse = await basketsApi.basketsCheckoutRetrieve()
29+
const { url, payload } = checkoutResponse.data
30+
31+
if (url && payload) {
32+
// Create a form and submit it to CyberSource
33+
const form = document.createElement("form")
34+
form.method = "POST"
35+
form.action = url
36+
37+
// Add all payload fields as hidden inputs
38+
Object.entries(payload).forEach(([key, value]) => {
39+
const input = document.createElement("input")
40+
input.type = "hidden"
41+
input.name = key
42+
input.value = String(value)
43+
form.appendChild(input)
44+
})
45+
46+
document.body.appendChild(form)
47+
form.submit()
48+
}
49+
} catch (error) {
50+
console.error("Failed to get checkout URL:", error)
51+
}
52+
},
53+
})
54+
}
55+
56+
/**
57+
* Hook to clear the user's basket.
58+
*/
59+
const useClearBasket = () => {
60+
const queryClient = useQueryClient()
61+
return useMutation({
62+
mutationFn: async (): Promise<void> => {
63+
await basketsApi.basketsClearDestroy()
64+
},
65+
onSuccess: () => {
66+
queryClient.invalidateQueries({
67+
queryKey: basketQueries.checkout().queryKey,
68+
})
69+
},
70+
})
71+
}
72+
73+
export { basketQueries, useAddToBasket, useClearBasket }
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { queryOptions } from "@tanstack/react-query"
2+
import type { CheckoutPayload } from "@mitodl/mitxonline-api-axios/v2"
3+
4+
import { basketsApi } from "../../clients"
5+
6+
const basketKeys = {
7+
root: ["mitxonline", "baskets"],
8+
checkout: () => [...basketKeys.root, "checkout"],
9+
}
10+
11+
const basketQueries = {
12+
checkout: () =>
13+
queryOptions({
14+
queryKey: basketKeys.checkout(),
15+
queryFn: async (): Promise<CheckoutPayload> => {
16+
const response = await basketsApi.basketsCheckoutRetrieve()
17+
return response.data
18+
},
19+
// Don't cache checkout data - always fetch fresh
20+
staleTime: 0,
21+
gcTime: 0,
22+
}),
23+
}
24+
25+
export { basketKeys, basketQueries }

frontends/main/src/app-pages/DashboardPage/CoursewareDisplay/DashboardCard.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { useCreateB2bEnrollment } from "api/mitxonline-hooks/enrollment"
2929
import { mitxUserQueries } from "api/mitxonline-hooks/user"
3030
import { useQuery } from "@tanstack/react-query"
3131
import { programView } from "@/common/urls"
32+
import { useAddToBasket } from "api/mitxonline-hooks/baskets"
3233
import { EnrollmentStatus, getBestRun, getEnrollmentStatus } from "./helpers"
3334
import {
3435
CourseWithCourseRunsSerializerV2,
@@ -421,13 +422,24 @@ const UpgradeBanner: React.FC<
421422
canUpgrade: boolean
422423
certificateUpgradeDeadline?: string | null
423424
certificateUpgradePrice?: string | null
425+
productId?: number | null
424426
} & React.HTMLAttributes<HTMLDivElement>
425427
> = ({
426428
canUpgrade,
427429
certificateUpgradeDeadline,
428430
certificateUpgradePrice,
431+
productId,
429432
...others
430433
}) => {
434+
const addToBasket = useAddToBasket()
435+
436+
const handleUpgradeClick = async (e: React.MouseEvent<HTMLAnchorElement>) => {
437+
e.preventDefault()
438+
if (!productId || addToBasket.isPending) return
439+
440+
addToBasket.mutate(productId)
441+
}
442+
431443
if (!canUpgrade || !certificateUpgradeDeadline || !certificateUpgradePrice) {
432444
return null
433445
}
@@ -437,7 +449,7 @@ const UpgradeBanner: React.FC<
437449
const formattedPrice = `$${certificateUpgradePrice}`
438450
return (
439451
<SubtitleLinkRoot {...others}>
440-
<SubtitleLink href="#">
452+
<SubtitleLink href="#" onClick={handleUpgradeClick}>
441453
<RiAddLine size="16px" />
442454
Add a certificate for {formattedPrice}
443455
</SubtitleLink>
@@ -691,6 +703,7 @@ const DashboardCard: React.FC<DashboardCardProps> = ({
691703
canUpgrade={run?.is_upgradable ?? false}
692704
certificateUpgradeDeadline={run?.upgrade_deadline}
693705
certificateUpgradePrice={run?.products?.[0]?.price}
706+
productId={run?.products?.[0]?.id}
694707
/>
695708
) : null}
696709
</>

0 commit comments

Comments
 (0)