richie-education 2.30.1-dev9 → 2.31.1-dev4

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 (55) hide show
  1. package/i18n/locales/ar-SA.json +32 -0
  2. package/i18n/locales/es-ES.json +32 -0
  3. package/i18n/locales/fa-IR.json +32 -0
  4. package/i18n/locales/fr-CA.json +32 -0
  5. package/i18n/locales/fr-FR.json +32 -0
  6. package/i18n/locales/ko-KR.json +32 -0
  7. package/i18n/locales/pt-PT.json +125 -93
  8. package/i18n/locales/ru-RU.json +32 -0
  9. package/i18n/locales/vi-VN.json +32 -0
  10. package/js/api/lms/openedx-fonzie.spec.ts +1 -1
  11. package/js/api/lms/openedx-hawthorn.spec.ts +1 -1
  12. package/js/components/CourseGlimpse/utils.ts +3 -3
  13. package/js/components/CourseGlimpseList/utils.ts +2 -2
  14. package/js/components/PaymentInterfaces/types.ts +1 -0
  15. package/js/components/PurchaseButton/index.spec.tsx +20 -2
  16. package/js/components/PurchaseButton/index.tsx +3 -0
  17. package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +2 -0
  18. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +6 -1
  19. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +2 -0
  20. package/js/components/SaleTunnel/SubscriptionButton/_styles.scss +8 -0
  21. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +17 -2
  22. package/js/components/SaleTunnel/WithdrawRightCheckbox/index.tsx +105 -0
  23. package/js/components/SaleTunnel/index.credential.spec.tsx +2 -2
  24. package/js/components/SaleTunnel/index.full-process.spec.tsx +22 -2
  25. package/js/components/SaleTunnel/index.spec.tsx +83 -6
  26. package/js/components/SaleTunnel/index.stories.tsx +1 -0
  27. package/js/components/SaleTunnel/index.tsx +1 -1
  28. package/js/components/TeacherDashboardCourseList/index.tsx +2 -2
  29. package/js/contexts/SessionContext/index.spec.tsx +2 -2
  30. package/js/hooks/useCourseProductUnion/index.ts +2 -1
  31. package/js/hooks/useTeacherCoursesSearch/index.tsx +2 -2
  32. package/js/translations/ar-SA.json +1 -1
  33. package/js/translations/es-ES.json +1 -1
  34. package/js/translations/fa-IR.json +1 -1
  35. package/js/translations/fr-CA.json +1 -1
  36. package/js/translations/fr-FR.json +1 -1
  37. package/js/translations/ko-KR.json +1 -1
  38. package/js/translations/pt-PT.json +1 -1
  39. package/js/translations/ru-RU.json +1 -1
  40. package/js/translations/vi-VN.json +1 -1
  41. package/js/types/Joanie.ts +9 -4
  42. package/js/utils/test/factories/factories.spec.ts +1 -1
  43. package/js/utils/test/factories/joanie.ts +8 -5
  44. package/js/utils/test/factories/openEdx.tsx +1 -1
  45. package/js/utils/test/factories/richie.ts +1 -1
  46. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +2 -1
  47. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +29 -5
  48. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +7 -1
  49. package/js/widgets/Dashboard/components/DashboardItem/Order/Installment/index.tsx +1 -0
  50. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +2 -0
  51. package/package.json +3 -2
  52. package/scss/colors/_theme.scss +4 -0
  53. package/scss/components/_index.scss +1 -0
  54. package/scss/components/templates/richie/_multiple-columns.scss +8 -5
  55. package/scss/components/templates/richie/simpletext/_simpletext.scss +43 -0
@@ -174,17 +174,21 @@ export interface DefinitionResourcesProduct {
174
174
  contract_definition_id: Nullable<ContractDefinition['id']>;
175
175
  }
176
176
 
