richie-education 3.2.1-dev9 → 3.2.2-dev26

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 (98) hide show
  1. package/i18n/locales/ar-SA.json +29 -1
  2. package/i18n/locales/es-ES.json +29 -1
  3. package/i18n/locales/fa-IR.json +29 -1
  4. package/i18n/locales/fr-CA.json +29 -1
  5. package/i18n/locales/fr-FR.json +29 -1
  6. package/i18n/locales/ko-KR.json +29 -1
  7. package/i18n/locales/pt-PT.json +29 -1
  8. package/i18n/locales/ru-RU.json +29 -1
  9. package/i18n/locales/vi-VN.json +29 -1
  10. package/js/api/joanie.ts +144 -0
  11. package/js/components/PaymentInterfaces/types.ts +7 -0
  12. package/js/components/PaymentScheduleGrid/index.tsx +4 -2
  13. package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +9 -2
  14. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +33 -0
  15. package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup.tsx +253 -0
  16. package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular.tsx +314 -0
  17. package/js/components/SaleTunnel/SaleTunnelInformation/StepContent.tsx +528 -0
  18. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +47 -261
  19. package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +25 -11
  20. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +54 -6
  21. package/js/components/SaleTunnel/_styles.scss +55 -0
  22. package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +356 -0
  23. package/js/components/SaleTunnel/{index.full-process.spec.tsx → index.full-process-b2c.spec.tsx} +4 -1
  24. package/js/components/SaleTunnel/index.spec.tsx +130 -1
  25. package/js/hooks/useBatchOrder/index.tsx +36 -0
  26. package/js/hooks/useContractArchive/index.ts +2 -0
  27. package/js/hooks/useOfferingOrganizations/index.tsx +38 -0
  28. package/js/hooks/useOrganizationAgreements.tsx/index.tsx +66 -0
  29. package/js/hooks/useOrganizationQuotes/index.tsx +56 -0
  30. package/js/hooks/usePaymentPlan.tsx +2 -1
  31. package/js/hooks/useTeacherPendingAgreementsCount/index.ts +34 -0
  32. package/js/pages/DashboardBatchOrderLayout/_styles.scss +5 -0
  33. package/js/pages/DashboardBatchOrderLayout/index.spec.tsx +78 -0
  34. package/js/pages/DashboardBatchOrderLayout/index.tsx +45 -0
  35. package/js/pages/DashboardBatchOrders/index.spec.tsx +237 -0
  36. package/js/pages/DashboardBatchOrders/index.tsx +84 -0
  37. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardCourseContractsLayout/index.tsx +0 -1
  38. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +3 -1
  39. package/js/pages/TeacherDashboardOrganizationAgreements/AgreementActionsBar.tsx +49 -0
  40. package/js/pages/TeacherDashboardOrganizationAgreements/BulkAgreementContractButton.tsx +79 -0
  41. package/js/pages/TeacherDashboardOrganizationAgreements/OrganizationAgreementFrame.tsx +71 -0
  42. package/js/pages/TeacherDashboardOrganizationAgreements/SignOrganizationAgreementButton.tsx +60 -0
  43. package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useAgreementsAbilities.tsx +8 -0
  44. package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useHasAgreementToDownload.tsx +27 -0
  45. package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useTeacherAgreementsToSign.tsx +32 -0
  46. package/js/pages/TeacherDashboardOrganizationAgreements/index.spec.tsx +433 -0
  47. package/js/pages/TeacherDashboardOrganizationAgreements/index.tsx +130 -0
  48. package/js/pages/TeacherDashboardOrganizationAgreementsLayout/index.tsx +25 -0
  49. package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +9 -0
  50. package/js/pages/TeacherDashboardOrganizationQuotes/_styles.scss +40 -0
  51. package/js/pages/TeacherDashboardOrganizationQuotes/index.full-process.spec.tsx +194 -0
  52. package/js/pages/TeacherDashboardOrganizationQuotes/index.spec.tsx +144 -0
  53. package/js/pages/TeacherDashboardOrganizationQuotes/index.tsx +521 -0
  54. package/js/pages/TeacherDashboardOrganizationQuotesLayout/index.tsx +26 -0
  55. package/js/translations/ar-SA.json +1 -1
  56. package/js/translations/es-ES.json +1 -1
  57. package/js/translations/fa-IR.json +1 -1
  58. package/js/translations/fr-CA.json +1 -1
  59. package/js/translations/fr-FR.json +1 -1
  60. package/js/translations/ko-KR.json +1 -1
  61. package/js/translations/pt-PT.json +1 -1
  62. package/js/translations/ru-RU.json +1 -1
  63. package/js/translations/vi-VN.json +1 -1
  64. package/js/types/Joanie.ts +216 -1
  65. package/js/utils/AbilitiesHelper/agreementAbilities.ts +14 -0
  66. package/js/utils/AbilitiesHelper/index.ts +7 -0
  67. package/js/utils/AbilitiesHelper/types.ts +12 -3
  68. package/js/utils/ObjectHelper/index.ts +20 -0
  69. package/js/utils/OrderHelper/index.ts +10 -0
  70. package/js/utils/errors/HttpError.ts +1 -0
  71. package/js/utils/test/factories/joanie.ts +156 -1
  72. package/js/widgets/Dashboard/components/DashboardBatchOrderLoader/_styles.scss +14 -0
  73. package/js/widgets/Dashboard/components/DashboardBatchOrderLoader/index.tsx +32 -0
  74. package/js/widgets/Dashboard/components/DashboardCard/index.spec.tsx +18 -0
  75. package/js/widgets/Dashboard/components/DashboardCard/index.stories.tsx +25 -2
  76. package/js/widgets/Dashboard/components/DashboardCard/index.tsx +4 -2
  77. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/BatchOrderPaymentManager.tsx +88 -0
  78. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/index.tsx +216 -0
  79. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/DashboardBatchOrderSubItems.tsx +316 -0
  80. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/index.spec.tsx +27 -0
  81. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/index.tsx +175 -0
  82. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +5 -2
  83. package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.tsx +4 -1
  84. package/js/widgets/Dashboard/components/DashboardItem/Order/_styles.scss +5 -0
  85. package/js/widgets/Dashboard/components/DashboardItem/_styles.scss +43 -0
  86. package/js/widgets/Dashboard/components/DashboardSidebar/components/AgreementNavLink/index.spec.tsx +214 -0
  87. package/js/widgets/Dashboard/components/DashboardSidebar/components/AgreementNavLink/index.tsx +47 -0
  88. package/js/widgets/Dashboard/components/LearnerDashboardSidebar/index.tsx +1 -0
  89. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.spec.tsx +21 -3
  90. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +9 -0
  91. package/js/widgets/Dashboard/utils/learnerRoutes.tsx +30 -0
  92. package/js/widgets/Dashboard/utils/learnerRoutesPaths.tsx +12 -0
  93. package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +12 -0
  94. package/js/widgets/Dashboard/utils/teacherRoutes.tsx +17 -0
  95. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +8 -2
  96. package/package.json +4 -1
  97. package/scss/colors/_theme.scss +1 -1
  98. package/scss/components/_index.scss +1 -0
