richie-education 2.33.1-dev19 → 2.33.1-dev23

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.
@@ -22,9 +22,10 @@
22
22
  font-weight: var(--c--theme--font--weight--semibold);
23
23
  font-size: rem-calc(12px);
24
24
 
25
- &--incoming {
26
- background-color: var(--c--theme--colors--grey87);
27
- color: var(--c--theme--colors--charcoal);
25
+ &--canceled,
26
+ &--refunded {
27
+ color: var(--c--theme--colors--white);
28
+ background-color: var(--c--theme--colors--grey59);
28
29
  }
29
30
 
30
31
  &--paid {
@@ -37,8 +38,8 @@
37
38
  color: var(--c--theme--colors--white);
38
39
  }
39
40
 
40
- &--require_payment,
41
- &--failed {
41
+ &--refused,
42
+ &--error {
42
43
  background-color: var(--c--theme--colors--firebrick6);
43
44
  color: var(--c--theme--colors--white);
44
45
  }
@@ -22,6 +22,12 @@ export const stateMessages = defineMessages({
22
22
  defaultMessage: 'Pending',
23
23
  description: 'Label displayed for pending payment state',
24
24
  },
25
+ [PaymentScheduleState.ERROR]: {
26
+ id: 'components.PaymentScheduleGrid.state.error',
27
+ defaultMessage: 'Pending',
28
+ description:
29
+ 'Label displayed for error payment state. For learner we assume to display `pending`.',
30
+ },
25
31
  [PaymentScheduleState.PAID]: {
26
32
  id: 'components.PaymentScheduleGrid.state.paid',
27
33
  defaultMessage: 'Paid',
@@ -32,6 +38,16 @@ export const stateMessages = defineMessages({
32
38
  defaultMessage: 'Refused',
33
39
  description: 'Label displayed for refused payment state',
34
40
  },
41
+ [PaymentScheduleState.CANCELED]: {
42
+ id: 'components.PaymentScheduleGrid.state.canceled',
43
+ defaultMessage: 'Canceled',
44
+ description: 'Label displayed for canceled payment state',
45
+ },
46
+ [PaymentScheduleState.REFUNDED]: {
47
+ id: 'components.PaymentScheduleGrid.state.refunded',
48
+ defaultMessage: 'Refunded',
49
+ description: 'Label displayed for refunded payment state',
50
+ },
35
51
  });
36
52
 
37
53
  export const PaymentScheduleGrid = ({ schedule }: Props) => {
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from 'react';
2
2
  import { useSearchParams } from 'react-router';
3
- import { Enrollment, CredentialOrder, OrderState, ProductType } from 'types/Joanie';
3
+ import { Enrollment, CredentialOrder, ProductType, CANCELED_ORDER_STATES } from 'types/Joanie';
4
4
  import { Maybe, Nullable } from 'types/utils';
5
5
  import { useOrdersEnrollments } from 'pages/DashboardCourses/useOrdersEnrollments';
6
6
 
@@ -23,7 +23,7 @@ const useLearnerCoursesSearch = () => {
23
23
  query,
24
24
  orderFilters: {
25
25
  product_type: [ProductType.CREDENTIAL],
26
- state_exclude: [OrderState.CANCELED],
26
+ state_exclude: CANCELED_ORDER_STATES,
27
27
  },
28
28
  });
29
29
 
@@ -103,7 +103,13 @@ describe('<DashboardCourses/>', () => {
103
103
  it('renders an empty placeholder', async () => {
104
104
  const ordersDeferred = new Deferred();
105
105
  fetchMock.get(
106
- `https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=1&page_size=${perPage}`,
106
+ 'https://joanie.endpoint/api/v1.0/orders/' +
107
+ '?product_type=credential' +
108
+ '&state_exclude=canceled' +
109
+ '&state_exclude=refunding' +
110
+ '&state_exclude=refunded' +
111
+ '&page=1' +
112
+ `&page_size=${perPage}`,
107
113
  ordersDeferred.promise,
108
114
  );
109
115
  const enrollmentsDeferred = new Deferred();
@@ -145,25 +151,57 @@ describe('<DashboardCourses/>', () => {
145
151
  client,
146
152
  );
147
153
  fetchMock.get(
148
- `https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=1&page_size=${perPage}`,
154
+ 'https://joanie.endpoint/api/v1.0/orders/' +
155
+ '?product_type=credential' +
156
+ '&state_exclude=canceled' +
157
+ '&state_exclude=refunding' +
158
+ '&state_exclude=refunded' +
159
+ '&page=1' +
160
+ `&page_size=${perPage}`,
149
161
  {
150
162
  results: orders.slice(0, perPage),
151
- next: `https://joanie.endpoint/api/v1.0/orders/?product_type=credentia&state_exclude=canceled&page=2&page_size=${perPage}`,
163
+ next:
164
+ 'https://joanie.endpoint/api/v1.0/orders/' +
165
+ '?product_type=credential' +
166
+ '&state_exclude=canceled' +
167
+ '&state_exclude=refunding' +
168
+ '&state_exclude=refunded' +
169
+ '&page=2' +
170
+ `&page_size=${perPage}`,
152
171
  previous: null,
153
172
  count: orders.length,
154
173
  },
155
174
  );
156
175
  fetchMock.get(
157
- `https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=2&page_size=${perPage}`,
176
+ 'https://joanie.endpoint/api/v1.0/orders/' +
177
+ '?product_type=credential' +
178
+ '&state_exclude=canceled' +
179
+ '&state_exclude=refunding' +
180
+ '&state_exclude=refunded' +
181
+ '&page=2' +
182
+ `&page_size=${perPage}`,
158
183
  {
159
184
  results: orders.slice(perPage, perPage * 2),
160
- next: `https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=3&page_size=${perPage}`,
185
+ next:
186
+ 'https://joanie.endpoint/api/v1.0/orders/' +
187
+ '?product_type=credential' +
188
+ '&state_exclude=canceled' +
189
+ '&state_exclude=refunding' +
190
+ '&state_exclude=refunded' +
191
+ '&page=3' +
192
+ `&page_size=${perPage}`,
161
193
  previous: null,
162
194
  count: orders.length,
163
195
  },
164
196
  );
165
197
  fetchMock.get(
166
- `https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=3&page_size=${perPage}`,
198
+ 'https://joanie.endpoint/api/v1.0/orders/' +
199
+ '?product_type=credential' +
200
+ '&state_exclude=canceled' +
201
+ '&state_exclude=refunding' +
202
+ '&state_exclude=refunded' +
203
+ '&page=3' +
204
+ `&page_size=${perPage}`,
167
205
  {
168
206
  results: orders.slice(perPage * 2, perPage * 3),
169
207
  next: null,
@@ -237,7 +275,13 @@ describe('<DashboardCourses/>', () => {
237
275
  jest.spyOn(console, 'error').mockImplementation(noop);
238
276
  const ordersDeferred = new Deferred();
239
277
  fetchMock.get(
240
- `https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=1&page_size=${perPage}`,
278
+ 'https://joanie.endpoint/api/v1.0/orders/' +
279
+ '?product_type=credential' +
280
+ '&state_exclude=canceled' +
281
+ '&state_exclude=refunding' +
282
+ '&state_exclude=refunded' +
283
+ '&page=1' +
284
+ `&page_size=${perPage}`,
241
285
  ordersDeferred.promise,
242
286
  );
243
287
  fetchMock.get(
@@ -265,6 +265,8 @@ export interface EnrollmentLight {
265
265
  export enum OrderState {
266
266
  ASSIGNED = 'assigned',
267
267
  CANCELED = 'canceled',
268
+ REFUNDING = 'refunding',
269
+ REFUNDED = 'refunded',
268
270
  COMPLETED = 'completed',
269
271
  DRAFT = 'draft',
270
272
  FAILED_PAYMENT = 'failed_payment',
@@ -294,6 +296,12 @@ export const ACTIVE_ORDER_STATES = [
294
296
 
295
297
  export const NOT_CANCELED_ORDER_STATES = [...ACTIVE_ORDER_STATES, ...PURCHASABLE_ORDER_STATES];
296
298
 
299
+ export const CANCELED_ORDER_STATES = [
300
+ OrderState.CANCELED,
301
+ OrderState.REFUNDING,
302
+ OrderState.REFUNDED,
303
+ ];
304
+
297
305
  export const ENROLLABLE_ORDER_STATES = [
298
306
  OrderState.COMPLETED,
299
307
  OrderState.PENDING_PAYMENT,
@@ -448,8 +456,11 @@ export interface OrderPaymentInfo {
448
456
  }
449
457
 
450
458
  export enum PaymentScheduleState {
451
- PENDING = 'pending',
459
+ CANCELED = 'canceled',
460
+ ERROR = 'error',
452
461
  PAID = 'paid',
462
+ PENDING = 'pending',
463
+ REFUNDED = 'refunded',
453
464
  REFUSED = 'refused',
454
465
  }
455
466
 
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  ACTIVE_ORDER_STATES,
3
+ CANCELED_ORDER_STATES,
3
4
  ENROLLABLE_ORDER_STATES,
4
5
  NestedCourseOrder,
5
6
  Order,
@@ -11,6 +12,7 @@ import {
11
12
  export enum OrderStatus {
12
13
  ASSIGNED = 'assigned',
13
14
  CANCELED = 'canceled',
15
+ REFUNDED = 'refunded',
14
16
  COMPLETED = 'completed',
15
17
  DRAFT = 'draft',
16
18
  FAILED_PAYMENT = 'failed_payment',
@@ -38,6 +40,8 @@ export class OrderHelper {
38
40
  const orderStatusMap = {
39
41
  [OrderState.ASSIGNED]: OrderStatus.ASSIGNED,
40
42
  [OrderState.CANCELED]: OrderStatus.CANCELED,
43
+ [OrderState.REFUNDING]: OrderStatus.CANCELED,
44
+ [OrderState.REFUNDED]: OrderStatus.REFUNDED,
41
45
  [OrderState.COMPLETED]: OrderStatus.COMPLETED,
42
46
  [OrderState.DRAFT]: OrderStatus.DRAFT,
43
47
  [OrderState.FAILED_PAYMENT]: OrderStatus.FAILED_PAYMENT,
@@ -91,6 +95,11 @@ export class OrderHelper {
91
95
  return ACTIVE_ORDER_STATES.includes(order.state);
92
96
  }
93
97
 
98
+ static isCanceled(order?: Order | NestedCourseOrder | OrderEnrollment) {
99
+ if (!order) return false;
100
+ return CANCELED_ORDER_STATES.includes(order.state);
101
+ }
102
+
94
103
  static isPurchasable(order?: Order | NestedCourseOrder | OrderEnrollment) {
95
104
  if (!order) return true;
96
105
  return PURCHASABLE_ORDER_STATES.includes(order.state);
@@ -228,7 +228,13 @@ describe('<ProductCertificateFooter/>', () => {
228
228
  // From https://github.com/openfun/richie/issues/2237
229
229
  it('should hide purchase button after payment', async () => {
230
230
  fetchMock.get(
231
- `https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=1&page_size=${PER_PAGE.useOrdersEnrollments}`,
231
+ 'https://joanie.endpoint/api/v1.0/orders/' +
232
+ '?product_type=credential' +
233
+ '&state_exclude=canceled' +
234
+ '&state_exclude=refunding' +
235
+ '&state_exclude=refunded' +
236
+ '&page=1' +
237
+ `&page_size=${PER_PAGE.useOrdersEnrollments}`,
232
238
  {
233
239
  results: [],
234
240
  next: null,
@@ -86,7 +86,13 @@ describe('<DashboardItemOrder/> Contract', () => {
86
86
  });
87
87
 
88
88
  fetchMock.get(
89
- 'https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=1&page_size=50',
89
+ 'https://joanie.endpoint/api/v1.0/orders/' +
90
+ '?product_type=credential' +
91
+ '&state_exclude=canceled' +
92
+ '&state_exclude=refunding' +
93
+ '&state_exclude=refunded' +
94
+ '&page=1' +
95
+ '&page_size=50',
90
96
  { results: [order], next: null, previous: null, count: 1 },
91
97
  );
92
98
 
@@ -259,7 +265,13 @@ describe('<DashboardItemOrder/> Contract', () => {
259
265
 
260
266
  // Go back to the list view to make sure the sign button is not shown anymore.
261
267
  fetchMock.get(
262
- 'https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=1&page_size=50',
268
+ 'https://joanie.endpoint/api/v1.0/orders/' +
269
+ '?product_type=credential' +
270
+ '&state_exclude=canceled' +
271
+ '&state_exclude=refunding' +
272
+ '&state_exclude=refunded' +
273
+ '&page=1' +
274
+ '&page_size=50',
263
275
  { results: [signedOrder], next: null, previous: null, count: 1 },
264
276
  { overwriteRoutes: true },
265
277
  );
@@ -61,6 +61,7 @@ type Props = {
61
61
 
62
62
  const Installment = ({ order }: Props) => {
63
63
  const isActive = OrderHelper.isActive(order);
64
+ const isCanceled = OrderHelper.isCanceled(order);
64
65
  const failedInstallment = PaymentScheduleHelper.getFailedInstallment(order.payment_schedule);
65
66
  const needsPaymentMethod = order.state === OrderState.TO_SAVE_PAYMENT_METHOD;
66
67
  const shouldDisplayDot = needsPaymentMethod || !!failedInstallment;
@@ -80,13 +81,13 @@ const Installment = ({ order }: Props) => {
80
81
  <FormattedMessage {...messages.paymentTitle} />
81
82
  </span>
82
83
  </div>
83
- {!isActive && !needsPaymentMethod && (
84
+ {!isActive && !isCanceled && !needsPaymentMethod && (
84
85
  <p className="dashboard-splitted-card__item__description">
85
86
  <FormattedMessage {...messages.paymentInactiveDescription} />
86
87
  </p>
87
88
  )}
88
89
  <PaymentMethodManager order={order} />
89
- {isActive && <InstallmentManager order={order} />}
90
+ {(isActive || isCanceled) && <InstallmentManager order={order} />}
90
91
  </div>
91
92
  );
92
93
  };
@@ -11,7 +11,7 @@ import {
11
11
  import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
12
12
  import { useState, useEffect } from 'react';
13
13
  import { PaymentScheduleGrid } from 'components/PaymentScheduleGrid';
14
- import { CreditCard, Order } from 'types/Joanie';
14
+ import { ACTIVE_ORDER_STATES, CreditCard, Order, OrderState } from 'types/Joanie';
15
15
  import { CreditCardSelector } from 'components/CreditCardSelector';
16
16
  import { OrderPaymentRetryModal } from 'widgets/Dashboard/components/DashboardItem/Order/OrderPaymentRetryModal';
17
17
  import { Maybe } from 'types/utils';
@@ -54,14 +54,21 @@ export const OrderPaymentDetailsModal = ({ order, ...props }: PaymentModalProps)
54
54
  const intl = useIntl();
55
55
  const retryModal = useModal();
56
56
  const failedInstallment = PaymentScheduleHelper.getFailedInstallment(order.payment_schedule);
57
+ const showPaymentMethod = ACTIVE_ORDER_STATES.filter<OrderState>(
58
+ (state) => state !== OrderState.COMPLETED,
59
+ ).includes(order.state);
57
60
 
58
61
  return (
59
62
  <>
60
63
  <Modal {...props} size={ModalSize.MEDIUM} title={intl.formatMessage(messages.title)}>
61
- <h3 className="order-payment-details__title mb-s">
62
- <FormattedMessage {...messages.paymentMethodTitle} />
63
- </h3>
64
- <CreditCardSelectorWrapper selectedCreditCardId={order.credit_card_id} />
64
+ {showPaymentMethod && (
65
+ <>
66
+ <h3 className="order-payment-details__title mb-s">
67
+ <FormattedMessage {...messages.paymentMethodTitle} />
68
+ </h3>
69
+ <CreditCardSelectorWrapper selectedCreditCardId={order.credit_card_id} />
70
+ </>
71
+ )}
65
72
  <h3 className="order-payment-details__title mb-s mt-b">
66
73
  <FormattedMessage {...messages.scheduleTitle} />
67
74
  </h3>
@@ -66,6 +66,11 @@ export const messages = defineMessages<MessageKeys>({
66
66
  'Status shown on the dashboard order item when order is completed and has a certificate',
67
67
  defaultMessage: 'Successfully completed',
68
68
  },
69
+ statusRefunded: {
70
+ id: 'components.DashboardItem.Order.OrderStateLearnerMessage.statusRefunded',
71
+ description: 'Status shown on the dashboard order item when order is refunded',
72
+ defaultMessage: 'Refunded',
73
+ },
69
74
  });
70
75
 
71
76
  const OrderStateLearnerMessage = (props: OrderStateMessageBaseProps) => {
@@ -10,18 +10,19 @@ export interface OrderStateMessageBaseProps {
10
10
  }
11
11
 
12
12
  export type MessageKeys =
13
- | 'statusDraft'
14
13
  | 'statusAssigned'
14
+ | 'statusCanceled'
15
+ | 'statusCompleted'
16
+ | 'statusDraft'
17
+ | 'statusFailedPayment'
18
+ | 'statusNoPayment'
19
+ | 'statusPassed'
15
20
  | 'statusPending'
16
21
  | 'statusPendingPayment'
17
- | 'statusCompleted'
18
- | 'statusWaitingSignature'
22
+ | 'statusRefunded'
19
23
  | 'statusWaitingCounterSignature'
20
24
  | 'statusWaitingPaymentMethod'
21
- | 'statusCanceled'
22
- | 'statusNoPayment'
23
- | 'statusFailedPayment'
24
- | 'statusPassed';
25
+ | 'statusWaitingSignature';
25
26
 
26
27
  interface OrderStateMessageProps extends OrderStateMessageBaseProps {
27
28
  messages: Record<MessageKeys, MessageDescriptor>;
@@ -37,13 +38,14 @@ const OrderStateMessage = ({ order, messages }: OrderStateMessageProps) => {
37
38
  const orderStatusMessagesMap = {
38
39
  [OrderStatus.ASSIGNED]: messages.statusAssigned,
39
40
  [OrderStatus.CANCELED]: messages.statusCanceled,
40
- [OrderStatus.DRAFT]: messages.statusDraft,
41
41
  [OrderStatus.COMPLETED]: messages.statusCompleted,
42
+ [OrderStatus.DRAFT]: messages.statusDraft,
42
43
  [OrderStatus.FAILED_PAYMENT]: messages.statusFailedPayment,
43
44
  [OrderStatus.NO_PAYMENT]: messages.statusNoPayment,
44
45
  [OrderStatus.PASSED]: messages.statusPassed,
45
46
  [OrderStatus.PENDING]: messages.statusPending,
46
47
  [OrderStatus.PENDING_PAYMENT]: messages.statusPendingPayment,
48
+ [OrderStatus.REFUNDED]: messages.statusRefunded,
47
49
  [OrderStatus.WAITING_COUNTER_SIGNATURE]: messages.statusWaitingCounterSignature,
48
50
  [OrderStatus.WAITING_PAYMENT_METHOD]: messages.statusWaitingPaymentMethod,
49
51
  [OrderStatus.WAITING_SIGNATURE]: messages.statusWaitingSignature,
@@ -67,6 +67,11 @@ export const messages = defineMessages<MessageKeys>({
67
67
  'Status shown on the dashboard order item when order is completed with certificate',
68
68
  defaultMessage: 'Certified',
69
69
  },
70
+ statusRefunded: {
71
+ id: 'components.DashboardItem.Order.OrderStateTeacherMessage.statusRefunded',
72
+ description: 'Status shown on the dashboard order item when order is refunded',
73
+ defaultMessage: 'Refunded',
74
+ },
70
75
  });
71
76
 
72
77
  const OrderStateTeacherMessage = (props: OrderStateMessageBaseProps) => {
@@ -49,7 +49,13 @@ describe('<Dashboard />', () => {
49
49
  { count: 0, results: [] },
50
50
  );
51
51
  fetchMock.get(
52
- 'https://joanie.endpoint/api/v1.0/orders/?product_type=credential&state_exclude=canceled&page=1&page_size=50',
52
+ 'https://joanie.endpoint/api/v1.0/orders/' +
53
+ '?product_type=credential' +
54
+ '&state_exclude=canceled' +
55
+ '&state_exclude=refunding' +
56
+ '&state_exclude=refunded' +
57
+ '&page=1' +
58
+ '&page_size=50',
53
59
  {
54
60
  count: 0,
55
61
  results: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "2.33.1-dev19",
3
+ "version": "2.33.1-dev23",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {