richie-education 3.2.1 → 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 (78) hide show
  1. package/js/api/joanie.ts +144 -0
  2. package/js/components/PaymentInterfaces/types.ts +7 -0
  3. package/js/components/PaymentScheduleGrid/index.tsx +4 -2
  4. package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +9 -2
  5. package/js/components/SaleTunnel/GenericSaleTunnel.tsx +33 -0
  6. package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup.tsx +253 -0
  7. package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular.tsx +314 -0
  8. package/js/components/SaleTunnel/SaleTunnelInformation/StepContent.tsx +528 -0
  9. package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +47 -271
  10. package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +25 -11
  11. package/js/components/SaleTunnel/SubscriptionButton/index.tsx +54 -6
  12. package/js/components/SaleTunnel/_styles.scss +55 -0
  13. package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +356 -0
  14. package/js/components/SaleTunnel/{index.full-process.spec.tsx → index.full-process-b2c.spec.tsx} +4 -1
  15. package/js/components/SaleTunnel/index.spec.tsx +104 -0
  16. package/js/hooks/useBatchOrder/index.tsx +36 -0
  17. package/js/hooks/useContractArchive/index.ts +2 -0
  18. package/js/hooks/useOfferingOrganizations/index.tsx +38 -0
  19. package/js/hooks/useOrganizationAgreements.tsx/index.tsx +66 -0
  20. package/js/hooks/useOrganizationQuotes/index.tsx +56 -0
  21. package/js/hooks/useTeacherPendingAgreementsCount/index.ts +34 -0
  22. package/js/pages/DashboardBatchOrderLayout/_styles.scss +5 -0
  23. package/js/pages/DashboardBatchOrderLayout/index.spec.tsx +78 -0
  24. package/js/pages/DashboardBatchOrderLayout/index.tsx +45 -0
  25. package/js/pages/DashboardBatchOrders/index.spec.tsx +237 -0
  26. package/js/pages/DashboardBatchOrders/index.tsx +84 -0
  27. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardCourseContractsLayout/index.tsx +0 -1
  28. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +3 -1
  29. package/js/pages/TeacherDashboardOrganizationAgreements/AgreementActionsBar.tsx +49 -0
  30. package/js/pages/TeacherDashboardOrganizationAgreements/BulkAgreementContractButton.tsx +79 -0
  31. package/js/pages/TeacherDashboardOrganizationAgreements/OrganizationAgreementFrame.tsx +71 -0
  32. package/js/pages/TeacherDashboardOrganizationAgreements/SignOrganizationAgreementButton.tsx +60 -0
  33. package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useAgreementsAbilities.tsx +8 -0
  34. package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useHasAgreementToDownload.tsx +27 -0
  35. package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useTeacherAgreementsToSign.tsx +32 -0
  36. package/js/pages/TeacherDashboardOrganizationAgreements/index.spec.tsx +433 -0
  37. package/js/pages/TeacherDashboardOrganizationAgreements/index.tsx +130 -0
  38. package/js/pages/TeacherDashboardOrganizationAgreementsLayout/index.tsx +25 -0
  39. package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +9 -0
  40. package/js/pages/TeacherDashboardOrganizationQuotes/_styles.scss +40 -0
  41. package/js/pages/TeacherDashboardOrganizationQuotes/index.full-process.spec.tsx +194 -0
  42. package/js/pages/TeacherDashboardOrganizationQuotes/index.spec.tsx +144 -0
  43. package/js/pages/TeacherDashboardOrganizationQuotes/index.tsx +521 -0
  44. package/js/pages/TeacherDashboardOrganizationQuotesLayout/index.tsx +26 -0
  45. package/js/types/Joanie.ts +216 -1
  46. package/js/utils/AbilitiesHelper/agreementAbilities.ts +14 -0
  47. package/js/utils/AbilitiesHelper/index.ts +7 -0
  48. package/js/utils/AbilitiesHelper/types.ts +12 -3
  49. package/js/utils/ObjectHelper/index.ts +20 -0
  50. package/js/utils/OrderHelper/index.ts +10 -0
  51. package/js/utils/test/factories/joanie.ts +156 -1
  52. package/js/widgets/Dashboard/components/DashboardBatchOrderLoader/_styles.scss +14 -0
  53. package/js/widgets/Dashboard/components/DashboardBatchOrderLoader/index.tsx +32 -0
  54. package/js/widgets/Dashboard/components/DashboardCard/index.spec.tsx +18 -0
  55. package/js/widgets/Dashboard/components/DashboardCard/index.stories.tsx +25 -2
  56. package/js/widgets/Dashboard/components/DashboardCard/index.tsx +4 -2
  57. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/BatchOrderPaymentManager.tsx +88 -0
  58. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/index.tsx +216 -0
  59. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/DashboardBatchOrderSubItems.tsx +316 -0
  60. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/index.spec.tsx +27 -0
  61. package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/index.tsx +175 -0
  62. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +5 -2
  63. package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.tsx +4 -1
  64. package/js/widgets/Dashboard/components/DashboardItem/Order/_styles.scss +5 -0
  65. package/js/widgets/Dashboard/components/DashboardItem/_styles.scss +43 -0
  66. package/js/widgets/Dashboard/components/DashboardSidebar/components/AgreementNavLink/index.spec.tsx +214 -0
  67. package/js/widgets/Dashboard/components/DashboardSidebar/components/AgreementNavLink/index.tsx +47 -0
  68. package/js/widgets/Dashboard/components/LearnerDashboardSidebar/index.tsx +1 -0
  69. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.spec.tsx +21 -3
  70. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +9 -0
  71. package/js/widgets/Dashboard/utils/learnerRoutes.tsx +30 -0
  72. package/js/widgets/Dashboard/utils/learnerRoutesPaths.tsx +12 -0
  73. package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +12 -0
  74. package/js/widgets/Dashboard/utils/teacherRoutes.tsx +17 -0
  75. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +8 -2
  76. package/package.json +4 -1
  77. package/scss/colors/_theme.scss +1 -1
  78. package/scss/components/_index.scss +1 -0
@@ -3,7 +3,7 @@ import type { Nullable } from 'types/utils';
3
3
  import { Resource, ResourcesQuery } from 'hooks/useResources';
4
4
  import { OrderResourcesQuery } from 'hooks/useOrders';
5
5
  import { Course as RichieCourse } from 'types/Course';
6
- import { Payment } from 'components/PaymentInterfaces/types';
6
+ import { Payment, PaymentMethod } from 'components/PaymentInterfaces/types';
7
7
  import { JoanieUserProfile } from './User';
8
8
 
9
9
  // - Generic
@@ -65,6 +65,13 @@ export interface Contract {
65
65
 
66
66
  export type ContractLight = Pick<Contract, 'id' | 'organization_signed_on' | 'student_signed_on'>;
67
67
 
68
+ export interface Agreement {
69
+ id: string;
70
+ batch_order: BatchOrderQuote;
71
+ abilities?: ContractAbilities;
72
+ organization_signed_on?: string;
73
+ }
74
+
68
75
  export interface CourseListItem extends Resource {
69
76
  id: string;
70
77
  title: string;
@@ -343,6 +350,7 @@ export interface Order {
343
350
  organization: Organization;
344
351
  payment_schedule?: PaymentSchedule;
345
352
  credit_card_id?: CreditCard['id'];
353
+ from_batch_order?: boolean;
346
354
  }
347
355
 
348
356
  export interface CredentialOrder extends Order {
@@ -449,6 +457,156 @@ export interface Address {
449
457
  title: string;
450
458
  }
451
459
 
460
+ // BatchOrder
461
+
462
+ export interface Billing {
463
+ company_name?: string;
464
+ identification_number?: string;
465
+ contact_name?: string;
466
+ contact_email?: string;
467
+ address?: string;
468
+ postcode?: string;
469
+ city?: string;
470
+ country?: string;
471
+ }
472
+
473
+ export interface OfferingBatchOrder {
474
+ product: {
475
+ id: string;
476
+ title: string;
477
+ };
478
+ course: CourseListItem;
479
+ }
480
+
481
+ export enum BatchOrderState {
482
+ DRAFT = 'draft',
483
+ ASSIGNED = 'assigned',
484
+ QUOTED = 'quoted',
485
+ TO_SIGN = 'to_sign',
486
+ SIGNING = 'signing',
487
+ PENDING = 'pending',
488
+ PROCESS_PAYMENT = 'process_payment',
489
+ FAILED_PAYMENT = 'failed_payment',
490
+ CANCELED = 'canceled',
491
+ COMPLETED = 'completed',
492
+ }
493
+
494
+ export interface BatchOrder {
495
+ id?: string;
496
+ offering_id: string;
497
+ company_name: string;
498
+ identification_number: string;
499
+ vat_registration?: string;
500
+ address: string;
501
+ postcode: string;
502
+ city: string;
503
+ country: string;
504
+ administrative_lastname: string;
505
+ administrative_firstname: string;
506
+ administrative_profession: string;
507
+ administrative_email: string;
508
+ administrative_telephone: string;
509
+ signatory_lastname: string;
510
+ signatory_firstname: string;
511
+ signatory_profession: string;
512
+ signatory_email: string;
513
+ signatory_telephone: string;
514
+ billing_address?: Billing;
515
+ nb_seats: number;
516
+ payment_method: PaymentMethod;
517
+ funding_entity?: string;
518
+ funding_amount?: number;
519
+ organization_id?: string;
520
+ }
521
+
522
+ export interface BatchOrderRead {
523
+ id: string;
524
+ owner: string;
525
+ total: number;
526
+ currency: string;
527
+ organization: Organization;
528
+ main_invoice_reference?: string;
529
+ contract_id?: string;
530
+ company_name: string;
531
+ identification_number: string;
532
+ vat_registration?: string;
533
+ address: string;
534
+ postcode: string;
535
+ city: string;
536
+ country: string;
537
+ nb_seats: number;
538
+ payment_method: PaymentMethod;
539
+ state: BatchOrderState;
540
+ administrative_lastname: string;
541
+ administrative_firstname: string;
542
+ administrative_profession: string;
543
+ administrative_email: string;
544
+ administrative_telephone: string;
545
+ signatory_lastname: string;
546
+ signatory_firstname: string;
547
+ signatory_profession: string;
548
+ signatory_email: string;
549
+ signatory_telephone: string;
550
+ billing_address?: Billing;
551
+ funding_entity?: string;
552
+ funding_amount?: number;
553
+ offering: OfferingBatchOrder;
554
+ }
555
+
556
+ export interface Relation {
557
+ id: string;
558
+ product: {
559
+ title: string;
560
+ id: string;
561
+ };
562
+ course: {
563
+ title: string;
564
+ id: string;
565
+ };
566
+ }
567
+
568
+ export type BatchOrderAction =
569
+ | 'confirm_quote'
570
+ | 'confirm_purchase_order'
571
+ | 'confirm_bank_transfer'
572
+ | 'submit_for_signature';
573
+
574
+ export type BatchOrderAvailableActions = {
575
+ [K in BatchOrderAction]: boolean;
576
+ } & {
577
+ next_action: BatchOrderAction | null;
578
+ };
579
+
580
+ export interface BatchOrderQuote {
581
+ id: string;
582
+ owner_name: string;
583
+ organization_id: string;
584
+ state: BatchOrderState;
585
+ company_name: string;
586
+ relation: Relation;
587
+ payment_method: PaymentMethod;
588
+ contract_submitted: boolean;
589
+ nb_seats: number;
590
+ available_actions: BatchOrderAvailableActions;
591
+ }
592
+
593
+ export interface Definition {
594
+ id: string;
595
+ title: string;
596
+ description: string;
597
+ name: string;
598
+ body: string;
599
+ language: string;
600
+ }
601
+
602
+ export interface OrganizationQuote {
603
+ id: string;
604
+ organization_signed_on: string;
605
+ has_purchase_order: boolean;
606
+ batch_order: BatchOrderQuote;
607
+ total: number;
608
+ }
609
+
452
610
  // Wishlist
453
611
  export interface CourseWish extends Resource {
454
612
  status: boolean;
@@ -487,6 +645,7 @@ export interface PaymentPlan {
487
645
  discount?: string;
488
646
  discounted_price?: number;
489
647
  payment_schedule: PaymentSchedule;
648
+ from_batch_order: boolean;
490
649
  }
491
650
 
492
651
  // - API
@@ -563,6 +722,16 @@ export interface OfferingQueryFilters extends PaginatedResourceQuery {
563
722
  product_type?: ProductType;
564
723
  query?: string;
565
724
  }
725
+ export interface BatchOrderQueryFilters extends PaginatedResourceQuery {
726
+ id?: BatchOrderRead['id'];
727
+ }
728
+ export interface OrganizationQuoteQueryFilters extends PaginatedResourceQuery {
729
+ organization_id?: Organization['id'];
730
+ batch_order_id?: BatchOrder['id'];
731
+ quote_id?: OrganizationQuote['id'];
732
+ id?: OrganizationQuote['id'];
733
+ payload?: any;
734
+ }
566
735
 
567
736
  export enum ContractState {
568
737
  UNSIGNED = 'unsigned',
@@ -580,6 +749,7 @@ export interface OrganizationContractSignatureLinksFilters {
580
749
  contracts_ids?: string[];
581
750
  organization_id: Organization['id'];
582
751
  offering_ids?: Offering['id'][];
752
+ from_batch_order?: boolean;
583
753
  }
584
754
 
585
755
  export interface ContractInvitationLinkResponse {
@@ -638,6 +808,17 @@ interface APIUser {
638
808
  ): Promise<Payment>;
639
809
  set_payment_method(payload: OrderSetPaymentMethodPayload): Promise<void>;
640
810
  };
811
+ batchOrders: {
812
+ create(payload: BatchOrder): Promise<BatchOrderRead>;
813
+ get<Filters extends PaginatedResourceQuery = PaginatedResourceQuery>(
814
+ filters?: Filters,
815
+ ): Filters extends { id: string }
816
+ ? Promise<Nullable<BatchOrderRead>>
817
+ : Promise<PaginatedResponse<BatchOrderRead>>;
818
+ submit_for_payment: {
819
+ create(filters: ResourcesQuery): Promise<any>;
820
+ };
821
+ };
641
822
  certificates: {
642
823
  download(id: string): Promise<File>;
643
824
  get<Filters extends CertificateResourcesQuery = CertificateResourcesQuery>(
@@ -674,9 +855,11 @@ interface APIUser {
674
855
  create: ({
675
856
  organization_id,
676
857
  offering_id,
858
+ from_batch_order,
677
859
  }: {
678
860
  organization_id?: Organization['id'];
679
861
  offering_id?: Offering['id'];
862
+ from_batch_order?: boolean;
680
863
  }) => Promise<{ url: string }>;
681
864
  get: (id: string) => Promise<File>;
682
865
  };
@@ -719,6 +902,33 @@ export interface API {
719
902
  filters?: OrganizationContractSignatureLinksFilters,
720
903
  ): Promise<OrganizationContractInvitationLinkResponse>;
721
904
  };
905
+ quotes: {
906
+ get(
907
+ filters?: OrganizationQuoteQueryFilters,
908
+ ): OrganizationQuoteQueryFilters extends { id: string }
909
+ ? Promise<Nullable<OrganizationQuote>>
910
+ : Promise<PaginatedResponse<OrganizationQuote>>;
911
+ update(filters: OrganizationQuoteQueryFilters): Promise<any>;
912
+ purchase_order: {
913
+ update(filters: OrganizationQuoteQueryFilters): Promise<any>;
914
+ };
915
+ bank_transfer: {
916
+ create(filters: OrganizationQuoteQueryFilters): Promise<any>;
917
+ };
918
+ submit_for_signature: {
919
+ create(filters: OrganizationQuoteQueryFilters): Promise<any>;
920
+ };
921
+ download_quote: {
922
+ get(filters: OrganizationQuoteQueryFilters): Promise<any>;
923
+ };
924
+ };
925
+ agreements: {
926
+ get(
927
+ filters?: ContractResourceQuery,
928
+ ): ContractResourceQuery extends { id: string }
929
+ ? Promise<Nullable<Agreement>>
930
+ : Promise<PaginatedResponse<Agreement>>;
931
+ };
722
932
  };
723
933
  courseRuns: {
724
934
  get(
@@ -731,6 +941,11 @@ export interface API {
731
941
  ): Filters extends { id: string }
732
942
  ? Promise<Nullable<Offering>>
733
943
  : Promise<PaginatedResponse<OfferingLight>>;
944
+ organizations: {
945
+ get<Filters extends ResourcesQuery = ResourcesQuery>(
946
+ filters?: Filters,
947
+ ): Filters extends { id: string } ? Promise<Nullable<Organization>> : Promise<Organization[]>;
948
+ };
734
949
  };
735
950
  contractDefinitions: {
736
951
  previewTemplate(id: string): Promise<File>;
@@ -0,0 +1,14 @@
1
+ import { Agreement } from 'types/Joanie';
2
+ import { AgreementActions } from './types';
3
+
4
+ type AgreementAbilityList = {
5
+ [action in AgreementActions]: (entities: Agreement) => boolean;
6
+ };
7
+
8
+ const abilities: AgreementAbilityList = {
9
+ [AgreementActions.SIGN]: (agreement: Agreement) => {
10
+ return agreement?.abilities?.sign === true;
11
+ },
12
+ };
13
+
14
+ export default abilities;
@@ -1,5 +1,6 @@
1
1
  import { Maybe, Nullable } from 'types/utils';
2
2
  import contractAbilities from 'utils/AbilitiesHelper/contractAbilities';
3
+ import agreementAbilities from 'utils/AbilitiesHelper/agreementAbilities';
3
4
  import joanieUserProfileAbilities from './joanieUserProfileAbilities';
4
5
  import {
5
6
  Entity,
@@ -8,6 +9,8 @@ import {
8
9
  isJoanieUserProfileEntity,
9
10
  isContractEntity,
10
11
  ContractActions,
12
+ isAgreementEntity,
13
+ AgreementActions,
11
14
  } from './types';
12
15
 
13
16
  // further actions can be added here
@@ -37,6 +40,10 @@ const can = (entities: Maybe<Nullable<Entity | Entity[]>>, action: Actions) => {
37
40
  return contractAbilities[action as ContractActions](entity);
38
41
  }
39
42
 
43
+ if (isAgreementEntity(entity)) {
44
+ return agreementAbilities[action as AgreementActions](entity);
45
+ }
46
+
40
47
  return false;
41
48
  });
42
49
 
@@ -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
  }
@@ -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
  });