richie-education 3.5.1 → 3.5.2-dev6

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.
Files changed (26) hide show
  1. package/js/api/joanie.ts +5 -0
  2. package/js/components/SaleTunnel/index.spec.tsx +1 -1
  3. package/js/hooks/useLearnerCoursesSearch/index.tsx +17 -4
  4. package/js/hooks/useProductOrder/index.spec.tsx +1 -0
  5. package/js/pages/DashboardCourses/index.spec.tsx +40 -8
  6. package/js/types/Joanie.ts +18 -1
  7. package/js/utils/OrderHelper/index.spec.ts +75 -0
  8. package/js/utils/OrderHelper/index.ts +14 -2
  9. package/js/utils/test/factories/joanie.ts +10 -0
  10. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +213 -1
  11. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +133 -31
  12. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +22 -0
  13. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +29 -13
  14. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +0 -2
  15. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.spec.tsx +12 -0
  16. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateLearnerMessage/index.tsx +11 -0
  17. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +5 -1
  18. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.spec.tsx +12 -0
  19. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateTeacherMessage/index.tsx +11 -0
  20. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderWithdrawalModal/index.spec.tsx +139 -0
  21. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderWithdrawalModal/index.tsx +259 -0
  22. package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.spec.tsx +102 -0
  23. package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.tsx +84 -2
  24. package/js/widgets/Dashboard/components/DashboardItem/_styles.scss +9 -1
  25. package/js/widgets/Dashboard/index.spec.tsx +0 -1
  26. package/package.json +1 -1
