Skip to content

Commit 325cfd5

Browse files
committed
feat: update purchase summary sidebar
1 parent 8fecef0 commit 325cfd5

File tree

8 files changed

+120
-1
lines changed

8 files changed

+120
-1
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Button } from '@openedx/paragon';
2+
import React, { useMemo } from 'react';
3+
import { useLocation, useNavigate } from 'react-router-dom';
4+
5+
import { CheckoutPageRoute } from '@/constants/checkout';
6+
7+
const EditPlanButton: React.FC = () => {
8+
const navigate = useNavigate();
9+
const location = useLocation();
10+
11+
const show = useMemo(() => {
12+
const path = location.pathname;
13+
if (path === CheckoutPageRoute.AccountDetails || path === CheckoutPageRoute.BillingDetails) {
14+
return true;
15+
}
16+
return false;
17+
}, [location.pathname]);
18+
19+
if (!show) {
20+
return null;
21+
}
22+
23+
return (
24+
<Button
25+
variant="outline-danger"
26+
className="border-light w-100"
27+
onClick={() => navigate(CheckoutPageRoute.PlanDetails)}
28+
data-testid="edit-plan-button"
29+
>
30+
Edit Plan
31+
</Button>
32+
);
33+
};
34+
35+
export default React.memo(EditPlanButton);

src/components/PurchaseSummary/PurchaseSummary.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
import { Card, Stack } from '@openedx/paragon';
22
import React from 'react';
3+
import { useLocation } from 'react-router-dom';
34

45
import { usePurchaseSummaryPricing } from '@/components/app/data';
5-
import { DataStoreKey } from '@/constants/checkout';
6+
import { CheckoutPageRoute, DataStoreKey } from '@/constants/checkout';
67
import { useCheckoutFormStore } from '@/hooks/index';
78

89
import AutoRenewNotice from './AutoRenewNotice';
910
import DueTodayRow from './DueTodayRow';
11+
import EditPlanButton from './EditPlanButton';
1012
import LicensesRow from './LicensesRow';
1113
import PricePerUserRow from './PricePerUserRow';
1214
import PurchaseSummaryHeader from './PurchaseSummaryHeader';
15+
import ReceiptButton from './ReceiptButton';
1316
import TotalAfterTrialRow from './TotalAfterTrialRow';
1417

1518
const PurchaseSummary: React.FC = () => {
19+
const location = useLocation();
1620
const quantity = useCheckoutFormStore((state) => state.formData[DataStoreKey.PlanDetails]?.quantity);
1721
const companyName = useCheckoutFormStore((state) => state.formData[DataStoreKey.AccountDetails].companyName);
1822
const {
@@ -36,6 +40,12 @@ const PurchaseSummary: React.FC = () => {
3640
<DueTodayRow amountDue={yearlySubscriptionCostForQuantity ?? 0} />
3741
</Stack>
3842
</Card.Section>
43+
<Card.Footer>
44+
<EditPlanButton />
45+
{location.pathname === CheckoutPageRoute.BillingDetailsSuccess && (
46+
<ReceiptButton />
47+
)}
48+
</Card.Footer>
3949
</Card>
4050
);
4151
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Button } from '@openedx/paragon';
2+
3+
import { useCreateBillingPortalSession } from '@/components/app/data';
4+
5+
const ReceiptButton: React.FC = () => {
6+
const { data: billingPortalSession } = useCreateBillingPortalSession();
7+
return (
8+
<Button
9+
className="w-100 text-primary-500"
10+
variant="outline-primary"
11+
disabled={!billingPortalSession?.url}
12+
href={billingPortalSession?.url}
13+
target="_blank"
14+
rel="noopener noreferrer"
15+
>
16+
View receipt
17+
</Button>
18+
);
19+
};
20+
21+
export default ReceiptButton;

src/components/app/data/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export { default as useLoginMutation } from './useLoginMutation';
55
export { default as useCreateCheckoutSessionMutation } from './useCreateCheckoutSessionMutation';
66
export { default as usePurchaseSummaryPricing } from './usePurchaseSummaryPricing';
77
export { default as useCheckoutIntent } from './useCheckoutIntent';
8+
export { default as useCreateBillingPortalSession } from './useCreateBillingPortalSession';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { AppContext } from '@edx/frontend-platform/react';
2+
import { queryOptions, useQuery } from '@tanstack/react-query';
3+
import { useContext } from 'react';
4+
5+
import useBFFSuccess from '@/components/app/data/hooks/useBFFSuccess';
6+
import { queryCreateBillingPortalSession } from '@/components/app/data/queries/queries';
7+
8+
const useCreateBillingPortalSession = (options = {}) => {
9+
const { authenticatedUser }: AppContextValue = useContext(AppContext);
10+
const { data: contextData } = useBFFSuccess(authenticatedUser?.userId ?? null);
11+
const { checkoutIntent } = contextData ?? {};
12+
13+
return useQuery(
14+
queryOptions({
15+
...queryCreateBillingPortalSession(checkoutIntent?.id),
16+
...options,
17+
enabled: !!checkoutIntent?.id,
18+
}),
19+
);
20+
};
21+
22+
export default useCreateBillingPortalSession;

src/components/app/data/queries/queries.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@ export const queryBffValidation = (payload: ValidationSchema) => {
3333
._ctx.validation(fields, snakeCasedPayload)
3434
);
3535
};
36+
37+
export const queryCreateBillingPortalSession = (checkout_intent_id?: number) => (
38+
queries
39+
.enterpriseCheckout
40+
.createBillingPortalSession(checkout_intent_id)
41+
);

src/components/app/data/queries/queryKeyFactory.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createQueryKeys, mergeQueryKeys } from '@lukemorales/query-key-factory'
22

33
import createCheckoutSession from '@/components/app/data/services/checkout-session';
44
import { fetchCheckoutContext, fetchCheckoutSuccess } from '@/components/app/data/services/context';
5+
import createBillingPortalSession from '@/components/app/data/services/create-billing-portal';
56
import fetchCheckoutValidation from '@/components/app/data/services/validation';
67

78
const enterpriseCheckout = createQueryKeys('enterpriseCheckout', {
@@ -26,6 +27,10 @@ const enterpriseCheckout = createQueryKeys('enterpriseCheckout', {
2627
queryKey: [fields],
2728
queryFn: () => createCheckoutSession(payload),
2829
}),
30+
createBillingPortalSession: (checkout_intent_id) => ({
31+
queryKey: [checkout_intent_id],
32+
queryFn: () => createBillingPortalSession(checkout_intent_id),
33+
}),
2934
});
3035

3136
const queries = mergeQueryKeys(enterpriseCheckout);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
2+
import { getConfig } from '@edx/frontend-platform/config';
3+
import { camelCaseObject } from '@edx/frontend-platform/utils';
4+
5+
export interface CreateBillingPortalSessionResponseSchema {
6+
url: string;
7+
}
8+
9+
const createBillingPortalSession = async (checkoutIntentId?: number | null) => {
10+
const { ENTERPRISE_ACCESS_BASE_URL } = getConfig();
11+
if (!checkoutIntentId) {
12+
return null;
13+
}
14+
const url = `${ENTERPRISE_ACCESS_BASE_URL}/api/v1/customer-billing/${checkoutIntentId}/create-portal-session`;
15+
const response = await getAuthenticatedHttpClient().get(url);
16+
return camelCaseObject(response.data) as CreateBillingPortalSessionResponseSchema;
17+
};
18+
19+
export default createBillingPortalSession;

0 commit comments

Comments
 (0)