177
- export interface CourseProductRelation {
177
+ export interface CourseProductRelationLight {
178
178
  id: string;
179
179
  course: CourseLight;
180
180
  organizations: Organization[];
181
181
  product: Product;
182
182
  created_on: string;
183
+ }
184
+
185
+ export interface CourseProductRelation extends CourseProductRelationLight {
183
186
  order_groups: OrderGroup[];
187
+ is_withdrawable: boolean;
184
188
  }
185
189
  export function isCourseProductRelation(
186
- entity: CourseListItem | CourseProductRelation | RichieCourse,
187
- ): entity is CourseProductRelation {
190
+ entity: CourseListItem | CourseProductRelationLight | RichieCourse,
191
+ ): entity is CourseProductRelationLight {
188
192
  return 'course' in entity && 'product' in entity;
189
193
  }
190
194
 
@@ -468,6 +472,7 @@ interface AbstractOrderProductCreationPayload {
468
472
  product_id: Product['id'];
469
473
  order_group_id?: OrderGroup['id'];
470
474
  billing_address: Omit<Address, 'id' | 'is_main'>;
475
+ has_waived_withdrawal_right: boolean;
471
476
  }
472
477
 
473
478
  interface OrderCertificateCreationPayload extends AbstractOrderProductCreationPayload {
@@ -697,7 +702,7 @@ export interface API {
697
702
  filters?: Filters,
698
703
  ): Filters extends { id: string }
699
704
  ? Promise<Nullable<CourseProductRelation>>
700
- : Promise<PaginatedResponse<CourseProductRelation>>;
705
+ : Promise<PaginatedResponse<CourseProductRelationLight>>;
701
706
  };
702
707
  contractDefinitions: {
703
708
  previewTemplate(id: string): Promise<File>;
@@ -38,7 +38,7 @@ describe('factory', () => {
38
38
  }
39
39
  return {
40
40
  fullname,
41
- username: faker.internet.userName(),
41
+ username: faker.internet.username(),
42
42
  mainAddress: DemoAddressFactory().one(),
43
43
  addresses: DemoAddressFactory().many(3),
44
44
  favoriteColors: Array(2).fill(faker.color.human()),
@@ -51,7 +51,7 @@ import { factory } from './factories';
51
51
  export const UserLightFactory = factory((): UserLight => {
52
52
  return {
53
53
  id: faker.string.uuid(),
54
- username: faker.internet.userName(),
54
+ username: faker.internet.username(),
55
55
  full_name: faker.person.fullName(),
56
56
  email: faker.internet.email(),
57
57
  };
@@ -59,7 +59,7 @@ export const UserLightFactory = factory((): UserLight => {
59
59
  export const JoanieUserProfileFactory = factory((): JoanieUserProfile => {
60
60
  return {
61
61
  id: faker.string.uuid(),
62
- username: faker.internet.userName(),
62
+ username: faker.internet.username(),
63
63
  full_name: faker.person.fullName(),
64
64
  is_superuser: false,
65
65
  is_staff: false,
@@ -337,6 +337,7 @@ export const CourseProductRelationFactory = factory((): CourseProductRelation =>
337
337
  product: ProductFactory().one(),
338
338
  organizations: OrganizationFactory().many(1),
339
339
  order_groups: [],
340
+ is_withdrawable: true,
340
341
  };
341
342
  });
342
343
 
@@ -393,7 +394,7 @@ export const NestedCertificateOrderFactory = factory((): NestedCertificateOrder
393
394
  enrollment: EnrollmentLightFactory().one(),
394
395
  organization: OrganizationFactory().one(),
395
396
  product_title: FactoryHelper.unique(faker.lorem.words, { args: [1] }),
396
- owner_name: faker.internet.userName(),
397
+ owner_name: faker.internet.username(),
397
398
  state: OrderState.COMPLETED,
398
399
  };
399
400
  });
@@ -405,7 +406,7 @@ export const NestedCredentialOrderFactory = factory((): NestedCredentialOrder =>
405
406
  enrollment: null,
406
407
  organization: OrganizationFactory().one(),
407
408
  product_title: FactoryHelper.unique(faker.lorem.words, { args: [1] }),
408
- owner_name: faker.internet.userName(),
409
+ owner_name: faker.internet.username(),
409
410
  state: OrderState.COMPLETED,
410
411
  };
411
412
  });
@@ -414,7 +415,7 @@ const AbstractOrderFactory = factory((): Order => {
414
415
  return {
415
416
  id: faker.string.uuid(),
416
417
  created_on: faker.date.past({ years: 1 }).toISOString(),
417
- owner: faker.internet.userName(),
418
+ owner: faker.internet.username(),
418
419
  total: faker.number.int(),
419
420
  total_currency: faker.finance.currencyCode(),
420
421
  main_invoice_reference: faker.string.uuid(),
@@ -490,6 +491,8 @@ export const SaleTunnelContextFactory = factory(
490
491
  billingAddress: undefined,
491
492
  setBillingAddress: noop,
492
493
  setCreditCard: noop,
494
+ setHasWaivedWithdrawalRight: noop,
495
+ hasWaivedWithdrawalRight: false,
493
496
  step: SaleTunnelStep.IDLE,
494
497
  registerSubmitCallback: noop,
495
498
  unregisterSubmitCallback: noop,
@@ -9,7 +9,7 @@ import { factory } from './factories';
9
9
 
10
10
  export const OpenEdxApiProfileFactory = factory((): OpenEdxApiProfile => {
11
11
  return {
12
- username: faker.internet.userName(),
12
+ username: faker.internet.username(),
13
13
  name: faker.person.fullName(),
14
14
  country: faker.location.countryCode(),
15
15
  year_of_birth: faker.date.past().toISOString(),
@@ -154,7 +154,7 @@ export const UserFactory = factory<User>(() => ({
154
154
  access_token: faker.lorem.word(12),
155
155
  full_name: faker.person.fullName(),
156
156
  email: faker.internet.email(),
157
- username: faker.internet.userName(),
157
+ username: faker.internet.username(),
158
158
  }));
159
159
 
160
160
  export const FonzieUserFactory = factory<User>(() => ({
@@ -33,13 +33,14 @@ export const DashboardItemEnrollment = ({ enrollment }: DashboardItemCourseRunPr
33
33
  </div>
34
34
  </div>,
35
35
  ];
36
- enrollment.product_relations.forEach(({ product }) => {
36
+ enrollment.product_relations.forEach(({ product, is_withdrawable }) => {
37
37
  if (isCertificateProduct(product)) {
38
38
  partialFooterList.push(
39
39
  <ProductCertificateFooter
40
40
  key={[enrollment.id, product.id].join('_')}
41
41
  product={product}
42
42
  enrollment={enrollment}
43
+ isWithdrawable={is_withdrawable}
43
44
  />,
44
45
  );
45
46
  }
@@ -124,6 +124,7 @@ describe('<ProductCertificateFooter/>', () => {
124
124
  course,
125
125
  }).one(),
126
126
  }).one()}
127
+ isWithdrawable={true}
127
128
  />,
128
129
  );
129
130
  expect(screen.getByTestId('PurchaseButton__cta')).toBeInTheDocument();
@@ -155,6 +156,7 @@ describe('<ProductCertificateFooter/>', () => {
155
156
  course,
156
157
  }).one(),
157
158
  }).one()}
159
+ isWithdrawable={true}
158
160
  />,
159
161
  );
160
162
 
@@ -176,7 +178,9 @@ describe('<ProductCertificateFooter/>', () => {
176
178
  'https://joanie.endpoint/api/v1.0/certificates/FAKE_CERTIFICATE_ID/',
177
179
  CertificateFactory({ id: order.certificate_id }).one(),
178
180
  );
179
- render(<ProductCertificateFooter product={product} enrollment={enrollment} />);
181
+ render(
182
+ <ProductCertificateFooter product={product} enrollment={enrollment} isWithdrawable={true} />,
183
+ );
180
184
  expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument();
181
185
  expect(screen.queryByTestId('PurchaseButton__cta')).not.toBeInTheDocument();
182
186
  });
@@ -193,7 +197,13 @@ describe('<ProductCertificateFooter/>', () => {
193
197
  orders: [order],
194
198
  course_run: CourseRunFactory({ course }).one(),
195
199
  }).one();
196
- render(<ProductCertificateFooter product={product} enrollment={enrollment} />);
200
+ render(
201
+ <ProductCertificateFooter
202
+ product={product}
203
+ enrollment={enrollment}
204
+ isWithdrawable={true}
205
+ />,
206
+ );
197
207
  expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument();
198
208
  expect(screen.getByTestId('PurchaseButton__cta')).toBeInTheDocument();
199
209
  },
@@ -208,7 +218,9 @@ describe('<ProductCertificateFooter/>', () => {
208
218
  orders: [order],
209
219
  course_run: CourseRunFactory({ course }).one(),
210
220
  }).one();
211
- render(<ProductCertificateFooter product={product} enrollment={enrollment} />);
221
+ render(
222
+ <ProductCertificateFooter product={product} enrollment={enrollment} isWithdrawable={true} />,
223
+ );
212
224
  expect(screen.queryByRole('button', { name: 'Download' })).not.toBeInTheDocument();
213
225
  expect(screen.queryByTestId('PurchaseButton__cta')).not.toBeInTheDocument();
214
226
  });
@@ -274,7 +286,13 @@ describe('<ProductCertificateFooter/>', () => {
274
286
  }).one();
275
287
  const enrollment = EnrollmentFactory({ orders: [order] }).one();
276
288
 
277
- render(<ProductCertificateFooter product={product} enrollment={enrollment} />);
289
+ render(
290
+ <ProductCertificateFooter
291
+ product={product}
292
+ enrollment={enrollment}
293
+ isWithdrawable={true}
294
+ />,
295
+ );
278
296
 
279
297
  if (order.state === OrderState.PENDING) {
280
298
  // As the order is in pending state, the user should see the following message.
@@ -315,7 +333,13 @@ describe('<ProductCertificateFooter/>', () => {
315
333
 
316
334
  fetchMock.get(`https://joanie.endpoint/api/v1.0/orders/${order.id}/`, order);
317
335
 
318
- render(<ProductCertificateFooter product={product} enrollment={enrollment} />);
336
+ render(
337
+ <ProductCertificateFooter
338
+ product={product}
339
+ enrollment={enrollment}
340
+ isWithdrawable={true}
341
+ />,
342
+ );
319
343
 
320
344
  if (order.state === OrderState.NO_PAYMENT) {
321
345
  // As the order is in no_payment state, the user should see the following message.
@@ -63,9 +63,14 @@ const messages = defineMessages({
63
63
  export interface ProductCertificateFooterProps {
64
64
  product: CertificateProduct;
65
65
  enrollment: Enrollment;
66
+ isWithdrawable: boolean;
66
67
  }
67
68
 
68
- const ProductCertificateFooter = ({ product, enrollment }: ProductCertificateFooterProps) => {
69
+ const ProductCertificateFooter = ({
70
+ product,
71
+ enrollment,
72
+ isWithdrawable,
73
+ }: ProductCertificateFooterProps) => {
69
74
  const [order, setOrder] = useState(
70
75
  OrderHelper.getActiveEnrollmentOrder(enrollment.orders || [], product.id),
71
76
  );
@@ -103,6 +108,7 @@ const ProductCertificateFooter = ({ product, enrollment }: ProductCertificateFoo
103
108
  className="dashboard-item__button"
104
109
  product={product}
105
110
  enrollment={enrollment}
111
+ isWithdrawable={isWithdrawable}
106
112
  buttonProps={{ size: 'small' }}
107
113
  disabled={!isPurchasable}
108
114
  onFinish={(o) => {
@@ -119,6 +119,7 @@ const PaymentMethodManager = ({ order }: Props) => {
119
119
  {...modal}
120
120
  product={relation.product as CredentialProduct}
121
121
  course={relation.course}
122
+ isWithdrawable={relation.is_withdrawable}
122
123
  />
123
124
  </>
124
125
  );
@@ -43,6 +43,7 @@ const CourseProductItemFooter = ({
43
43
  course={course}
44
44
  product={courseProductRelation.product as CredentialProduct}
45
45
  organizations={courseProductRelation.organizations}
46
+ isWithdrawable={courseProductRelation.is_withdrawable}
46
47
  disabled={!canPurchase}
47
48
  buttonProps={{ fullWidth: true }}
48
49
  />
@@ -61,6 +62,7 @@ const CourseProductItemFooter = ({
61
62
  course={course}
62
63
  product={courseProductRelation.product as CredentialProduct}
63
64
  organizations={courseProductRelation.organizations}
65
+ isWithdrawable={courseProductRelation.is_withdrawable}
64
66
  disabled={!canPurchase}
65
67
  orderGroup={orderGroup}
66
68
  buttonProps={{ fullWidth: true }}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "richie-education",
3
- "version": "2.30.1-dev9",
3
+ "version": "2.31.1-dev4",
4
4
  "description": "A CMS to build learning portals for Open Education",
5
5
  "main": "sandbox/manage.py",
6
6
  "scripts": {
@@ -148,7 +148,8 @@
148
148
  "workerDirectory": "../richie/static/richie/js"
149
149
  },
150
150
  "volta": {
151
- "node": "20.11.0"
151
+ "node": "20.11.0",
152
+ "yarn": "1.22.22"
152
153
  },
153
154
  "devDependencies": {
154
155
  "@storybook/addon-mdx-gfm": "8.3.6",
@@ -147,6 +147,10 @@ $r-theme: (
147
147
  content-color: r-color('slate-grey'),
148
148
  description-color: r-color('slate-grey'),
149
149
  ),
150
+ simpletext-variant-box: (
151
+ primary-color: r-color('denim'),
152
+ secondary-color: r-color('white'),
153
+ ),
150
154
  blogpost-glimpse: (
151
155
  card-background: r-color('white'),
152
156
  title-color: r-color('black'),
@@ -59,6 +59,7 @@
59
59
 
60
60
  @import './templates/courses/plugins/category_plugin';
61
61
  @import './templates/courses/plugins/licence_plugin';
62
+ @import './templates/richie/simpletext/simpletext';
62
63
  @import './templates/richie/section/section';
63
64
  @import './templates/richie/large_banner/large_banner';
64
65
  @import './templates/richie/large_banner/compacted_banner';
@@ -23,11 +23,6 @@
23
23
  padding: 1rem;
24
24
  }
25
25
 
26
- // Impose tiny horizontal padding to every section row
27
- .section__row {
28
- padding: $cell-gutter;
29
- }
30
-
31
26
  // ----------
32
27
  // Shared base adjustments for short columns (25+33+50)
33
28
  // ----------
@@ -117,6 +112,8 @@
117
112
  // ----------
118
113
  &__w25 {
119
114
  @include sv-flex(1, 0, 100%);
115
+ padding: $cell-gutter;
116
+
120
117
  @include media-breakpoint-up(lg) {
121
118
  @include sv-flex(1, 0, 25%);
122
119
  }
@@ -169,6 +166,8 @@
169
166
  // ----------
170
167
  &__w33 {
171
168
  @include sv-flex(1, 0, 100%);
169
+ padding: $cell-gutter;
170
+
172
171
  @include media-breakpoint-up(lg) {
173
172
  @include sv-flex(1, 0, 33.3333%);
174
173
  }
@@ -193,6 +192,8 @@
193
192
  // ----------
194
193
  &__w50 {
195
194
  @include sv-flex(1, 0, 100%);
195
+ padding: $cell-gutter;
196
+
196
197
  @include media-breakpoint-up(lg) {
197
198
  @include sv-flex(1, 0, 50%);
198
199
  }
@@ -268,6 +269,8 @@
268
269
  // ----------
269
270
  &__w75 {
270
271
  @include sv-flex(1, 0, 100%);
272
+ padding: $cell-gutter;
273
+
271
274
  @include media-breakpoint-up(lg) {
272
275
  @include sv-flex(1, 0, 75%);
273
276
  }
@@ -0,0 +1,43 @@
1
+ // CKEditor 'simple text' plugin
2
+ //
3
+ // This aims to only adjust every possible plugins so they correctly fit since they
4
+ // were done for 100% size only. No colour, font or anything else should be changed
5
+ // here.
6
+ //
7
+ $r-simpletext-margin-bottom: 0.5rem !default;
8
+ $r-simpletext-variant-padding: 1rem !default;
9
+ $r-simpletext-variant-radius: 0.75rem !default;
10
+
11
+ .simple-text {
12
+ margin-bottom: $r-simpletext-margin-bottom;
13
+
14
+ & > *:last-child {
15
+ margin-bottom: 0;
16
+ }
17
+
18
+ &__variant-round-box {
19
+ padding: $r-simpletext-variant-padding;
20
+ border: 1px solid transparent;
21
+ border-radius: $r-simpletext-variant-radius;
22
+ }
23
+
24
+ &__variant-stroked {
25
+ border-color: r-theme-val(simpletext-variant-box, primary-color);
26
+ background: r-theme-val(simpletext-variant-box, secondary-color);
27
+
28
+ h1,
29
+ h2,
30
+ h3,
31
+ h4,
32
+ h5,
33
+ h6 {
34
+ color: r-theme-val(simpletext-variant-box, primary-color);
35
+ }
36
+ }
37
+
38
+ &__variant-fulfilled {
39
+ color: r-theme-val(simpletext-variant-box, secondary-color);
40
+ border-color: r-theme-val(simpletext-variant-box, primary-color);
41
+ background: r-theme-val(simpletext-variant-box, primary-color);
42
+ }
43
+ }