@@ -0,0 +1,259 @@
1
+ import {
2
+ Alert,
3
+ Button,
4
+ Modal,
5
+ ModalProps,
6
+ ModalSize,
7
+ useModals,
8
+ VariantType,
9
+ } from '@openfun/cunningham-react';
10
+ import { defineMessages, FormattedMessage, MessageDescriptor, useIntl } from 'react-intl';
11
+ import { ReactNode, useState } from 'react';
12
+ import { Order, OrderEnrollment, OrderState } from 'types/Joanie';
13
+ import { useJoanieApi } from 'contexts/JoanieApiContext';
14
+ import { useOrders } from 'hooks/useOrders';
15
+ import { useSession } from 'contexts/SessionContext';
16
+ import { UserHelper } from 'utils/UserHelper';
17
+ import { AuthenticationApi } from 'api/authentication';
18
+ import { APIBackend, KeycloakAccountApi } from 'types/api';
19
+ import context from 'utils/context';
20
+ import { Spinner } from 'components/Spinner';
21
+ import { HttpStatusCode, isHttpError } from 'utils/errors/HttpError';
22
+
23
+ const boldChunk = (chunks: ReactNode) => <strong>{chunks}</strong>;
24
+
25
+ const InfoRow = ({ label, value }: { label: MessageDescriptor; value: ReactNode }) => (
26
+ <p className="mb-s">
27
+ <span className="fw-bold">
28
+ <FormattedMessage {...label} />
29
+ </span>{' '}
30
+ {value}
31
+ </p>
32
+ );
33
+
34
+ const messages = defineMessages({
35
+ title: {
36
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.title',
37
+ defaultMessage: 'Withdrawal request',
38
+ description: 'Title of the withdrawal request modal',
39
+ },
40
+ description: {
41
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.description',
42
+ defaultMessage:
43
+ 'You wish to cancel your subscription to <bold>{productTitle} - Ref. {reference}</bold>.',
44
+ description: 'Message displayed in the withdrawal request modal',
45
+ },
46
+ informationTitle: {
47
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.informationTitle',
48
+ defaultMessage: 'Information',
49
+ description: 'Title of the information recap section in the withdrawal request modal',
50
+ },
51
+ informationDescription: {
52
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.informationDescription',
53
+ defaultMessage:
54
+ 'This information will be included in the acknowledgement of receipt for your request.',
55
+ description: 'Description of the information recap section in the withdrawal request modal',
56
+ },
57
+ accountNameLabel: {
58
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.accountNameLabel',
59
+ defaultMessage: 'Account name',
60
+ description: 'Label for the account name in the withdrawal request modal',
61
+ },
62
+ accountEmailLabel: {
63
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.accountEmailLabel',
64
+ defaultMessage: 'Account email',
65
+ description: 'Label for the account email in the withdrawal request modal',
66
+ },
67
+ orderReferenceLabel: {
68
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.orderReferenceLabel',
69
+ defaultMessage: 'Order reference',
70
+ description: 'Label for the order reference in the withdrawal request modal',
71
+ },
72
+ accountLinkInfo: {
73
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.accountLinkInfo',
74
+ defaultMessage: 'If any of the information above is incorrect,',
75
+ description: 'Text before the account update link in the withdrawal request modal',
76
+ },
77
+ accountLinkLabel: {
78
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.accountLinkLabel',
79
+ defaultMessage: 'please update your account',
80
+ description: 'Label of the account update link in the withdrawal request modal',
81
+ },
82
+ submit: {
83
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.submit',
84
+ defaultMessage: 'I confirm my withdrawal request',
85
+ description: 'Submit button label of the withdrawal request modal',
86
+ },
87
+ submitInProgress: {
88
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.submitInProgress',
89
+ defaultMessage: 'Withdrawal request in progress',
90
+ description: 'Label for screen reader when a withdrawal request is in progress.',
91
+ },
92
+ successConfirmedTitle: {
93
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.successConfirmedTitle',
94
+ defaultMessage: 'Withdrawal confirmed',
95
+ description: 'Title of the success modal when the withdrawal is confirmed immediately',
96
+ },
97
+ successConfirmedDescription: {
98
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.successConfirmedDescription',
99
+ defaultMessage: 'Your order has been canceled following your withdrawal request.',
100
+ description: 'Description of the success modal when the withdrawal is confirmed immediately',
101
+ },
102
+ successPendingTitle: {
103
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.successPendingTitle',
104
+ defaultMessage: 'Withdrawal request recorded',
105
+ description: 'Title of the success modal when the withdrawal request needs manual review',
106
+ },
107
+ successPendingDescription: {
108
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.successPendingDescription',
109
+ defaultMessage: 'Your withdrawal request has been recorded and is being processed.',
110
+ description: 'Description of the success modal when the withdrawal request needs manual review',
111
+ },
112
+ errorDelayExpired: {
113
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.errorDelayExpired',
114
+ defaultMessage: 'The withdrawal period for this order has expired.',
115
+ description: 'Error message when the withdrawal delay has expired',
116
+ },
117
+ errorDefault: {
118
+ id: 'components.DashboardItemOrder.OrderWithdrawalModal.errorDefault',
119
+ defaultMessage: 'An error occurred, please contact our support team.',
120
+ description: 'Generic error message when the withdrawal request failed',
121
+ },
122
+ });
123
+
124
+ interface Props extends Pick<ModalProps, 'isOpen' | 'onClose'> {
125
+ order: Order | OrderEnrollment;
126
+ productTitle: string;
127
+ reference: string;
128
+ /**
129
+ * Called with the fresh order returned by the API once the withdrawal request succeeds.
130
+ * Callers relying on locally held order state (rather than a query that gets invalidated)
131
+ * must use this to update it, otherwise the UI keeps showing stale data until a full
132
+ * page refresh.
133
+ */
134
+ onSuccess?: (order: Order) => void;
135
+ }
136
+
137
+ enum ComponentStates {
138
+ IDLE = 'idle',
139
+ LOADING = 'loading',
140
+ ERROR = 'error',
141
+ }
142
+
143
+ export const OrderWithdrawalModal = ({
144
+ order,
145
+ productTitle,
146
+ reference,
147
+ onSuccess,
148
+ ...props
149
+ }: Props) => {
150
+ const intl = useIntl();
151
+ const API = useJoanieApi();
152
+ const { user } = useSession();
153
+ const { methods: orderMethods } = useOrders(undefined, { enabled: false });
154
+ const modals = useModals();
155
+ const [state, setState] = useState<ComponentStates>(ComponentStates.IDLE);
156
+ const [error, setError] = useState<string>();
157
+
158
+ const isKeycloakBackend = [APIBackend.KEYCLOAK, APIBackend.FONZIE_KEYCLOAK].includes(
159
+ context?.authentication.backend as APIBackend,
160
+ );
161
+
162
+ const submit = async () => {
163
+ setState(ComponentStates.LOADING);
164
+ try {
165
+ const updatedOrder = await API.user.orders.withdraw(order.id);
166
+ await orderMethods.invalidate();
167
+ onSuccess?.(updatedOrder);
168
+ props.onClose();
169
+ await modals.messageModal({
170
+ messageType: VariantType.SUCCESS,
171
+ title: intl.formatMessage(
172
+ updatedOrder.state === OrderState.PENDING_WITHDRAW
173
+ ? messages.successPendingTitle
174
+ : messages.successConfirmedTitle,
175
+ ),
176
+ children: intl.formatMessage(
177
+ updatedOrder.state === OrderState.PENDING_WITHDRAW
178
+ ? messages.successPendingDescription
179
+ : messages.successConfirmedDescription,
180
+ ),
181
+ });
182
+ } catch (submitError) {
183
+ setState(ComponentStates.ERROR);
184
+ setError(
185
+ intl.formatMessage(
186
+ isHttpError(submitError) && submitError.code === HttpStatusCode.UNPROCESSABLE_ENTITY
187
+ ? messages.errorDelayExpired
188
+ : messages.errorDefault,
189
+ ),
190
+ );
191
+ }
192
+ };
193
+
194
+ return (
195
+ <Modal
196
+ {...props}
197
+ size={ModalSize.MEDIUM}
198
+ title={intl.formatMessage(messages.title)}
199
+ closeOnEsc={state !== ComponentStates.LOADING}
200
+ preventClose={state === ComponentStates.LOADING}
201
+ hideCloseButton={state === ComponentStates.LOADING}
202
+ actions={
203
+ <Button
204
+ color="error"
205
+ variant="primary"
206
+ size="small"
207
+ fullWidth={true}
208
+ onClick={submit}
209
+ disabled={state === ComponentStates.LOADING}
210
+ data-testid="order-withdrawal-modal-submit-button"
211
+ >
212
+ {state === ComponentStates.LOADING ? (
213
+ <Spinner theme="light" aria-labelledby="withdrawal-request-in-progress">
214
+ <span id="withdrawal-request-in-progress">
215
+ <FormattedMessage {...messages.submitInProgress} />
216
+ </span>
217
+ </Spinner>
218
+ ) : (
219
+ <FormattedMessage {...messages.submit} />
220
+ )}
221
+ </Button>
222
+ }
223
+ >
224
+ {error && (
225
+ <Alert type={VariantType.ERROR} className="mb-t">
226
+ {error}
227
+ </Alert>
228
+ )}
229
+ <p className="mb-b">
230
+ <FormattedMessage
231
+ {...messages.description}
232
+ values={{
233
+ productTitle,
234
+ reference,
235
+ bold: boldChunk,
236
+ }}
237
+ />
238
+ </p>
239
+ <h3 className="block-title mb-t">
240
+ <FormattedMessage {...messages.informationTitle} />
241
+ </h3>
242
+ <p className="mb-s">
243
+ <FormattedMessage {...messages.informationDescription} />
244
+ </p>
245
+ <InfoRow label={messages.accountNameLabel} value={user ? UserHelper.getName(user) : ''} />
246
+ <InfoRow label={messages.accountEmailLabel} value={user?.email} />
247
+ <InfoRow label={messages.orderReferenceLabel} value={order.id} />
248
+ {isKeycloakBackend && (
249
+ <p className="mb-b">
250
+ <FormattedMessage {...messages.accountLinkInfo} />{' '}
251
+ <a href={(AuthenticationApi!.account as KeycloakAccountApi).updateUrl()}>
252
+ <FormattedMessage {...messages.accountLinkLabel} />
253
+ </a>
254
+ .
255
+ </p>
256
+ )}
257
+ </Modal>
258
+ );
259
+ };
@@ -0,0 +1,102 @@
1
+ import { screen, within } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
4
+ import { CredentialOrderFactory, CredentialProductFactory } from 'utils/test/factories/joanie';
5
+ import { setupJoanieSession } from 'utils/test/wrappers/JoanieAppWrapper';
6
+ import { render } from 'utils/test/render';
7
+ import OrganizationBlock from '.';
8
+
9
+ jest.mock('utils/context', () => ({
10
+ __esModule: true,
11
+ default: mockRichieContextFactory({
12
+ authentication: { backend: 'fonzie', endpoint: 'https://auth.endpoint.test' },
13
+ joanie_backend: { endpoint: 'https://joanie.endpoint' },
14
+ }).one(),
15
+ }));
16
+
17
+ jest.mock('../OrderWithdrawalModal', () => ({
18
+ __esModule: true,
19
+ OrderWithdrawalModal: ({
20
+ isOpen,
21
+ order,
22
+ productTitle,
23
+ reference,
24
+ }: {
25
+ isOpen: boolean;
26
+ order: { id: string };
27
+ productTitle: string;
28
+ reference: string;
29
+ }) => {
30
+ if (!isOpen) return null;
31
+ return (
32
+ <div data-testid="OrderWithdrawalModalMock">
33
+ {order.id} - {productTitle} - {reference}
34
+ </div>
35
+ );
36
+ },
37
+ }));
38
+
39
+ describe('<OrganizationBlock/>', () => {
40
+ setupJoanieSession();
41
+ const product = CredentialProductFactory({ contract_definition: undefined }).one();
42
+
43
+ it('should display the withdrawal block with the withdraw button when the order is eligible', () => {
44
+ const order = CredentialOrderFactory({
45
+ total: 0,
46
+ eligible_to_withdraw: true,
47
+ withdrawal_date_limit: '2026-08-30T10:00:00.000Z',
48
+ withdrawn_confirmation_at: null,
49
+ }).one();
50
+
51
+ render(<OrganizationBlock order={order} product={product} />);
52
+
53
+ const block = screen.getByTestId('dashboard-item-withdrawal');
54
+ within(block).getByRole('button', { name: 'Withdraw' });
55
+ within(block).getByText('Aug 30, 2026', { exact: false });
56
+ expect(screen.queryByTestId('dashboard-item-withdrawn')).not.toBeInTheDocument();
57
+ });
58
+
59
+ it('should not display any withdrawal information when the order is not eligible and has not been withdrawn', () => {
60
+ const order = CredentialOrderFactory({
61
+ total: 0,
62
+ eligible_to_withdraw: false,
63
+ withdrawn_confirmation_at: null,
64
+ }).one();
65
+
66
+ render(<OrganizationBlock order={order} product={product} />);
67
+
68
+ expect(screen.queryByTestId('dashboard-item-withdrawal')).not.toBeInTheDocument();
69
+ expect(screen.queryByTestId('dashboard-item-withdrawn')).not.toBeInTheDocument();
70
+ });
71
+
72
+ it('should display the withdrawn status instead of the withdrawal block once the order has been withdrawn', () => {
73
+ const order = CredentialOrderFactory({
74
+ total: 0,
75
+ eligible_to_withdraw: false,
76
+ withdrawn_confirmation_at: '2026-08-10T10:00:00.000Z',
77
+ }).one();
78
+
79
+ render(<OrganizationBlock order={order} product={product} />);
80
+
81
+ const block = screen.getByTestId('dashboard-item-withdrawn');
82
+ within(block).getByText('Aug 10, 2026', { exact: false });
83
+ expect(screen.queryByTestId('dashboard-item-withdrawal')).not.toBeInTheDocument();
84
+ });
85
+
86
+ it('should open the withdrawal modal with the order reference and product title when clicking the withdraw button', async () => {
87
+ const order = CredentialOrderFactory({
88
+ total: 0,
89
+ eligible_to_withdraw: true,
90
+ withdrawn_confirmation_at: null,
91
+ }).one();
92
+
93
+ render(<OrganizationBlock order={order} product={product} />);
94
+
95
+ expect(screen.queryByTestId('OrderWithdrawalModalMock')).not.toBeInTheDocument();
96
+ await userEvent.click(screen.getByRole('button', { name: 'Withdraw' }));
97
+
98
+ within(screen.getByTestId('OrderWithdrawalModalMock')).getByText(
99
+ `${order.id} - ${product.title} - ${order.course.code}`,
100
+ );
101
+ });
102
+ });
@@ -1,10 +1,12 @@
1
- import { defineMessages, FormattedMessage } from 'react-intl';
2
- import { Button } from '@openfun/cunningham-react';
1
+ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
2
+ import { Button, useModal } from '@openfun/cunningham-react';
3
3
  import { CredentialOrder, Product } from 'types/Joanie';