@@ -1,5 +1,5 @@
1
1
  import { JoanieUserProfile } from 'types/User';
2
- import { Contract } from 'types/Joanie';
2
+ import { Agreement, Contract } from 'types/Joanie';
3
3
 
4
4
  export enum JoanieUserProfileActions {
5
5
  ACCESS_TEACHER_DASHBOARD = 'access_teacher_dashboard',
@@ -9,6 +9,10 @@ export enum ContractActions {
9
9
  SIGN = 'sign',
10
10
  }
11
11
 
12
+ export enum AgreementActions {
13
+ SIGN = 'sign',
14
+ }
15
+
12
16
  const JOANIE_PROFILE_KEYS = [
13
17
  'abilities',
14
18
  'full_name',
@@ -30,7 +34,12 @@ export const isContractEntity = (entity: Entity): entity is Contract => {
30
34
  return CONTRACT_KEYS.every((key) => entity.hasOwnProperty(key));
31
35
  };
32
36
 
37
+ export function isAgreementEntity(entity: any): entity is Agreement {
38
+ const AGREEMENT_KEYS = ['batch_order', 'abilities', 'id'];
39
+ return AGREEMENT_KEYS.every((key) => entity.hasOwnProperty(key));
40
+ }
41
+
33
42
  // further entities and entity actions can be added here
34
43
  // like Entity = JoanieUserProfileEntity | CourseEntity
35
- export type Entity = JoanieUserProfile | Contract;
36
- export type Actions = JoanieUserProfileActions | ContractActions;
44
+ export type Entity = JoanieUserProfile | Contract | Agreement;
45
+ export type Actions = JoanieUserProfileActions | ContractActions | AgreementActions;
@@ -11,4 +11,24 @@ export class ObjectHelper {
11
11
  keys.forEach((key) => delete cleanedObject[key]);
12
12
  return cleanedObject;
13
13
  }
14
+
15
+ static removeEmptyFields<T>(obj: T): T | undefined {
16
+ if (Array.isArray(obj)) {
17
+ const arr = obj.map((v) => this.removeEmptyFields(v)).filter((v) => v !== undefined);
18
+ return arr.length > 0 ? (arr as unknown as T) : undefined;
19
+ }
20
+ if (obj && typeof obj === 'object') {
21
+ const cleanedEntries = Object.entries(obj)
22
+ .map(([k, v]) => [k, this.removeEmptyFields(v)])
23
+ .filter(([, v]) => {
24
+ if (v === undefined || v === null) return false;
25
+ if (typeof v === 'string' && v.trim() === '') return false;
26
+ if (Array.isArray(v) && v.length === 0) return false;
27
+ if (typeof v === 'object' && Object.keys(v).length === 0) return false;
28
+ return true;
29
+ });
30
+ return cleanedEntries.length > 0 ? (Object.fromEntries(cleanedEntries) as T) : undefined;
31
+ }
32
+ return obj;
33
+ }
14
34
  }
@@ -104,4 +104,14 @@ export class OrderHelper {
104
104
  if (!order) return true;
105
105
  return PURCHASABLE_ORDER_STATES.includes(order.state);
106
106
  }
107
+
108
+ static isFreeWithVoucher(order?: Order) {
109
+ if (!order) return false;
110
+ return order.total === 0;
111
+ }
112
+
113
+ static isFreeFromBatchOrder(order?: Order) {
114
+ if (!order) return false;
115
+ return order.from_batch_order === true;
116
+ }
107
117
  }
@@ -31,5 +31,6 @@ export enum HttpStatusCode {
31
31
  FORBIDDEN = 403,
32
32
  NOT_FOUND = 404,
33
33
  CONFLICT = 409,
34
+ TOO_MANY_REQUESTS = 429,
34
35
  INTERNAL_SERVER_ERROR = 500,
35
36
  }
@@ -38,8 +38,16 @@ import {
38
38
  TargetCourse,
39
39
  UserLight,
40
40
  PaymentPlan,
41
+ OfferingBatchOrder,
42
+ BatchOrderRead,
43
+ BatchOrder,
44
+ BatchOrderState,
45
+ OrganizationQuote,
46
+ BatchOrderQuote,
47
+ Relation,
48
+ Agreement,
41
49
  } from 'types/Joanie';
42
- import { Payment, PaymentProviders } from 'components/PaymentInterfaces/types';
50
+ import { Payment, PaymentMethod, PaymentProviders } from 'components/PaymentInterfaces/types';
43
51
  import { CourseStateFactory } from 'utils/test/factories/richie';
44
52
  import { FactoryHelper } from 'utils/test/factories/helper';
45
53
  import { JoanieUserApiAbilityActions, JoanieUserProfile } from 'types/User';
@@ -166,6 +174,51 @@ export const OrganizationFactory = factory((): Organization => {
166
174
  };
167
175
  });
168
176
 
177
+ export const RelationFactory = factory((): Relation => {
178
+ return {
179
+ id: faker.string.uuid(),
180
+ product: {
181
+ id: faker.string.uuid(),
182
+ title: faker.commerce.productName(),
183
+ },
184
+ course: {
185
+ id: faker.string.uuid(),
186
+ title: faker.lorem.words({ min: 2, max: 5 }),
187
+ },
188
+ };
189
+ });
190
+
191
+ export const BatchOrderQuoteFactory = factory((): BatchOrderQuote => {
192
+ return {
193
+ id: faker.string.uuid(),
194
+ owner_name: faker.person.firstName(),
195
+ organization_id: faker.string.uuid(),
196
+ state: faker.helpers.arrayElement(Object.values(BatchOrderState)),
197
+ company_name: faker.company.name(),
198
+ relation: RelationFactory().one(),
199
+ payment_method: faker.helpers.arrayElement(Object.values(PaymentMethod)),
200
+ contract_submitted: faker.datatype.boolean(),
201
+ nb_seats: faker.number.int({ min: 1, max: 100 }),
202
+ available_actions: {
203
+ confirm_quote: false,
204
+ confirm_purchase_order: false,
205
+ confirm_bank_transfer: false,
206
+ submit_for_signature: false,
207
+ next_action: null,
208
+ },
209
+ };
210
+ });
211
+
212
+ export const OrganizationQuoteFactory = factory((): OrganizationQuote => {
213
+ return {
214
+ id: faker.string.uuid(),
215
+ organization_signed_on: faker.date.past().toISOString(),
216
+ has_purchase_order: faker.datatype.boolean(),
217
+ batch_order: BatchOrderQuoteFactory().one(),
218
+ total: faker.number.float({ min: 1, max: 100 }),
219
+ };
220
+ });
221
+
169
222
  export const OrganizationLightFactory = factory((): OrganizationLight => {
170
223
  return {
171
224
  code: faker.string.alphanumeric(5),
@@ -173,6 +226,17 @@ export const OrganizationLightFactory = factory((): OrganizationLight => {
173
226
  };
174
227
  });
175
228
 
229
+ export const AgreementFactory = factory((): Agreement => {
230
+ return {
231
+ id: faker.string.uuid(),
232
+ batch_order: BatchOrderQuoteFactory().one(),
233
+ abilities: {
234
+ sign: faker.datatype.boolean(),
235
+ },
236
+ organization_signed_on: faker.date.past().toISOString(),
237
+ };
238
+ });
239
+
176
240
  export const CertificationDefinitionFactory = factory((): CertificateDefinition => {
177
241
  return {
178
242
  id: faker.string.uuid(),
@@ -336,6 +400,16 @@ export const OfferingFactory = factory((): Offering => {
336
400
  };
337
401
  });
338
402
 
403
+ export const OfferingBatchOrderFactory = factory((): OfferingBatchOrder => {
404
+ return {
405
+ product: {
406
+ id: faker.string.uuid(),
407
+ title: faker.string.alphanumeric(5),
408
+ },
409
+ course: CourseListItemFactory().one(),
410
+ };
411
+ });
412
+
339
413
  export const CourseListItemFactory = factory((): CourseListItem => {
340
414
  return {
341
415
  id: faker.string.uuid(),
@@ -367,6 +441,7 @@ export const PaymentPlanFactory = factory((): PaymentPlan => {
367
441
  price: faker.number.int({ min: 1, max: 1000, multipleOf: 10 }),
368
442
  discount: undefined,
369
443
  discounted_price: undefined,
444
+ from_batch_order: false,
370
445
  };
371
446
  });
372
447
 
@@ -391,6 +466,78 @@ export const OrderLiteFactory = factory((): OrderLite => {
391
466
  };
392
467
  });
393
468
 
469
+ export const BatchOrderFactory = factory((): BatchOrder => {
470
+ return {
471
+ id: faker.string.uuid(),
472
+ offering_id: faker.string.uuid(),
473
+ company_name: faker.company.name(),
474
+ identification_number: faker.string.alphanumeric(14),
475
+ vat_registration: faker.string.alphanumeric(11),
476
+ address: faker.location.streetAddress(),
477
+ postcode: faker.location.zipCode(),
478
+ city: faker.location.city(),
479
+ country: faker.location.countryCode(),
480
+ administrative_lastname: faker.person.lastName(),
481
+ administrative_firstname: faker.person.firstName(),
482
+ administrative_profession: faker.person.jobTitle(),
483
+ administrative_email: faker.internet.email(),
484
+ administrative_telephone: faker.phone.number(),
485
+ signatory_lastname: faker.person.lastName(),
486
+ signatory_firstname: faker.person.firstName(),
487
+ signatory_profession: faker.person.jobTitle(),
488
+ signatory_email: faker.internet.email(),
489
+ signatory_telephone: faker.phone.number(),
490
+ nb_seats: faker.number.int({ min: 1, max: 200 }),
491
+ payment_method: faker.helpers.arrayElement([
492
+ PaymentMethod.CARD_PAYMENT,
493
+ PaymentMethod.BANK_TRANSFER,
494
+ PaymentMethod.PURCHASE_ORDER,
495
+ ]),
496
+ funding_entity: faker.company.name(),
497
+ funding_amount: faker.number.int({ min: 100, max: 10000 }),
498
+ organization_id: faker.string.uuid(),
499
+ };
500
+ });
501
+
502
+ export const BatchOrderReadFactory = factory((): BatchOrderRead => {
503
+ return {
504
+ id: faker.string.uuid(),
505
+ owner: faker.internet.email(),
506
+ total: faker.number.int({ min: 100, max: 5000 }),
507
+ currency: faker.finance.currencyCode(),
508
+ organization: OrganizationFactory().one(),
509
+ main_invoice_reference: faker.string.alphanumeric(10),
510
+ contract_id: faker.string.alphanumeric(12),
511
+ company_name: faker.company.name(),
512
+ identification_number: faker.string.alphanumeric(14),
513
+ vat_registration: faker.string.alphanumeric(11),
514
+ address: faker.location.streetAddress(),
515
+ postcode: faker.location.zipCode(),
516
+ city: faker.location.city(),
517
+ country: faker.location.countryCode(),
518
+ nb_seats: faker.number.int({ min: 1, max: 200 }),
519
+ payment_method: faker.helpers.arrayElement([
520
+ PaymentMethod.CARD_PAYMENT,
521
+ PaymentMethod.BANK_TRANSFER,
522
+ PaymentMethod.PURCHASE_ORDER,
523
+ ]),
524
+ state: faker.helpers.arrayElement([BatchOrderState.PENDING, BatchOrderState.COMPLETED]),
525
+ administrative_lastname: faker.person.lastName(),
526
+ administrative_firstname: faker.person.firstName(),
527
+ administrative_profession: faker.person.jobTitle(),
528
+ administrative_email: faker.internet.email(),
529
+ administrative_telephone: faker.phone.number(),
530
+ signatory_lastname: faker.person.lastName(),
531
+ signatory_firstname: faker.person.firstName(),
532
+ signatory_profession: faker.person.jobTitle(),
533
+ signatory_email: faker.internet.email(),
534
+ signatory_telephone: faker.phone.number(),
535
+ funding_entity: faker.company.name(),
536
+ funding_amount: faker.number.int({ min: 100, max: 10000 }),
537
+ offering: OfferingBatchOrderFactory().one(),
538
+ };
539
+ });
540
+
394
541
  export const NestedCertificateOrderFactory = factory((): NestedCertificateOrder => {
395
542
  return {
396
543
  id: faker.string.uuid(),
@@ -494,6 +641,11 @@ export const SaleTunnelContextFactory = factory(
494
641
  props: {} as SaleTunnelProps,
495
642
  billingAddress: undefined,
496
643
  setBillingAddress: noop,
644
+ batchOrder: undefined,
645
+ setBatchOrder: noop,
646
+ batchOrderFormMethods: undefined,
647
+ setBatchOrderFormMethods: noop,
648
+ validateBatchOrder: noop,
497
649
  setCreditCard: noop,
498
650
  setHasWaivedWithdrawalRight: noop,
499
651
  hasWaivedWithdrawalRight: false,
@@ -503,5 +655,8 @@ export const SaleTunnelContextFactory = factory(
503
655
  runSubmitCallbacks: () => new Promise((resolve) => resolve()),
504
656
  nextStep: noop,
505
657
  setVoucherCode: noop,
658
+ setSchedule: noop,
659
+ needsPayment: true,
660
+ setNeedsPayment: noop,
506
661
  }),
507
662
  );
@@ -0,0 +1,14 @@
1
+ .dashboard-order-loader__banners {
2
+ .banner {
3
+ margin-top: 0;
4
+
5
+ a {
6
+ color: white;
7
+ text-decoration: underline;
8
+
9
+ &:hover {
10
+ color: white;
11
+ }
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,32 @@
1
+ import { useParams } from 'react-router';
2
+ import { defineMessages, FormattedMessage } from 'react-intl';
3
+ import { Spinner } from 'components/Spinner';
4
+ import { useBatchOrder } from 'hooks/useBatchOrder';
5
+ import { DashboardItemBatchOrder } from '../DashboardItem/BatchOrder';
6
+
7
+ const messages = defineMessages({
8
+ loading: {
9
+ defaultMessage: 'Loading order ...',
10
+ description: 'Message displayed while loading an order',
11
+ id: 'components.DashboardOrderLoader.loading',
12
+ },
13
+ });
14
+
15
+ export const DashboardBatchOrderLoader = () => {
16
+ const params = useParams<{ batchOrderId: string }>();
17
+ const { item: batchOrder, states } = useBatchOrder(params.batchOrderId);
18
+ const fetching = states.isPending;
19
+
20
+ return (
21
+ <>
22
+ {fetching && !batchOrder && (
23
+ <Spinner aria-labelledby="loading-courses-data">
24
+ <span id="loading-courses-data">
25
+ <FormattedMessage {...messages.loading} />
26
+ </span>
27
+ </Spinner>
28
+ )}
29
+ {batchOrder && <DashboardItemBatchOrder batchOrder={batchOrder} showDetails />}
30
+ </>
31
+ );
32
+ };
@@ -17,4 +17,22 @@ describe('<DashboardCard/>', () => {
17
17
  fireEvent.click(screen.getByTestId('dashboard-card__header__toggle'));
18
18
  expect(screen.getByTestId('dashboard-card__wrapper').style.height).toBe('0px');
19
19
  });
20
+
21
+ it('is not expanded', async () => {
22
+ render(
23
+ <DashboardCard
24
+ defaultExpanded={false}
25
+ header="My header"
26
+ footer={<Button color="primary">Update</Button>}
27
+ >
28
+ Content here
29
+ </DashboardCard>,
30
+ );
31
+
32
+ expect(screen.getByTestId('dashboard-card__wrapper').style.height).toBe('0px');
33
+ const card = screen.getByText('My header').closest('.dashboard-card');
34
+ expect(card).toHaveClass('dashboard-card--closed');
35
+ fireEvent.click(screen.getByTestId('dashboard-card__header__toggle'));
36
+ expect(card).toHaveClass('dashboard-card--opened');
37
+ });
20
38
  });
@@ -1,6 +1,5 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
  import { Button } from '@openfun/cunningham-react';
3
- import Input from 'components/Form/Input';
4
3
  import { DashboardBox } from '../DashboardBox';
5
4
  import { DashboardCard } from './index';
6
5
 
@@ -18,7 +17,15 @@ type Story = StoryObj<typeof DashboardCard>;
18
17
  export const Default: Story = {
19
18
  args: {
20
19
  header: 'Billing Addresses',
21
- children: <Input name="default" label="Country" />,
20
+ children: (
21
+ <div>
22
+ <div>
23
+ <div>Home</div>
24
+ <strong>Pierre Léger</strong>
25
+ <p>21 rue du pavillon - 78130 Chapter ( France )</p>
26
+ </div>
27
+ </div>
28
+ ),
22
29
  footer: <Button color="primary">Update</Button>,
23
30
  },
24
31
  };
@@ -47,3 +54,19 @@ export const WithBoxes: Story = {
47
54
  ),
48
55
  },
49
56
  };
57
+
58
+ export const NotExpanded: Story = {
59
+ args: {
60
+ header: 'Not expanded',
61
+ defaultExpanded: false,
62
+ children: (
63
+ <div>
64
+ <div>
65
+ <div>Home</div>
66
+ <strong>Pierre Léger</strong>
67
+ <p>21 rue du pavillon - 78130 Chapter ( France )</p>
68
+ </div>
69
+ </div>
70
+ ),
71
+ },
72
+ };
@@ -9,6 +9,7 @@ interface Props {
9
9
  expandable?: boolean;
10
10
  fullWidth?: boolean;
11
11
  className?: string;
12
+ defaultExpanded?: boolean;
12
13
  }
13
14
 
14
15
  export const DashboardCard = ({
@@ -18,10 +19,11 @@ export const DashboardCard = ({
18
19
  className,
19
20
  expandable = true,
20
21
  fullWidth = false,
22
+ defaultExpanded = true,
21
23
  }: PropsWithChildren<Props>) => {
22
- const [opened, setOpened] = useState(true);
24
+ const [opened, setOpened] = useState(defaultExpanded);
23
25
  const expandableRef = useRef<HTMLDivElement>(null);
24
- const [wrapperHeight, setWrapperHeight] = useState('auto');
26
+ const [wrapperHeight, setWrapperHeight] = useState(defaultExpanded ? 'auto' : '0');
25
27
 
26
28
  const toggle = () => {
27
29
  if (opened) {
@@ -0,0 +1,88 @@
1
+ import React, { useEffect } from 'react';
2
+ import { Button, useModal } from '@openfun/cunningham-react';
3
+ import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
4
+ import { useBatchOrder } from 'hooks/useBatchOrder';
5
+ import { BatchOrderRead, BatchOrderState } from 'types/Joanie';
6
+ import { DashboardSubItem } from 'widgets/Dashboard/components/DashboardItem/DashboardSubItem';
7
+ import { DashboardSubItemsList } from '../../DashboardSubItemsList';
8
+ import { BatchOrderPaymentModal } from '.';
9
+
10
+ const messages = defineMessages({
11
+ batchOrderPayment: {
12
+ id: 'components.ProductCertificateFooter.batchOrderPayment',
13
+ description: 'Label before the payment button',
14
+ defaultMessage: 'We are waiting for your payment to finalize the batch order.',
15
+ },
16
+ paymentProcessing: {
17
+ id: 'components.ProductCertificateFooter.paymentProcessing',
18
+ description: 'Button label for the payment is processing message',
19
+ defaultMessage: 'Payment processing...',
20
+ },
21
+ paymentNeededButton: {
22
+ id: 'components.ProductCertificateFooter.paymentNeededButton',
23
+ description: 'Button label for the payment needed message',
24
+ defaultMessage: 'Pay {amount}',
25
+ },
26
+ paymentSectionTitle: {
27
+ id: 'batchOrder.payment.sectionTitle',
28
+ description: 'Title for the payment section in dashboard',
29
+ defaultMessage: 'Payment required',
30
+ },
31
+ });
32
+
33
+ interface BatchPaymentManagerProps {
34
+ batchOrder: BatchOrderRead;
35
+ }
36
+
37
+ export const BatchOrderPaymentManager = ({ batchOrder }: BatchPaymentManagerProps) => {
38
+ const intl = useIntl();
39
+ const retryModal = useModal();
40
+ const batchOrderQuery = useBatchOrder(batchOrder.id);
41
+ const processingPayment = batchOrder.state === BatchOrderState.PROCESS_PAYMENT;
42
+
43
+ useEffect(() => {
44
+ if (batchOrderQuery.item) {
45
+ batchOrderQuery.methods.invalidate();
46
+ }
47
+ }, [batchOrderQuery.item]);
48
+
49
+ return (
50
+ <DashboardSubItemsList
51
+ subItems={[
52
+ <DashboardSubItem
53
+ title={intl.formatMessage(messages.paymentSectionTitle)}
54
+ footer={
55
+ <div className="content">
56
+ <FormattedMessage {...messages.batchOrderPayment} />
57
+ <Button
58
+ size="small"
59
+ color="primary"
60
+ onClick={retryModal.open}
61
+ disabled={processingPayment}
62
+ >
63
+ {processingPayment ? (
64
+ <FormattedMessage {...messages.paymentProcessing} />
65
+ ) : (
66
+ <FormattedMessage
67
+ {...messages.paymentNeededButton}
68
+ values={{
69
+ amount: intl.formatNumber(batchOrder.total ?? 0, {
70
+ style: 'currency',
71
+ currency: batchOrder.currency ?? 'EUR',
72
+ }),
73
+ }}
74
+ />
75
+ )}
76
+ </Button>
77
+ <BatchOrderPaymentModal
78
+ {...retryModal}
79
+ batchOrder={batchOrder}
80
+ onClose={batchOrderQuery.methods.invalidate}
81
+ />
82
+ </div>
83
+ }
84
+ />,
85
+ ]}
86
+ />
87
+ );
88
+ };