richie-education 2.28.2-dev58 → 2.28.2-dev65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/components/CreditCardSelector/index.tsx +2 -2
- package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +0 -1
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +28 -24
- package/js/components/SaleTunnel/SaleTunnelSavePaymentMethod/index.tsx +20 -23
- package/js/components/SaleTunnel/index.full-process.spec.tsx +3 -5
- package/js/components/SaleTunnel/index.spec.tsx +0 -1
- package/js/settings/settings.prod.ts +0 -2
- package/js/settings/settings.test.ts +5 -0
- package/js/utils/OrderHelper/index.ts +6 -0
- package/js/utils/test/factories/joanie.ts +0 -1
- package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +19 -27
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +2 -2
- package/package.json +20 -20
|
@@ -78,7 +78,7 @@ export const CreditCardSelector = ({
|
|
|
78
78
|
const isMobile = useMatchMediaLg();
|
|
79
79
|
|
|
80
80
|
const {
|
|
81
|
-
states: { fetching },
|
|
81
|
+
states: { fetching, isFetched },
|
|
82
82
|
items: creditCards,
|
|
83
83
|
} = useCreditCardsManagement();
|
|
84
84
|
|
|
@@ -101,7 +101,7 @@ export const CreditCardSelector = ({
|
|
|
101
101
|
|
|
102
102
|
return (
|
|
103
103
|
<div className="credit-card-selector">
|
|
104
|
-
{fetching ? (
|
|
104
|
+
{!isFetched && fetching ? (
|
|
105
105
|
<Spinner />
|
|
106
106
|
) : (
|
|
107
107
|
<>
|
|
@@ -28,7 +28,6 @@ export interface SaleTunnelContextType {
|
|
|
28
28
|
webAnalyticsEventKey: string;
|
|
29
29
|
|
|
30
30
|
// internal
|
|
31
|
-
onPaymentSuccess: () => void;
|
|
32
31
|
step: SaleTunnelStep;
|
|
33
32
|
|
|
34
33
|
// meta
|
|
@@ -75,16 +74,6 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
75
74
|
enrollmentId: props.enrollment?.id,
|
|
76
75
|
productId: props.product.id,
|
|
77
76
|
});
|
|
78
|
-
|
|
79
|
-
const {
|
|
80
|
-
methods: { refetch: refetchOmniscientOrders },
|
|
81
|
-
} = useOmniscientOrders();
|
|
82
|
-
const {
|
|
83
|
-
methods: { invalidate: invalidateOrders },
|
|
84
|
-
} = useOrders(undefined, { enabled: false });
|
|
85
|
-
const {
|
|
86
|
-
methods: { invalidate: invalidateEnrollments },
|
|
87
|
-
} = useEnrollments(undefined, { enabled: false });
|
|
88
77
|
const [billingAddress, setBillingAddress] = useState<Address>();
|
|
89
78
|
const [creditCard, setCreditCard] = useState<CreditCard>();
|
|
90
79
|
const [step, setStep] = useState<SaleTunnelStep>(SaleTunnelStep.IDLE);
|
|
@@ -127,19 +116,6 @@ export const GenericSaleTunnel = (props: GenericSaleTunnelProps) => {
|
|
|
127
116
|
creditCard,
|
|
128
117
|
setCreditCard,
|
|
129
118
|
nextStep,
|
|
130
|
-
onPaymentSuccess: () => {
|
|
131
|
-
nextStep();
|
|
132
|
-
WebAnalyticsAPIHandler()?.sendCourseProductEvent(
|
|
133
|
-
CourseProductEvent.PAYMENT_SUCCEED,
|
|
134
|
-
props.eventKey,
|
|
135
|
-
);
|
|
136
|
-
// Once the user has completed the purchase, we need to refetch the orders
|
|
137
|
-
// to update the ordersQuery cache
|
|
138
|
-
invalidateOrders();
|
|
139
|
-
refetchOmniscientOrders();
|
|
140
|
-
invalidateEnrollments();
|
|
141
|
-
props.onFinish?.(order!);
|
|
142
|
-
},
|
|
143
119
|
step,
|
|
144
120
|
registerSubmitCallback: (key, callback) => {
|
|
145
121
|
setSubmitCallbacks((prev) => new Map(prev).set(key, callback));
|
|
@@ -230,6 +206,34 @@ export const GenericSaleTunnelInitialStep = (props: GenericSaleTunnelProps) => {
|
|
|
230
206
|
};
|
|
231
207
|
|
|
232
208
|
export const GenericSaleTunnelSuccessStep = (props: SaleTunnelProps) => {
|
|
209
|
+
const {
|
|
210
|
+
webAnalyticsEventKey,
|
|
211
|
+
props: { onFinish },
|
|
212
|
+
order,
|
|
213
|
+
} = useSaleTunnelContext();
|
|
214
|
+
const {
|
|
215
|
+
methods: { refetch: refetchOmniscientOrders },
|
|
216
|
+
} = useOmniscientOrders();
|
|
217
|
+
const {
|
|
218
|
+
methods: { invalidate: invalidateOrders },
|
|
219
|
+
} = useOrders(undefined, { enabled: false });
|
|
220
|
+
const {
|
|
221
|
+
methods: { invalidate: invalidateEnrollments },
|
|
222
|
+
} = useEnrollments(undefined, { enabled: false });
|
|
223
|
+
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
WebAnalyticsAPIHandler()?.sendCourseProductEvent(
|
|
226
|
+
CourseProductEvent.PAYMENT_SUCCEED,
|
|
227
|
+
webAnalyticsEventKey,
|
|
228
|
+
);
|
|
229
|
+
// Once the user has completed the purchase, we need to refetch the orders
|
|
230
|
+
// to update the ordersQuery cache
|
|
231
|
+
invalidateOrders();
|
|
232
|
+
refetchOmniscientOrders();
|
|
233
|
+
invalidateEnrollments();
|
|
234
|
+
onFinish?.(order!);
|
|
235
|
+
}, []);
|
|
236
|
+
|
|
233
237
|
return (
|
|
234
238
|
<Modal {...props} size={ModalSize.MEDIUM}>
|
|
235
239
|
<SaleTunnelSuccess closeModal={props.onClose} />
|
|
@@ -10,7 +10,7 @@ import { Spinner } from 'components/Spinner';
|
|
|
10
10
|
import PaymentInterfaces from 'components/PaymentInterfaces';
|
|
11
11
|
import { useOrders } from 'hooks/useOrders';
|
|
12
12
|
import { CreditCardSelector } from 'components/CreditCardSelector';
|
|
13
|
-
import {
|
|
13
|
+
import { PAYMENT_SETTINGS } from 'settings';
|
|
14
14
|
|
|
15
15
|
const messages = defineMessages({
|
|
16
16
|
title: {
|
|
@@ -48,11 +48,12 @@ const messages = defineMessages({
|
|
|
48
48
|
|
|
49
49
|
const SaleTunnelSavePaymentMethod = () => {
|
|
50
50
|
const initialCreditCards = useRef<CreditCard[]>();
|
|
51
|
-
const
|
|
52
|
-
const JoanieApi = useJoanieApi();
|
|
51
|
+
const [shouldPoll, setShouldPoll] = useState(false);
|
|
53
52
|
const [payment, setPayment] = useState<Payment>();
|
|
54
53
|
const [error, setError] = useState<string>();
|
|
55
|
-
const
|
|
54
|
+
const creditCardsQuery = useCreditCards(undefined, {
|
|
55
|
+
refetchInterval: shouldPoll && PAYMENT_SETTINGS.pollInterval,
|
|
56
|
+
});
|
|
56
57
|
const orders = useOrders(undefined, { enabled: false });
|
|
57
58
|
const { order, nextStep, creditCard, setCreditCard } = useSaleTunnelContext();
|
|
58
59
|
|
|
@@ -64,23 +65,20 @@ const SaleTunnelSavePaymentMethod = () => {
|
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
const tokenizePaymentMethod = async () => {
|
|
67
|
-
const data = await
|
|
68
|
+
const data = await creditCardsQuery.methods.tokenize();
|
|
68
69
|
setPayment(data);
|
|
69
70
|
setError(undefined);
|
|
70
71
|
};
|
|
71
72
|
|
|
72
|
-
const waitForNewCreditCard =
|
|
73
|
-
const { results } = await JoanieApi.user.creditCards.get();
|
|
73
|
+
const waitForNewCreditCard = () => {
|
|
74
74
|
const initialIds = initialCreditCards.current!.map((cc) => cc.id);
|
|
75
|
-
const newCard =
|
|
75
|
+
const newCard = creditCardsQuery.items.find((cc) => !initialIds.includes(cc.id));
|
|
76
76
|
|
|
77
|
-
if (!newCard)
|
|
78
|
-
pollingTimeoutRef.current = setTimeout(waitForNewCreditCard, 1000);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
77
|
+
if (!newCard) return;
|
|
81
78
|
|
|
82
79
|
setCreditCard(newCard);
|
|
83
|
-
|
|
80
|
+
setShouldPoll(false);
|
|
81
|
+
setPaymentMethod(newCard.id);
|
|
84
82
|
};
|
|
85
83
|
|
|
86
84
|
const handleError = (message: string = PaymentErrorMessageId.ERROR_DEFAULT) => {
|
|
@@ -90,9 +88,11 @@ const SaleTunnelSavePaymentMethod = () => {
|
|
|
90
88
|
|
|
91
89
|
useEffect(() => {
|
|
92
90
|
if (!payment) {
|
|
93
|
-
initialCreditCards.current =
|
|
91
|
+
initialCreditCards.current = creditCardsQuery.items;
|
|
92
|
+
} else {
|
|
93
|
+
waitForNewCreditCard();
|
|
94
94
|
}
|
|
95
|
-
}, [
|
|
95
|
+
}, [creditCardsQuery.items]);
|
|
96
96
|
|
|
97
97
|
useEffect(() => {
|
|
98
98
|
if (order?.state !== OrderState.TO_SAVE_PAYMENT_METHOD) {
|
|
@@ -100,13 +100,6 @@ const SaleTunnelSavePaymentMethod = () => {
|
|
|
100
100
|
}
|
|
101
101
|
}, [order]);
|
|
102
102
|
|
|
103
|
-
useEffect(
|
|
104
|
-
() => () => {
|
|
105
|
-
clearTimeout(pollingTimeoutRef.current);
|
|
106
|
-
},
|
|
107
|
-
[],
|
|
108
|
-
);
|
|
109
|
-
|
|
110
103
|
return (
|
|
111
104
|
<section
|
|
112
105
|
className="sale-tunnel-step sale-tunnel-step--save-payment-method"
|
|
@@ -150,7 +143,11 @@ const SaleTunnelSavePaymentMethod = () => {
|
|
|
150
143
|
</p>
|
|
151
144
|
)}
|
|
152
145
|
{payment && (
|
|
153
|
-
<PaymentInterfaces
|
|
146
|
+
<PaymentInterfaces
|
|
147
|
+
{...payment}
|
|
148
|
+
onSuccess={() => setShouldPoll(true)}
|
|
149
|
+
onError={handleError}
|
|
150
|
+
/>
|
|
154
151
|
)}
|
|
155
152
|
</footer>
|
|
156
153
|
</section>
|
|
@@ -328,11 +328,9 @@ describe('SaleTunnel', () => {
|
|
|
328
328
|
fetchMock
|
|
329
329
|
.post('https://joanie.endpoint/api/v1.0/credit-cards/tokenize-card/', PaymentFactory().one())
|
|
330
330
|
.post(`https://joanie.endpoint/api/v1.0/orders/${order.id}/payment-method/`, 200)
|
|
331
|
-
.get(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
{ overwriteRoutes: true },
|
|
335
|
-
)
|
|
331
|
+
.get('https://joanie.endpoint/api/v1.0/credit-cards/', [paymentMethod], {
|
|
332
|
+
overwriteRoutes: true,
|
|
333
|
+
})
|
|
336
334
|
.get(
|
|
337
335
|
`https://joanie.endpoint/api/v1.0/orders/?${queryString.stringify(orderQueryParameters)}`,
|
|
338
336
|
[order],
|
|
@@ -217,7 +217,6 @@ describe.each([
|
|
|
217
217
|
|
|
218
218
|
// - Route to create order should have been called
|
|
219
219
|
nbApiCalls += 1; // order create
|
|
220
|
-
nbApiCalls += 1; // order get (invalidate queries)
|
|
221
220
|
nbApiCalls += 1; // useProductOrder call (invalidate from create)
|
|
222
221
|
|
|
223
222
|
await waitFor(() => expect(fetchMock.calls()).toHaveLength(nbApiCalls));
|
|
@@ -31,8 +31,6 @@ export const REACT_QUERY_SETTINGS = {
|
|
|
31
31
|
export const PAYMENT_SETTINGS = {
|
|
32
32
|
// Interval in ms to poll the related order when a payment has succeeded.
|
|
33
33
|
pollInterval: 1000,
|
|
34
|
-
// Number of retries
|
|
35
|
-
pollLimit: 30,
|
|
36
34
|
};
|
|
37
35
|
|
|
38
36
|
export const CONTRACT_SETTINGS = {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
OrderEnrollment,
|
|
7
7
|
OrderState,
|
|
8
8
|
PaymentScheduleState,
|
|
9
|
+
PURCHASABLE_ORDER_STATES,
|
|
9
10
|
} from 'types/Joanie';
|
|
10
11
|
|
|
11
12
|
export enum OrderStatus {
|
|
@@ -96,4 +97,9 @@ export class OrderHelper {
|
|
|
96
97
|
if (!order) return false;
|
|
97
98
|
return ACTIVE_ORDER_STATES.includes(order.state);
|
|
98
99
|
}
|
|
100
|
+
|
|
101
|
+
static isPurchasable(order?: Order | NestedCourseOrder | OrderEnrollment) {
|
|
102
|
+
if (!order) return true;
|
|
103
|
+
return PURCHASABLE_ORDER_STATES.includes(order.state);
|
|
104
|
+
}
|
|
99
105
|
}
|
|
@@ -484,7 +484,6 @@ export const SaleTunnelContextFactory = factory(
|
|
|
484
484
|
billingAddress: undefined,
|
|
485
485
|
setBillingAddress: noop,
|
|
486
486
|
setCreditCard: noop,
|
|
487
|
-
onPaymentSuccess: noop,
|
|
488
487
|
step: SaleTunnelStep.IDLE,
|
|
489
488
|
registerSubmitCallback: noop,
|
|
490
489
|
unregisterSubmitCallback: noop,
|
package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx
CHANGED
|
@@ -2,12 +2,7 @@ import { FormattedMessage, defineMessages } from 'react-intl';
|
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import PurchaseButton from 'components/PurchaseButton';
|
|
4
4
|
import { Icon, IconTypeEnum } from 'components/Icon';
|
|
5
|
-
import {
|
|
6
|
-
CertificateProduct,
|
|
7
|
-
Enrollment,
|
|
8
|
-
ProductType,
|
|
9
|
-
PURCHASABLE_ORDER_STATES,
|
|
10
|
-
} from 'types/Joanie';
|
|
5
|
+
import { CertificateProduct, Enrollment, ProductType } from 'types/Joanie';
|
|
11
6
|
import DownloadCertificateButton from 'components/DownloadCertificateButton';
|
|
12
7
|
import { useCertificate } from 'hooks/useCertificates';
|
|
13
8
|
import { isOpenedCourseRunCertificate } from 'utils/CourseRuns';
|
|
@@ -65,30 +60,27 @@ const ProductCertificateFooter = ({ product, enrollment }: ProductCertificateFoo
|
|
|
65
60
|
<FormattedMessage {...messages.buyProductCertificateLabel} />
|
|
66
61
|
)}
|
|
67
62
|
</div>
|
|
68
|
-
{OrderHelper.isActive(order)
|
|
69
|
-
|
|
70
|
-
<DownloadCertificateButton
|
|
71
|
-
className="dashboard-item__button"
|
|
72
|
-
certificateId={order!.certificate_id}
|
|
73
|
-
/>
|
|
74
|
-
)
|
|
75
|
-
) : (
|
|
76
|
-
<PurchaseButton
|
|
63
|
+
{OrderHelper.isActive(order) && order!.certificate_id && (
|
|
64
|
+
<DownloadCertificateButton
|
|
77
65
|
className="dashboard-item__button"
|
|
78
|
-
|
|
79
|
-
enrollment={enrollment}
|
|
80
|
-
buttonProps={{ size: 'small' }}
|
|
81
|
-
disabled={order && !PURCHASABLE_ORDER_STATES.includes(order.state)}
|
|
82
|
-
onFinish={(o) => {
|
|
83
|
-
/**
|
|
84
|
-
* As we do not refetch enrollments in DashboardCourses after SaleTunnel cache invalidation (to avoid
|
|
85
|
-
* scroll reset - and SaleTunnel modal unmounting too early caused by list reset) we need to manually
|
|
86
|
-
* update the active order in the enrollment in order to hide the buy button and display the download button.
|
|
87
|
-
*/
|
|
88
|
-
setOrder(o);
|
|
89
|
-
}}
|
|
66
|
+
certificateId={order!.certificate_id}
|
|
90
67
|
/>
|
|
91
68
|
)}
|
|
69
|
+
<PurchaseButton
|
|
70
|
+
className="dashboard-item__button"
|
|
71
|
+
product={product}
|
|
72
|
+
enrollment={enrollment}
|
|
73
|
+
buttonProps={{ size: 'small' }}
|
|
74
|
+
disabled={!OrderHelper.isPurchasable(order)}
|
|
75
|
+
onFinish={(o) => {
|
|
76
|
+
/**
|
|
77
|
+
* As we do not refetch enrollments in DashboardCourses after SaleTunnel cache invalidation (to avoid
|
|
78
|
+
* scroll reset - and SaleTunnel modal unmounting too early caused by list reset) we need to manually
|
|
79
|
+
* update the active order in the enrollment in order to hide the buy button and display the download button.
|
|
80
|
+
*/
|
|
81
|
+
setOrder(o);
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
92
84
|
</div>
|
|
93
85
|
);
|
|
94
86
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Children, useEffect, useMemo } from 'react';
|
|
2
2
|
import { defineMessages, FormattedMessage, FormattedNumber, useIntl } from 'react-intl';
|
|
3
3
|
import c from 'classnames';
|
|
4
|
-
import { ProductType, Product, CredentialOrder
|
|
4
|
+
import { ProductType, Product, CredentialOrder } from 'types/Joanie';
|
|
5
5
|
import { useCourseProduct } from 'hooks/useCourseProducts';
|
|
6
6
|
import { Spinner } from 'components/Spinner';
|
|
7
7
|
import { Icon, IconTypeEnum } from 'components/Icon';
|
|
@@ -156,7 +156,7 @@ const CourseProductItem = ({ productId, course, compact = false }: CourseProduct
|
|
|
156
156
|
});
|
|
157
157
|
|
|
158
158
|
const order = productOrder as CredentialOrder;
|
|
159
|
-
const canPurchase =
|
|
159
|
+
const canPurchase = OrderHelper.isPurchasable(order);
|
|
160
160
|
const hasPurchased = OrderHelper.isActive(order);
|
|
161
161
|
const canEnroll = OrderHelper.allowEnrollment(order);
|
|
162
162
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "richie-education",
|
|
3
|
-
"version": "2.28.2-
|
|
3
|
+
"version": "2.28.2-dev65",
|
|
4
4
|
"description": "A CMS to build learning portals for Open Education",
|
|
5
5
|
"main": "sandbox/manage.py",
|
|
6
6
|
"scripts": {
|
|
@@ -51,20 +51,20 @@
|
|
|
51
51
|
"@lyracom/embedded-form-glue": "1.4.2",
|
|
52
52
|
"@openfun/cunningham-react": "2.9.3",
|
|
53
53
|
"@openfun/cunningham-tokens": "2.1.1",
|
|
54
|
-
"@sentry/browser": "8.
|
|
55
|
-
"@sentry/types": "8.
|
|
56
|
-
"@storybook/addon-actions": "8.2.
|
|
57
|
-
"@storybook/addon-essentials": "8.2.
|
|
58
|
-
"@storybook/addon-interactions": "8.2.
|
|
59
|
-
"@storybook/addon-links": "8.2.
|
|
60
|
-
"@storybook/react": "8.2.
|
|
61
|
-
"@storybook/react-webpack5": "8.2.
|
|
62
|
-
"@storybook/test": "8.2.
|
|
63
|
-
"@tanstack/query-core": "5.51.
|
|
64
|
-
"@tanstack/query-sync-storage-persister": "5.51.
|
|
65
|
-
"@tanstack/react-query": "5.51.
|
|
66
|
-
"@tanstack/react-query-devtools": "5.51.
|
|
67
|
-
"@tanstack/react-query-persist-client": "5.51.
|
|
54
|
+
"@sentry/browser": "8.26.0",
|
|
55
|
+
"@sentry/types": "8.26.0",
|
|
56
|
+
"@storybook/addon-actions": "8.2.9",
|
|
57
|
+
"@storybook/addon-essentials": "8.2.9",
|
|
58
|
+
"@storybook/addon-interactions": "8.2.9",
|
|
59
|
+
"@storybook/addon-links": "8.2.9",
|
|
60
|
+
"@storybook/react": "8.2.9",
|
|
61
|
+
"@storybook/react-webpack5": "8.2.9",
|
|
62
|
+
"@storybook/test": "8.2.9",
|
|
63
|
+
"@tanstack/query-core": "5.51.24",
|
|
64
|
+
"@tanstack/query-sync-storage-persister": "5.51.24",
|
|
65
|
+
"@tanstack/react-query": "5.51.24",
|
|
66
|
+
"@tanstack/react-query-devtools": "5.51.24",
|
|
67
|
+
"@tanstack/react-query-persist-client": "5.51.24",
|
|
68
68
|
"@testing-library/dom": "10.4.0",
|
|
69
69
|
"@testing-library/jest-dom": "6.4.8",
|
|
70
70
|
"@testing-library/react": "16.0.0",
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
"@types/react-dom": "18.3.0",
|
|
82
82
|
"@types/react-modal": "3.16.3",
|
|
83
83
|
"@types/uuid": "10.0.0",
|
|
84
|
-
"@typescript-eslint/eslint-plugin": "8.0
|
|
85
|
-
"@typescript-eslint/parser": "8.0
|
|
84
|
+
"@typescript-eslint/eslint-plugin": "8.1.0",
|
|
85
|
+
"@typescript-eslint/parser": "8.1.0",
|
|
86
86
|
"babel-jest": "29.7.0",
|
|
87
87
|
"babel-loader": "9.1.3",
|
|
88
88
|
"babel-plugin-react-intl": "8.2.25",
|
|
@@ -126,10 +126,10 @@
|
|
|
126
126
|
"react-hook-form": "7.52.2",
|
|
127
127
|
"react-intl": "6.6.8",
|
|
128
128
|
"react-modal": "3.16.1",
|
|
129
|
-
"react-router-dom": "6.26.
|
|
129
|
+
"react-router-dom": "6.26.1",
|
|
130
130
|
"sass": "1.77.8",
|
|
131
131
|
"source-map-loader": "5.0.0",
|
|
132
|
-
"storybook": "8.2.
|
|
132
|
+
"storybook": "8.2.9",
|
|
133
133
|
"tsconfig-paths-webpack-plugin": "4.1.0",
|
|
134
134
|
"typescript": "5.5.4",
|
|
135
135
|
"uuid": "10.0.0",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"node": "20.11.0"
|
|
153
153
|
},
|
|
154
154
|
"devDependencies": {
|
|
155
|
-
"@storybook/addon-mdx-gfm": "8.2.
|
|
155
|
+
"@storybook/addon-mdx-gfm": "8.2.9",
|
|
156
156
|
"@storybook/addon-webpack5-compiler-babel": "3.0.3"
|
|
157
157
|
}
|
|
158
158
|
}
|