4
4
  import { AddressView } from 'components/Address';
5
5
  import { OrderHelper } from 'utils/OrderHelper';
6
+ import useDateFormat, { DATETIME_FORMAT } from 'hooks/useDateFormat';
6
7
  import ContractItem from '../ContractItem';
7
8
  import Installment from '../Installment';
9
+ import { OrderWithdrawalModal } from '../OrderWithdrawalModal';
8
10
 
9
11
  const messages = defineMessages({
10
12
  contactDescription: {
@@ -52,6 +54,31 @@ const messages = defineMessages({
52
54
  description: 'Subtitle for the organization contacts section',
53
55
  defaultMessage: 'Contacts',
54
56
  },
57
+ withdrawalTitle: {
58
+ id: 'components.DashboardItemOrder.OrganizationBlock.withdrawalTitle',
59
+ description: 'Title of the withdrawal block',
60
+ defaultMessage: 'Withdrawal',
61
+ },
62
+ withdrawalDescription: {
63
+ id: 'components.DashboardItemOrder.OrganizationBlock.withdrawalDescription',
64
+ description: 'Description of the withdrawal block',
65
+ defaultMessage: 'You can withdraw from your contract until {date}.',
66
+ },
67
+ withdrawalButton: {
68
+ id: 'components.DashboardItemOrder.OrganizationBlock.withdrawalButton',
69
+ description: 'Button label of the withdrawal block',
70
+ defaultMessage: 'Withdraw',
71
+ },
72
+ withdrawnTitle: {
73
+ id: 'components.DashboardItemOrder.OrganizationBlock.withdrawnTitle',
74
+ description: 'Title of the withdrawal block once the withdrawal is confirmed',
75
+ defaultMessage: 'Withdrawn',
76
+ },
77
+ withdrawnDescription: {
78
+ id: 'components.DashboardItemOrder.OrganizationBlock.withdrawnDescription',
79
+ description: 'Description shown once the withdrawal is confirmed',
80
+ defaultMessage: 'You withdrew from this order on {date}.',
81
+ },
55
82
  });
56
83
 
57
84
  type Props = {
@@ -148,7 +175,62 @@ const OrganizationBlock = ({ order, product }: Props) => {
148
175
  </div>
149
176
  )}
150
177
  {!hidePaymentBlock && <Installment order={order} />}
178
+ {order.eligible_to_withdraw && <Withdrawal order={order} product={product} />}
179
+ {OrderHelper.isWithdrawn(order) && <WithdrawnStatus order={order} />}
180
+ </div>
181
+ </div>
182
+ );
183
+ };
184
+
185
+ const Withdrawal = ({ order, product }: Props) => {
186
+ const intl = useIntl();
187
+ const formatDate = useDateFormat(DATETIME_FORMAT);
188
+ const modal = useModal();
189
+
190
+ return (
191
+ <div data-testid="dashboard-item-withdrawal" className="dashboard-splitted-card__item">
192
+ <div className="dashboard-splitted-card__item__title">
193
+ <span>
194
+ <FormattedMessage {...messages.withdrawalTitle} />
195
+ </span>
196
+ </div>
197
+ <p className="dashboard-splitted-card__item__description">
198
+ <FormattedMessage
199
+ {...messages.withdrawalDescription}
200
+ values={{ date: formatDate(order.withdrawal_date_limit) }}
201
+ />
202
+ </p>
203
+ <div className="dashboard-splitted-card__item__actions">
204
+ <Button size="small" color="brand" variant="secondary" onClick={modal.open}>
205
+ {intl.formatMessage(messages.withdrawalButton)}
206
+ </Button>
207
+ </div>
208
+ <OrderWithdrawalModal
209
+ {...modal}
210
+ order={order}
211
+ productTitle={product.title}
212
+ reference={order.course.code}
213
+ />
214
+ </div>
215
+ );
216
+ };
217
+
218
+ const WithdrawnStatus = ({ order }: { order: CredentialOrder }) => {
219
+ const formatDate = useDateFormat(DATETIME_FORMAT);
220
+
221
+ return (
222
+ <div data-testid="dashboard-item-withdrawn" className="dashboard-splitted-card__item">
223
+ <div className="dashboard-splitted-card__item__title">
224
+ <span>
225
+ <FormattedMessage {...messages.withdrawnTitle} />
226
+ </span>
151
227
  </div>
228
+ <p className="dashboard-splitted-card__item__description">
229
+ <FormattedMessage
230
+ {...messages.withdrawnDescription}
231
+ values={{ date: formatDate(order.withdrawn_confirmation_at) }}
232
+ />
233
+ </p>
152
234
  </div>
153
235
  );
154
236
  };
