richie-education 3.2.1 → 3.2.2-dev27
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.
- package/js/api/joanie.ts +144 -0
- package/js/components/PaymentInterfaces/types.ts +7 -0
- package/js/components/PaymentScheduleGrid/index.tsx +4 -2
- package/js/components/SaleTunnel/AddressSelector/index.spec.tsx +9 -2
- package/js/components/SaleTunnel/GenericSaleTunnel.tsx +33 -0
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationGroup.tsx +253 -0
- package/js/components/SaleTunnel/SaleTunnelInformation/SaleTunnelInformationSingular.tsx +314 -0
- package/js/components/SaleTunnel/SaleTunnelInformation/StepContent.tsx +528 -0
- package/js/components/SaleTunnel/SaleTunnelInformation/index.tsx +47 -271
- package/js/components/SaleTunnel/SaleTunnelSuccess/index.tsx +25 -11
- package/js/components/SaleTunnel/SubscriptionButton/index.tsx +54 -6
- package/js/components/SaleTunnel/_styles.scss +55 -0
- package/js/components/SaleTunnel/index.full-process-b2b.spec.tsx +356 -0
- package/js/components/SaleTunnel/{index.full-process.spec.tsx → index.full-process-b2c.spec.tsx} +4 -1
- package/js/components/SaleTunnel/index.spec.tsx +104 -0
- package/js/hooks/useBatchOrder/index.tsx +36 -0
- package/js/hooks/useContractArchive/index.ts +2 -0
- package/js/hooks/useOfferingOrganizations/index.tsx +38 -0
- package/js/hooks/useOrganizationAgreements.tsx/index.tsx +66 -0
- package/js/hooks/useOrganizationQuotes/index.tsx +56 -0
- package/js/hooks/useTeacherPendingAgreementsCount/index.ts +34 -0
- package/js/pages/DashboardBatchOrderLayout/_styles.scss +5 -0
- package/js/pages/DashboardBatchOrderLayout/index.spec.tsx +78 -0
- package/js/pages/DashboardBatchOrderLayout/index.tsx +45 -0
- package/js/pages/DashboardBatchOrders/index.spec.tsx +237 -0
- package/js/pages/DashboardBatchOrders/index.tsx +84 -0
- package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardCourseContractsLayout/index.tsx +0 -1
- package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +3 -1
- package/js/pages/TeacherDashboardOrganizationAgreements/AgreementActionsBar.tsx +49 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/BulkAgreementContractButton.tsx +79 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/OrganizationAgreementFrame.tsx +71 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/SignOrganizationAgreementButton.tsx +60 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useAgreementsAbilities.tsx +8 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useHasAgreementToDownload.tsx +27 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/hooks/useTeacherAgreementsToSign.tsx +32 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/index.spec.tsx +433 -0
- package/js/pages/TeacherDashboardOrganizationAgreements/index.tsx +130 -0
- package/js/pages/TeacherDashboardOrganizationAgreementsLayout/index.tsx +25 -0
- package/js/pages/TeacherDashboardOrganizationCourseLoader/index.spec.tsx +9 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/_styles.scss +40 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/index.full-process.spec.tsx +194 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/index.spec.tsx +144 -0
- package/js/pages/TeacherDashboardOrganizationQuotes/index.tsx +521 -0
- package/js/pages/TeacherDashboardOrganizationQuotesLayout/index.tsx +26 -0
- package/js/types/Joanie.ts +216 -1
- package/js/utils/AbilitiesHelper/agreementAbilities.ts +14 -0
- package/js/utils/AbilitiesHelper/index.ts +7 -0
- package/js/utils/AbilitiesHelper/types.ts +12 -3
- package/js/utils/ObjectHelper/index.ts +20 -0
- package/js/utils/OrderHelper/index.ts +10 -0
- package/js/utils/test/factories/joanie.ts +156 -1
- package/js/widgets/Dashboard/components/DashboardBatchOrderLoader/_styles.scss +14 -0
- package/js/widgets/Dashboard/components/DashboardBatchOrderLoader/index.tsx +32 -0
- package/js/widgets/Dashboard/components/DashboardCard/index.spec.tsx +18 -0
- package/js/widgets/Dashboard/components/DashboardCard/index.stories.tsx +25 -2
- package/js/widgets/Dashboard/components/DashboardCard/index.tsx +4 -2
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/BatchOrderPaymentManager.tsx +88 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/BatchOrderPaymentModal/index.tsx +216 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/DashboardBatchOrderSubItems.tsx +316 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/index.spec.tsx +27 -0
- package/js/widgets/Dashboard/components/DashboardItem/BatchOrder/index.tsx +175 -0
- package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +5 -2
- package/js/widgets/Dashboard/components/DashboardItem/Order/OrganizationBlock/index.tsx +4 -1
- package/js/widgets/Dashboard/components/DashboardItem/Order/_styles.scss +5 -0
- package/js/widgets/Dashboard/components/DashboardItem/_styles.scss +43 -0
- package/js/widgets/Dashboard/components/DashboardSidebar/components/AgreementNavLink/index.spec.tsx +214 -0
- package/js/widgets/Dashboard/components/DashboardSidebar/components/AgreementNavLink/index.tsx +47 -0
- package/js/widgets/Dashboard/components/LearnerDashboardSidebar/index.tsx +1 -0
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.spec.tsx +21 -3
- package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.tsx +9 -0
- package/js/widgets/Dashboard/utils/learnerRoutes.tsx +30 -0
- package/js/widgets/Dashboard/utils/learnerRoutesPaths.tsx +12 -0
- package/js/widgets/Dashboard/utils/teacherDashboardPaths.tsx +12 -0
- package/js/widgets/Dashboard/utils/teacherRoutes.tsx +17 -0
- package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +8 -2
- package/package.json +4 -1
- package/scss/colors/_theme.scss +1 -1
- package/scss/components/_index.scss +1 -0
package/js/types/Joanie.ts
CHANGED
|
@@ -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,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
|
});
|