@@ -56,7 +56,9 @@
56
56
 
57
57
  &__status {
58
58
  display: flex;
59
- align-items: center;
59
+ // flex-start (not center) so the icon stays aligned with the first line
60
+ // of text when the text wraps onto several lines.
61
+ align-items: flex-start;
60
62
  gap: 0.5rem;
61
63
 
62
64
  @include media-breakpoint-down(sm) {
@@ -179,6 +181,7 @@
179
181
  justify-content: space-between;
180
182
  padding: 0.5rem 1rem;
181
183
  min-height: 3.75rem;
184
+ border-top: $onepixel solid r-theme-val(dashboard-item, base-border);
182
185
 
183
186
  &:first-child {
184
187
  border-top: 0;
@@ -201,6 +204,11 @@
201
204
  p {
202
205
  margin: 0;
203
206
  }
207
+
208
+ &__withdrawal__button {
209
+ text-decoration: underline;
210
+ white-space: nowrap;
211
+ }
204
212
  }
205
213
 
206
214
  &__runs {
@@ -52,7 +52,6 @@ describe('<Dashboard />', () => {
52
52
  fetchMock.get(
53
53
  'https://joanie.endpoint/api/v1.0/orders/' +
54
54
  '?product_type=credential' +
55
- '&state_exclude=canceled' +
56
55
  '&state_exclude=refunding' +
57
56
  '&state_exclude=refunded' +
58
57
  '&page=1' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "3.5.1",
3
+ "version": "3.5.2-dev6",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {