richie-education 2.24.0 → 2.25.0-b2.dev101

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 (492) hide show
  1. package/.eslintignore +2 -0
  2. package/.nvmrc +1 -1
  3. package/.prettierignore +2 -0
  4. package/.storybook/preview-body.html +1 -1
  5. package/.storybook/preview.tsx +5 -2
  6. package/cunningham.cjs +52 -43
  7. package/i18n/locales/ar-SA.json +500 -100
  8. package/i18n/locales/es-ES.json +500 -100
  9. package/i18n/locales/fa-IR.json +500 -100
  10. package/i18n/locales/fr-CA.json +564 -164
  11. package/i18n/locales/fr-FR.json +517 -117
  12. package/i18n/locales/ko-KR.json +500 -100
  13. package/i18n/locales/pt-PT.json +526 -126
  14. package/i18n/locales/ru-RU.json +500 -100
  15. package/i18n/locales/vi-VN.json +1734 -0
  16. package/jest/setup.ts +11 -1
  17. package/js/api/enrollment.ts +1 -1
  18. package/js/api/joanie.spec.ts +63 -2
  19. package/js/api/joanie.ts +218 -141
  20. package/js/api/lms/dummy.spec.ts +9 -1
  21. package/js/api/lms/dummy.ts +63 -10
  22. package/js/api/lms/joanie.spec.ts +49 -31
  23. package/js/api/lms/joanie.ts +53 -35
  24. package/js/api/lms/openedx-hawthorn.spec.ts +27 -11
  25. package/js/api/lms/openedx-hawthorn.ts +7 -6
  26. package/js/components/AddressesManagement/AddressForm/index.spec.tsx +157 -0
  27. package/js/components/AddressesManagement/AddressForm/index.stories.tsx +36 -0
  28. package/js/components/AddressesManagement/AddressForm/index.tsx +163 -0
  29. package/js/components/AddressesManagement/{validationSchema.ts → AddressForm/validationSchema.ts} +1 -23
  30. package/js/components/AddressesManagement/_styles.scss +1 -1
  31. package/js/components/AddressesManagement/index.spec.tsx +171 -202
  32. package/js/components/AddressesManagement/index.stories.tsx +29 -0
  33. package/js/components/AddressesManagement/index.tsx +11 -3
  34. package/js/components/Badge/index.spec.tsx +17 -0
  35. package/js/components/Badge/index.stories.tsx +22 -0
  36. package/js/components/Badge/index.tsx +18 -0
  37. package/js/components/Banner/index.tsx +6 -1
  38. package/js/components/ContractFrame/AbstractContractFrame.spec.tsx +332 -0
  39. package/js/components/ContractFrame/AbstractContractFrame.tsx +289 -0
  40. package/js/components/ContractFrame/LearnerContractFrame.spec.tsx +125 -0
  41. package/js/components/ContractFrame/LearnerContractFrame.tsx +42 -0
  42. package/js/components/ContractFrame/OrganizationContractFrame.spec.tsx +167 -0
  43. package/js/components/ContractFrame/OrganizationContractFrame.tsx +70 -0
  44. package/js/components/ContractFrame/_styles.scss +62 -0
  45. package/js/components/ContractFrame/iframe-manager.js +158 -0
  46. package/js/components/ContractFrame/index.ts +5 -0
  47. package/js/components/ContractStatus/index.spec.tsx +120 -0
  48. package/js/components/ContractStatus/index.tsx +67 -0
  49. package/js/components/CourseGlimpse/CourseGlimpseFooter.tsx +7 -7
  50. package/js/components/CourseGlimpse/index.tsx +5 -1
  51. package/js/components/CourseGlimpse/utils.ts +24 -16
  52. package/js/components/CourseGlimpseList/index.spec.tsx +1 -1
  53. package/js/components/CourseGlimpseList/index.tsx +1 -1
  54. package/js/components/CourseGlimpseList/utils.ts +3 -2
  55. package/js/components/DjangoCMSTemplate/index.spec.tsx +2 -2
  56. package/js/components/DownloadCertificateButton/index.tsx +58 -0
  57. package/js/components/DownloadContractButton/index.spec.tsx +155 -0
  58. package/js/components/DownloadContractButton/index.tsx +48 -0
  59. package/js/components/Form/CountrySelectField.tsx +28 -16
  60. package/js/components/Form/Input/index.spec.tsx +76 -0
  61. package/js/components/Form/Input/index.tsx +47 -0
  62. package/js/components/Form/Select/index.spec.tsx +99 -0
  63. package/js/components/Form/Select/index.tsx +43 -0
  64. package/js/components/{AddressesManagement → Form}/ValidationErrors.ts +10 -5
  65. package/js/components/Form/index.ts +5 -1
  66. package/js/components/Form/messages.ts +14 -0
  67. package/js/components/Form/test-utils.ts +19 -0
  68. package/js/components/Form/utils.spec.ts +72 -0
  69. package/js/components/Form/utils.ts +37 -0
  70. package/js/components/Icon/index.stories.tsx +2 -1
  71. package/js/components/Modal/_styles.scss +0 -8
  72. package/js/components/Modal/index.spec.tsx +0 -6
  73. package/js/components/Modal/index.tsx +23 -17
  74. package/js/components/PaymentButton/_styles.scss +26 -0
  75. package/js/{widgets/CourseProductItem → components/PaymentButton}/components/PaymentInterfaces/Dummy.tsx +1 -1
  76. package/js/{widgets/CourseProductItem → components/PaymentButton}/components/PaymentInterfaces/PayplugLightbox.tsx +30 -7
  77. package/js/{widgets/CourseProductItem → components/PaymentButton}/components/PaymentInterfaces/__mocks__/index.tsx +1 -1
  78. package/js/{widgets/CourseProductItem → components/PaymentButton}/components/PaymentInterfaces/index.spec.tsx +5 -3
  79. package/js/{widgets/CourseProductItem → components/PaymentButton}/components/PaymentInterfaces/index.tsx +7 -5
  80. package/js/components/PaymentButton/hooks/useTerms.tsx +74 -0
  81. package/js/components/PaymentButton/index.spec.tsx +1038 -0
  82. package/js/{widgets/CourseProductItem/components → components}/PaymentButton/index.tsx +94 -41
  83. package/js/components/PurchaseButton/index.spec.tsx +377 -0
  84. package/js/components/PurchaseButton/index.stories.tsx +15 -0
  85. package/js/{widgets/CourseProductItem/components → components}/PurchaseButton/index.tsx +72 -23
  86. package/js/components/PurchaseButton/styles.scss +7 -0
  87. package/js/components/RegisteredAddress/_styles.scss +1 -3
  88. package/js/components/RegisteredAddress/index.spec.tsx +1 -1
  89. package/js/components/RegisteredAddress/index.stories.tsx +40 -0
  90. package/js/components/RegisteredAddress/index.tsx +17 -19
  91. package/js/components/SaleTunnel/_styles.scss +11 -0
  92. package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/RegisteredCreditCard/index.tsx +4 -10
  93. package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/SaleTunnelStepPayment/_styles.scss +7 -0
  94. package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/SaleTunnelStepPayment/index.spec.tsx +85 -61
  95. package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/SaleTunnelStepPayment/index.tsx +55 -57
  96. package/js/components/SaleTunnel/components/SaleTunnelStepResume/_styles.scss +63 -0
  97. package/js/components/SaleTunnel/components/SaleTunnelStepResume/index.spec.tsx +80 -0
  98. package/js/components/SaleTunnel/components/SaleTunnelStepResume/index.tsx +88 -0
  99. package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/SaleTunnelStepValidation/CourseRunsList.tsx +6 -2
  100. package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/SaleTunnelStepValidation/_styles.scss +5 -0
  101. package/js/components/SaleTunnel/components/SaleTunnelStepValidation/index.spec.tsx +170 -0
  102. package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/SaleTunnelStepValidation/index.tsx +41 -10
  103. package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/StepBreadcrumb/index.spec.tsx +1 -1
  104. package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/StepBreadcrumb/index.tsx +1 -1
  105. package/js/components/SaleTunnel/context.tsx +44 -0
  106. package/js/{widgets/CourseProductItem/components → components}/SaleTunnel/index.spec.tsx +27 -22
  107. package/js/{widgets/CourseProductItem/components → components}/SaleTunnel/index.tsx +96 -24
  108. package/js/components/SignContractButton/index.omniscientOrders.spec.tsx +135 -0
  109. package/js/components/SignContractButton/index.spec.tsx +213 -0
  110. package/js/components/SignContractButton/index.tsx +97 -0
  111. package/js/components/SuccessIcon/_styles.scss +66 -0
  112. package/js/components/SuccessIcon/index.tsx +10 -0
  113. package/js/components/TeacherDashboardCourseList/_styles.scss +0 -1
  114. package/js/components/TeacherDashboardCourseList/index.spec.tsx +9 -9
  115. package/js/components/TeacherDashboardCourseList/index.tsx +25 -31
  116. package/js/contexts/JoanieApiContext/index.spec.tsx +1 -1
  117. package/js/contexts/SessionContext/BaseSessionProvider.tsx +12 -22
  118. package/js/contexts/SessionContext/JoanieSessionProvider.spec.tsx +14 -0
  119. package/js/contexts/SessionContext/JoanieSessionProvider.tsx +33 -34
  120. package/js/contexts/SessionContext/index.spec.tsx +6 -7
  121. package/js/hooks/useBreadcrumbsPlaceholders.tsx +1 -1
  122. package/js/hooks/useContractAbilities/index.spec.ts +27 -0
  123. package/js/hooks/useContractAbilities/index.ts +8 -0
  124. package/js/hooks/useContractArchive/index.download.spec.tsx +126 -0
  125. package/js/hooks/useContractArchive/index.spec.tsx +91 -0
  126. package/js/hooks/useContractArchive/index.ts +64 -0
  127. package/js/hooks/useContracts/index.tsx +68 -0
  128. package/js/hooks/useCourseProductRelation/index.ts +8 -5
  129. package/js/hooks/useCourseProductUnion/index.spec.tsx +14 -10
  130. package/js/hooks/useCourseProductUnion/index.ts +6 -2
  131. package/js/hooks/useCourseProducts.ts +45 -0
  132. package/js/hooks/useCourseSearchParams/computeNewFilterValue.ts +3 -3
  133. package/js/hooks/useCourses/index.spec.tsx +2 -2
  134. package/js/hooks/useCourses/index.ts +4 -4
  135. package/js/hooks/useCreditCards/index.spec.tsx +4 -4
  136. package/js/hooks/useDashboardAddressForm.tsx +85 -87
  137. package/js/hooks/useDownloadCertificate/index.spec.tsx +19 -6
  138. package/js/hooks/useDownloadCertificate/index.tsx +2 -20
  139. package/js/hooks/useEnrollments.ts +1 -1
  140. package/js/hooks/useJoanieUserAbilities/index.not.isJoanieEnabled.spec.tsx +17 -0
  141. package/js/hooks/useJoanieUserAbilities/index.spec.tsx +68 -0
  142. package/js/hooks/useJoanieUserAbilities/index.tsx +11 -0
  143. package/js/hooks/useJoanieUserProfile.tsx +34 -0
  144. package/js/hooks/useOrders.ts +69 -26
  145. package/js/hooks/useOrganizations/index.ts +1 -1
  146. package/js/hooks/useProductOrder/index.spec.tsx +113 -0
  147. package/js/hooks/useProductOrder/index.tsx +33 -0
  148. package/js/hooks/useQueryKeyInvalidateListener.tsx +16 -0
  149. package/js/hooks/useResources/index.spec.tsx +30 -29
  150. package/js/hooks/useResources/index.tsx +11 -4
  151. package/js/hooks/useResources/useResourcesOmniscient.ts +2 -2
  152. package/js/hooks/useResources/useResourcesRoot.ts +21 -17
  153. package/js/hooks/useTeacherPendingContractsCount/index.ts +34 -0
  154. package/js/hooks/useUnionResource/index.spec.tsx +5 -2
  155. package/js/hooks/useUnionResource/index.ts +20 -3
  156. package/js/hooks/useUnionResource/utils/fetchEntity.ts +5 -4
  157. package/js/index.tsx +2 -2
  158. package/js/pages/DashboardAddressesManagement/DashboardAddressBox.tsx +1 -1
  159. package/js/pages/DashboardAddressesManagement/DashboardCreateAddress.spec.tsx +79 -72
  160. package/js/pages/DashboardAddressesManagement/DashboardCreateAddress.tsx +4 -4
  161. package/js/pages/DashboardAddressesManagement/DashboardEditAddress.spec.tsx +32 -23
  162. package/js/pages/DashboardAddressesManagement/DashboardEditAddress.tsx +6 -6
  163. package/js/pages/DashboardAddressesManagement/DashboardEditAddressLoader.tsx +2 -2
  164. package/js/pages/DashboardAddressesManagement/index.spec.tsx +4 -3
  165. package/js/pages/DashboardAddressesManagement/index.tsx +5 -5
  166. package/js/pages/DashboardCertificates/index.spec.tsx +3 -2
  167. package/js/pages/DashboardCertificates/index.tsx +2 -1
  168. package/js/pages/DashboardContracts/_styles.scss +8 -0
  169. package/js/pages/DashboardContracts/index.spec.tsx +147 -0
  170. package/js/pages/DashboardContracts/index.tsx +76 -0
  171. package/js/pages/DashboardCourses/index.spec.tsx +81 -61
  172. package/js/pages/DashboardCourses/index.tsx +15 -12
  173. package/js/pages/DashboardCourses/useOrdersEnrollments.tsx +34 -8
  174. package/js/pages/DashboardCreditCardsManagement/DashboardCreditCardBox.tsx +1 -1
  175. package/js/pages/DashboardCreditCardsManagement/DashboardEditCreditCard.spec.tsx +7 -6
  176. package/js/pages/DashboardCreditCardsManagement/DashboardEditCreditCard.tsx +43 -45
  177. package/js/pages/DashboardCreditCardsManagement/DashboardEditCreditCardLoader.tsx +2 -2
  178. package/js/pages/DashboardCreditCardsManagement/index.spec.tsx +13 -12
  179. package/js/pages/DashboardCreditCardsManagement/index.tsx +3 -3
  180. package/js/pages/DashboardOrderLayout/_styles.scss +5 -0
  181. package/js/pages/DashboardOrderLayout/index.spec.tsx +8 -8
  182. package/js/pages/DashboardOrderLayout/index.tsx +11 -6
  183. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.spec.tsx +358 -0
  184. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardContracts/index.tsx +129 -0
  185. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardCourseContractsLayout/index.tsx +26 -0
  186. package/js/pages/TeacherDashboardContractsLayout/TeacherDashboardOrganizationContractsLayout/index.tsx +26 -0
  187. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.spec.tsx +136 -0
  188. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.timer.spec.tsx +144 -0
  189. package/js/pages/TeacherDashboardContractsLayout/components/BulkDownloadContractButton/index.tsx +73 -0
  190. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.spec.tsx +185 -0
  191. package/js/pages/TeacherDashboardContractsLayout/components/ContractActionsBar/index.tsx +47 -0
  192. package/js/pages/TeacherDashboardContractsLayout/components/ContractFiltersBar/index.spec.tsx +179 -0
  193. package/js/pages/TeacherDashboardContractsLayout/components/ContractFiltersBar/index.tsx +86 -0
  194. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.spec.tsx +109 -0
  195. package/js/pages/TeacherDashboardContractsLayout/components/SignOrganizationContractButton/index.tsx +60 -0
  196. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.spec.tsx +124 -0
  197. package/js/pages/TeacherDashboardContractsLayout/hooks/useCheckContractArchiveExists/index.tsx +73 -0
  198. package/js/pages/TeacherDashboardContractsLayout/hooks/useDefaultOrganizationId/index.spec.tsx +134 -0
  199. package/js/pages/TeacherDashboardContractsLayout/hooks/useDefaultOrganizationId/index.tsx +28 -0
  200. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.spec.ts +85 -0
  201. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/contractArchiveLocalStorage.ts +50 -0
  202. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.spec.tsx +266 -0
  203. package/js/pages/TeacherDashboardContractsLayout/hooks/useDownloadContractArchive/index.tsx +153 -0
  204. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.spec.tsx +100 -0
  205. package/js/pages/TeacherDashboardContractsLayout/hooks/useHasContractToDownload/index.tsx +27 -0
  206. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.spec.tsx +193 -0
  207. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractFilters/index.tsx +44 -0
  208. package/js/pages/TeacherDashboardContractsLayout/hooks/useTeacherContractsToSign.tsx +32 -0
  209. package/js/pages/TeacherDashboardContractsLayout/index.ts +2 -0
  210. package/js/pages/TeacherDashboardContractsLayout/styles.scss +15 -0
  211. package/js/pages/{TeacherCourseDashboardLoader → TeacherDashboardCourseLoader}/CourseRunList/CourseRunListCell/index.spec.tsx +1 -1
  212. package/js/pages/{TeacherCourseDashboardLoader → TeacherDashboardCourseLoader}/CourseRunList/_styles.scss +1 -2
  213. package/js/pages/{TeacherCourseDashboardLoader → TeacherDashboardCourseLoader}/CourseRunList/index.spec.tsx +2 -2
  214. package/js/pages/{TeacherCourseDashboardLoader → TeacherDashboardCourseLoader}/CourseRunList/utils.spec.tsx +1 -1
  215. package/js/pages/{TeacherCourseDashboardLoader → TeacherDashboardCourseLoader}/CourseRunList/utils.tsx +11 -10
  216. package/js/pages/{TeacherCourseDashboardLoader → TeacherDashboardCourseLoader}/index.tsx +9 -8
  217. package/js/pages/{TeacherCoursesDashboardLoader → TeacherDashboardCoursesLoader}/index.spec.tsx +17 -17
  218. package/js/pages/{TeacherCoursesDashboardLoader → TeacherDashboardCoursesLoader}/index.tsx +7 -7
  219. package/js/pages/{TeacherOrganizationCourseDashboardLoader → TeacherDashboardOrganizationCourseLoader}/index.tsx +5 -5
  220. package/js/pages/{TeacherTrainingDashboard/TeacherTrainingDashboardLoader.tsx → TeacherDashboardTraining/TeacherDashboardTrainingLoader.tsx} +10 -9
  221. package/js/pages/{TeacherTrainingDashboard → TeacherDashboardTraining}/index.spec.tsx +76 -6
  222. package/js/pages/{TeacherTrainingDashboard → TeacherDashboardTraining}/index.tsx +6 -6
  223. package/js/settings.dev.dist.ts +3 -0
  224. package/js/settings.ts +29 -1
  225. package/js/translations/ar-SA.json +1 -1
  226. package/js/translations/es-ES.json +1 -1
  227. package/js/translations/fa-IR.json +1 -1
  228. package/js/translations/fr-CA.json +1 -1
  229. package/js/translations/fr-FR.json +1 -1
  230. package/js/translations/ko-KR.json +1 -1
  231. package/js/translations/pt-PT.json +1 -1
  232. package/js/translations/ru-RU.json +1 -1
  233. package/js/translations/vi-VN.json +1 -0
  234. package/js/types/Joanie.ts +263 -80
  235. package/js/types/Suggestion.ts +2 -2
  236. package/js/types/User.ts +19 -1
  237. package/js/types/commonDataProps.ts +3 -0
  238. package/js/types/index.ts +1 -1
  239. package/js/utils/AbilitiesHelper/contractAbilities.spec.ts +35 -0
  240. package/js/utils/AbilitiesHelper/contractAbilities.ts +14 -0
  241. package/js/utils/AbilitiesHelper/index.ts +71 -0
  242. package/js/utils/AbilitiesHelper/joanieUserProfileAbilities.spec.ts +55 -0
  243. package/js/utils/AbilitiesHelper/joanieUserProfileAbilities.ts +16 -0
  244. package/js/utils/AbilitiesHelper/types.ts +36 -0
  245. package/js/utils/ContractHelper/index.spec.ts +73 -0
  246. package/js/utils/ContractHelper/index.ts +72 -0
  247. package/js/utils/CourseRuns/index.spec.tsx +20 -1
  248. package/js/utils/CourseRuns/index.ts +14 -2
  249. package/js/utils/CoursesHelper/index.spec.ts +45 -55
  250. package/js/utils/CoursesHelper/index.ts +6 -7
  251. package/js/utils/CreditCardHelper/index.spec.tsx +26 -22
  252. package/js/utils/CreditCardHelper/index.tsx +19 -6
  253. package/js/utils/ObjectHelper/index.spec.ts +18 -10
  254. package/js/utils/ObjectHelper/index.ts +9 -0
  255. package/js/utils/OrderHelper/index.ts +32 -0
  256. package/js/utils/ProductHelper/index.ts +5 -1
  257. package/js/utils/StringHelper/index.spec.tsx +11 -0
  258. package/js/utils/StringHelper/index.ts +8 -0
  259. package/js/utils/UserHelper/index.spec.ts +18 -0
  260. package/js/utils/UserHelper/index.ts +8 -0
  261. package/js/utils/download.ts +43 -0
  262. package/js/utils/errors/HttpError.ts +10 -0
  263. package/js/utils/indirection/window.ts +1 -1
  264. package/js/utils/react-query/createQueryClient.ts +12 -21
  265. package/js/utils/react-query/useLocalizedQueryKey.ts +1 -1
  266. package/js/utils/react-query/useSessionMutation/index.spec.tsx +8 -8
  267. package/js/utils/react-query/useSessionMutation/index.ts +6 -11
  268. package/js/utils/react-query/useSessionQuery/index.spec.tsx +36 -8
  269. package/js/utils/react-query/useSessionQuery/index.ts +14 -21
  270. package/js/utils/search/getSuggestionsSection/index.spec.ts +4 -3
  271. package/js/utils/search/getSuggestionsSection/index.ts +4 -1
  272. package/js/utils/search/index.tsx +8 -3
  273. package/js/utils/test/createTestQueryClient.ts +7 -7
  274. package/js/utils/test/expectBanner.ts +16 -3
  275. package/js/utils/test/factories/factories.ts +4 -4
  276. package/js/utils/test/factories/joanie.spec.ts +7 -0
  277. package/js/utils/test/factories/joanie.ts +214 -63
  278. package/js/utils/test/factories/reactQuery.ts +1 -1
  279. package/js/utils/test/factories/richie.ts +4 -2
  280. package/js/utils/test/mockCourseProductWithOrder.ts +28 -0
  281. package/js/utils/test/mockPaginatedResponse.ts +1 -1
  282. package/js/utils/test/render.tsx +72 -0
  283. package/js/utils/test/wrappers/IntlWrapper.tsx +23 -0
  284. package/js/utils/test/wrappers/JoanieAppWrapper.tsx +42 -0
  285. package/js/utils/test/wrappers/PresentationalAppWrapper.tsx +18 -0
  286. package/js/utils/test/wrappers/ReactQueryWrapper.tsx +16 -0
  287. package/js/utils/test/wrappers/RouterWrapper.tsx +29 -0
  288. package/js/utils/test/wrappers/types.ts +26 -0
  289. package/js/widgets/Dashboard/components/DashboardAvatar/_styles.scss +17 -5
  290. package/js/widgets/Dashboard/components/DashboardAvatar/index.spec.tsx +9 -2
  291. package/js/widgets/Dashboard/components/DashboardAvatar/index.tsx +16 -5
  292. package/js/widgets/Dashboard/components/DashboardBox/index.stories.tsx +1 -1
  293. package/js/widgets/Dashboard/components/DashboardBreadcrumbs/_styles.scss +1 -0
  294. package/js/widgets/Dashboard/components/DashboardBreadcrumbs/index.tsx +7 -4
  295. package/js/widgets/Dashboard/components/DashboardCard/index.spec.tsx +1 -1
  296. package/js/widgets/Dashboard/components/DashboardCard/index.stories.tsx +3 -3
  297. package/js/widgets/Dashboard/components/DashboardCard/index.tsx +2 -2
  298. package/js/widgets/Dashboard/components/DashboardItem/Certificate/index.spec.tsx +49 -9
  299. package/js/widgets/Dashboard/components/DashboardItem/Certificate/index.tsx +27 -73
  300. package/js/widgets/Dashboard/components/DashboardItem/CertificateStatus/index.spec.tsx +65 -0
  301. package/js/widgets/Dashboard/components/DashboardItem/CertificateStatus/index.tsx +59 -0
  302. package/js/widgets/Dashboard/components/DashboardItem/Contract/_styles.scss +29 -0
  303. package/js/widgets/Dashboard/components/DashboardItem/Contract/index.spec.tsx +197 -0
  304. package/js/widgets/Dashboard/components/DashboardItem/Contract/index.stories.tsx +34 -0
  305. package/js/widgets/Dashboard/components/DashboardItem/Contract/index.tsx +53 -0
  306. package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/hooks/useCourseRunPeriodMessage.ts +76 -0
  307. package/js/widgets/Dashboard/components/DashboardItem/CourseEnrolling/index.spec.tsx +158 -0
  308. package/js/widgets/Dashboard/components/DashboardItem/{DashboardItemCourseEnrolling.stories.tsx → CourseEnrolling/index.stories.tsx} +6 -6
  309. package/js/widgets/Dashboard/components/DashboardItem/{DashboardItemCourseEnrolling.tsx → CourseEnrolling/index.tsx} +141 -84
  310. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.spec.tsx +40 -37
  311. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/DashboardItemEnrollment.tsx +29 -11
  312. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.spec.tsx +248 -0
  313. package/js/widgets/Dashboard/components/DashboardItem/Enrollment/ProductCertificateFooter/index.tsx +89 -0
  314. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.spec.tsx +188 -117
  315. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrder.tsx +124 -78
  316. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.spec.tsx +299 -0
  317. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderContract.useUnionResource.cache.spec.tsx +286 -0
  318. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderReadonly.stories.tsx +9 -5
  319. package/js/widgets/Dashboard/components/DashboardItem/Order/DashboardItemOrderWritable.stories.tsx +10 -6
  320. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.spec.tsx +121 -0
  321. package/js/widgets/Dashboard/components/DashboardItem/Order/OrderStateMessage/index.tsx +98 -0
  322. package/js/widgets/Dashboard/components/DashboardItem/Order/_styles.scss +43 -0
  323. package/js/widgets/Dashboard/components/DashboardItem/_styles.scss +34 -37
  324. package/js/widgets/Dashboard/components/DashboardItem/index.spec.tsx +74 -4
  325. package/js/widgets/Dashboard/components/DashboardItem/index.stories.tsx +18 -0
  326. package/js/widgets/Dashboard/components/DashboardItem/index.tsx +91 -26
  327. package/js/widgets/Dashboard/components/DashboardItem/stories.mock.ts +4 -8
  328. package/js/widgets/Dashboard/components/DashboardLayout/_styles.scss +14 -5
  329. package/js/widgets/Dashboard/components/DashboardLayout/index.tsx +10 -3
  330. package/js/widgets/Dashboard/components/DashboardListAvatar/_styles.scss +8 -0
  331. package/js/widgets/Dashboard/components/DashboardListAvatar/index.tsx +11 -0
  332. package/js/widgets/Dashboard/components/DashboardOrderLoader/_styles.scss +5 -0
  333. package/js/widgets/Dashboard/components/DashboardOrderLoader/index.tsx +50 -14
  334. package/js/widgets/Dashboard/components/DashboardSidebar/_styles.scss +37 -24
  335. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.spec.tsx +244 -0
  336. package/js/widgets/Dashboard/components/DashboardSidebar/components/ContractNavLink/index.tsx +49 -0
  337. package/js/widgets/Dashboard/components/DashboardSidebar/components/MenuNavLink/index.spec.tsx +40 -0
  338. package/js/widgets/Dashboard/components/DashboardSidebar/components/MenuNavLink/index.tsx +28 -0
  339. package/js/widgets/Dashboard/components/DashboardSidebar/components/NavigationSelect.tsx +58 -0
  340. package/js/widgets/Dashboard/components/DashboardSidebar/index.stories.tsx +11 -1
  341. package/js/widgets/Dashboard/components/DashboardSidebar/index.tsx +18 -69
  342. package/js/widgets/Dashboard/components/DashboardSidebar/utils.ts +6 -0
  343. package/js/widgets/Dashboard/components/FilterOrganization/index.tsx +58 -0
  344. package/js/widgets/Dashboard/components/FiltersBar/index.tsx +9 -0
  345. package/js/widgets/Dashboard/components/LearnerDashboardSidebar/index.tsx +4 -2
  346. package/js/widgets/Dashboard/components/NavigateWithParams/index.spec.tsx +31 -40
  347. package/js/widgets/Dashboard/components/RouterButton/index.tsx +2 -1
  348. package/js/widgets/Dashboard/components/Signature/DummyContractPlaceholder.tsx +25 -0
  349. package/js/widgets/Dashboard/components/Signature/SignatureDummy.tsx +58 -0
  350. package/js/widgets/Dashboard/components/Signature/SignatureLexPersona.tsx +72 -0
  351. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/index.spec.tsx +215 -0
  352. package/js/widgets/Dashboard/components/{TeacherCourseDashboardSidebar → TeacherDashboardCourseSidebar}/index.tsx +75 -31
  353. package/js/widgets/Dashboard/components/TeacherDashboardCourseSidebar/utils.ts +23 -0
  354. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.spec.tsx +154 -0
  355. package/js/widgets/Dashboard/components/TeacherDashboardOrganizationSidebar/index.stories.tsx +42 -0
  356. package/js/widgets/Dashboard/components/{TeacherOrganizationDashboardSidebar → TeacherDashboardOrganizationSidebar}/index.tsx +40 -29
  357. package/js/widgets/Dashboard/components/{TeacherProfileDashboardSidebar → TeacherDashboardProfileSidebar}/components/OrganizationLinks/_styles.scss +27 -10
  358. package/js/widgets/Dashboard/components/TeacherDashboardProfileSidebar/components/OrganizationLinks/index.spec.tsx +49 -0
  359. package/js/widgets/Dashboard/components/{TeacherProfileDashboardSidebar → TeacherDashboardProfileSidebar}/components/OrganizationLinks/index.tsx +18 -7
  360. package/js/widgets/Dashboard/components/{TeacherProfileDashboardSidebar → TeacherDashboardProfileSidebar}/index.spec.tsx +13 -39
  361. package/js/widgets/Dashboard/components/{TeacherProfileDashboardSidebar → TeacherDashboardProfileSidebar}/index.stories.tsx +5 -5
  362. package/js/widgets/Dashboard/components/{TeacherProfileDashboardSidebar → TeacherDashboardProfileSidebar}/index.tsx +4 -3
  363. package/js/widgets/Dashboard/hooks/useEnroll/index.ts +8 -8
  364. package/js/widgets/Dashboard/index.spec.tsx +22 -12
  365. package/js/widgets/Dashboard/utils/learnerRouteMessages.tsx +12 -1
  366. package/js/widgets/Dashboard/utils/learnerRoutes.tsx +6 -0
  367. package/js/widgets/Dashboard/utils/teacherRouteMessages.tsx +60 -4
  368. package/js/widgets/Dashboard/utils/teacherRoutes.tsx +87 -13
  369. package/js/widgets/LtiConsumer/index.spec.tsx +44 -33
  370. package/js/widgets/LtiConsumer/index.tsx +11 -15
  371. package/js/widgets/Search/components/SearchFilterGroup/index.spec.tsx +0 -5
  372. package/js/widgets/Search/components/SearchFilterGroupModal/_styles.scss +0 -9
  373. package/js/widgets/Search/components/SearchFilterGroupModal/index.spec.tsx +0 -5
  374. package/js/widgets/Search/components/SearchFilterGroupModal/index.tsx +86 -60
  375. package/js/widgets/Search/components/SearchFilterValueParent/index.stories.tsx +51 -0
  376. package/js/widgets/Search/components/SearchFilterValueParent/index.tsx +7 -7
  377. package/js/widgets/Search/components/SearchFiltersPane/_styles.scss +2 -16
  378. package/js/widgets/Search/components/SearchFiltersPane/index.tsx +9 -6
  379. package/js/widgets/Search/hooks/useCourseSearch/index.ts +13 -7
  380. package/js/widgets/Search/index.spec.tsx +3 -2
  381. package/js/widgets/Search/utils/getResourceList/index.spec.ts +12 -5
  382. package/js/widgets/Search/utils/getResourceList/index.ts +6 -2
  383. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/CourseProductItemFooter/index.tsx +74 -0
  384. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/_styles.scss +11 -14
  385. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCertificateItem/_styles.scss +1 -1
  386. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCertificateItem/index.spec.tsx +13 -2
  387. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseProductCertificateItem/index.stories.tsx +33 -0
  388. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCertificateItem/index.tsx +5 -8
  389. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCourseRuns/CourseRunList.tsx +1 -1
  390. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCourseRuns/EnrollableCourseRunList.tsx +16 -8
  391. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCourseRuns/EnrolledCourseRun.tsx +5 -3
  392. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCourseRuns/_styles.scss +1 -0
  393. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCourseRuns/index.spec.tsx +100 -35
  394. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseRunItem/index.spec.tsx +7 -4
  395. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/CourseRunItem/index.stories.tsx +36 -0
  396. package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseRunItem/index.tsx +5 -3
  397. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/components/ProductSignatureHeader/index.tsx +40 -0
  398. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.spec.tsx +898 -0
  399. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.stories.tsx +83 -0
  400. package/js/widgets/SyllabusCourseRunsList/components/CourseProductItem/index.tsx +253 -0
  401. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/CourseRunUnenrollmentButton/index.tsx +3 -2
  402. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/_styles.scss +0 -25
  403. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.joanie.spec.tsx +73 -30
  404. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.openedx.spec.tsx +10 -5
  405. package/js/widgets/SyllabusCourseRunsList/components/CourseRunEnrollment/index.tsx +54 -26
  406. package/js/widgets/SyllabusCourseRunsList/components/CourseRunItemWithEnrollment/index.tsx +1 -0
  407. package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/_styles.scss +0 -3
  408. package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/hooks/useCourseWish/index.spec.tsx +12 -9
  409. package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.login.spec.tsx +14 -12
  410. package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.logout.spec.tsx +4 -6
  411. package/js/widgets/SyllabusCourseRunsList/components/CourseWishButton/index.tsx +5 -5
  412. package/js/widgets/SyllabusCourseRunsList/components/SyllabusAsideList/index.tsx +2 -2
  413. package/js/widgets/SyllabusCourseRunsList/components/SyllabusCourseRun/index.tsx +19 -13
  414. package/js/widgets/SyllabusCourseRunsList/components/SyllabusSimpleCourseRunsList/index.tsx +1 -0
  415. package/js/widgets/SyllabusCourseRunsList/hooks/useCourseEnrollment/index.spec.tsx +2 -1
  416. package/js/widgets/SyllabusCourseRunsList/hooks/useCourseEnrollment/index.ts +14 -19
  417. package/js/widgets/SyllabusCourseRunsList/index.spec.tsx +54 -14
  418. package/js/widgets/SyllabusCourseRunsList/index.tsx +5 -8
  419. package/js/widgets/UserLogin/components/UserMenu/DesktopUserMenu.tsx +2 -1
  420. package/js/widgets/UserLogin/components/UserMenu/MobileUserMenu.tsx +2 -1
  421. package/js/widgets/UserLogin/components/UserMenu/index.tsx +6 -5
  422. package/js/widgets/UserLogin/index.not.isJoanieEnabled.spec.tsx +120 -0
  423. package/js/widgets/UserLogin/index.spec.tsx +108 -43
  424. package/js/widgets/UserLogin/index.stories.tsx +29 -0
  425. package/js/widgets/UserLogin/index.tsx +33 -15
  426. package/js/widgets/index.tsx +3 -6
  427. package/mocks/browser.ts +1 -1
  428. package/mocks/handlers/contracts.ts +16 -0
  429. package/mocks/handlers.ts +3 -1
  430. package/package.json +82 -78
  431. package/scss/_main.scss +2 -0
  432. package/scss/colors/_palette.scss +2 -2
  433. package/scss/colors/_theme.scss +6 -16
  434. package/scss/components/_content.scss +1 -1
  435. package/scss/components/_index.scss +24 -14
  436. package/scss/generic/_type.scss +1 -1
  437. package/scss/objects/_characteristics.scss +7 -14
  438. package/scss/objects/_course_glimpses.scss +3 -7
  439. package/scss/objects/_dashboard.scss +28 -0
  440. package/scss/objects/_form.scss +14 -355
  441. package/scss/objects/_index.scss +1 -0
  442. package/scss/objects/_list.scss +8 -0
  443. package/scss/objects/_organization_glimpses.scss +2 -8
  444. package/scss/objects/_selector.scss +1 -0
  445. package/scss/trumps/_bootstrap.scss +4 -0
  446. package/scss/vendors/css/cunningham-tokens.css +89 -25
  447. package/scss/vendors/cunningham-tokens.scss +208 -128
  448. package/js/components/AddressesManagement/AddressForm.spec.tsx +0 -206
  449. package/js/components/AddressesManagement/AddressForm.tsx +0 -169
  450. package/js/components/Button/index.spec.tsx +0 -36
  451. package/js/components/Button/index.stories.tsx +0 -26
  452. package/js/components/Button/index.tsx +0 -38
  453. package/js/components/Form/CheckboxField.stories.tsx +0 -12
  454. package/js/components/Form/Field.stories.config.tsx +0 -24
  455. package/js/components/Form/Inputs.tsx +0 -295
  456. package/js/components/Form/RadioField.stories.tsx +0 -18
  457. package/js/components/Form/SelectField.stories.tsx +0 -27
  458. package/js/components/Form/TextAreaField.stories.tsx +0 -12
  459. package/js/components/Form/TextField.stories.tsx +0 -12
  460. package/js/components/Form/index.spec.tsx +0 -297
  461. package/js/hooks/useProduct.ts +0 -28
  462. package/js/utils/test/mockProductWithOrder.ts +0 -17
  463. package/js/widgets/CourseProductItem/components/PaymentButton/_styles.scss +0 -12
  464. package/js/widgets/CourseProductItem/components/PaymentButton/index.spec.tsx +0 -473
  465. package/js/widgets/CourseProductItem/components/PurchaseButton/index.spec.tsx +0 -259
  466. package/js/widgets/CourseProductItem/components/SaleTunnel/_styles.scss +0 -41
  467. package/js/widgets/CourseProductItem/components/SaleTunnelStepResume/_styles.scss +0 -130
  468. package/js/widgets/CourseProductItem/components/SaleTunnelStepResume/index.spec.tsx +0 -29
  469. package/js/widgets/CourseProductItem/components/SaleTunnelStepResume/index.tsx +0 -59
  470. package/js/widgets/CourseProductItem/components/SaleTunnelStepValidation/index.spec.tsx +0 -71
  471. package/js/widgets/CourseProductItem/contexts/CourseProductContext/index.spec.tsx +0 -35
  472. package/js/widgets/CourseProductItem/contexts/CourseProductContext/index.tsx +0 -45
  473. package/js/widgets/CourseProductItem/index.spec.tsx +0 -486
  474. package/js/widgets/CourseProductItem/index.tsx +0 -205
  475. package/js/widgets/Dashboard/components/DashboardItem/DashboardItemCourseEnrolling.spec.tsx +0 -64
  476. package/js/widgets/Dashboard/components/TeacherCourseDashboardSidebar/index.spec.tsx +0 -105
  477. package/js/widgets/Dashboard/components/TeacherOrganizationDashboardSidebar/index.stories.tsx +0 -28
  478. /package/js/components/AddressesManagement/{validationSchema.spec.ts → AddressForm/validationSchema.spec.ts} +0 -0
  479. /package/js/{widgets/CourseProductItem/components → components}/EnrollmentDate/index.tsx +0 -0
  480. /package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/RegisteredCreditCard/_styles.scss +0 -0
  481. /package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/RegisteredCreditCard/index.spec.tsx +0 -0
  482. /package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/SaleTunnelStepValidation/TargetCourseDetail.tsx +0 -0
  483. /package/js/{widgets/CourseProductItem → components/SaleTunnel}/components/StepBreadcrumb/_styles.scss +0 -0
  484. /package/js/{widgets/CourseProductItem/hooks → hooks}/useStepManager/index.spec.ts +0 -0
  485. /package/js/{widgets/CourseProductItem/hooks → hooks}/useStepManager/index.ts +0 -0
  486. /package/js/pages/{TeacherCourseDashboardLoader → TeacherDashboardCourseLoader}/CourseRunList/CourseRunListCell/index.tsx +0 -0
  487. /package/js/pages/{TeacherCourseDashboardLoader → TeacherDashboardCourseLoader}/CourseRunList/index.tsx +0 -0
  488. /package/js/pages/{TeacherTrainingDashboard → TeacherDashboardTraining}/_styles.scss +0 -0
  489. /package/js/widgets/Dashboard/components/{TeacherCourseDashboardSidebar → TeacherDashboardCourseSidebar}/_styles.scss +0 -0
  490. /package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCourseRuns/CourseRunSection.tsx +0 -0
  491. /package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/components/CourseProductCourseRuns/index.tsx +0 -0
  492. /package/js/widgets/{CourseProductItem → SyllabusCourseRunsList/components/CourseProductItem}/types/payments/payplug.d.ts +0 -0
@@ -0,0 +1,18 @@
1
+ import classNames from 'classnames';
2
+ import { PropsWithChildren } from 'react/ts5.0';
3
+
4
+ type BadgeProps = PropsWithChildren<{
5
+ color?: 'primary' | 'secondary' | 'tertiary';
6
+ }>;
7
+ const Badge = ({ children, color }: BadgeProps) => (
8
+ <div
9
+ data-testid="badge"
10
+ className={classNames('category-badge', {
11
+ [`category-badge--${color}`]: Boolean(color),
12
+ })}
13
+ >
14
+ <span className="category-badge__title">{children}</span>
15
+ </div>
16
+ );
17
+
18
+ export default Badge;
@@ -1,3 +1,4 @@
1
+ import { uniqueId } from 'lodash-es';
1
2
  import { useMemo } from 'react';
2
3
 
3
4
  export enum BannerType {
@@ -13,6 +14,10 @@ interface BannerProps {
13
14
  rounded?: boolean;
14
15
  }
15
16
 
17
+ export const getBannerTestId = (message: string, type: BannerType) => {
18
+ return `banner-${type}-${uniqueId(message)}`;
19
+ };
20
+
16
21
  const Banner = ({ message, rounded, type = BannerType.INFO }: BannerProps) => {
17
22
  const className = useMemo(
18
23
  (): string => ['banner', `banner--${type}`, ...(rounded ? ['banner--rounded'] : [])].join(' '),
@@ -20,7 +25,7 @@ const Banner = ({ message, rounded, type = BannerType.INFO }: BannerProps) => {
20
25
  );
21
26
 
22
27
  return (
23
- <div className={className}>
28
+ <div className={className} data-testid={getBannerTestId(message, type)}>
24
29
  <p className="banner__message">{message}</p>
25
30
  </div>
26
31
  );
@@ -0,0 +1,332 @@
1
+ import { QueryClientProvider } from '@tanstack/react-query';
2
+ import { IntlProvider } from 'react-intl';
3
+ import fetchMock from 'fetch-mock';
4
+ import { render, screen, waitFor } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import { PropsWithChildren } from 'react';
7
+ import { act } from 'react-dom/test-utils';
8
+ import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
9
+ import { createTestQueryClient } from 'utils/test/createTestQueryClient';
10
+ import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
11
+ import { Deferred } from 'utils/test/deferred';
12
+ import { expectBannerError } from 'utils/test/expectBanner';
13
+ import { HttpError } from 'utils/errors/HttpError';
14
+ import { CONTRACT_SETTINGS } from 'settings';
15
+ import AbstractContractFrame from './AbstractContractFrame';
16
+
17
+ jest.mock('utils/context', () => ({
18
+ __esModule: true,
19
+ default: mockRichieContextFactory({
20
+ authentication: { backend: 'fonzie', endpoint: 'https://demo.endpoint' },
21
+ joanie_backend: { endpoint: 'https://joanie.endpoint' },
22
+ }).one(),
23
+ }));
24
+
25
+ jest.mock('settings', () => ({
26
+ ...jest.requireActual('settings'),
27
+ CONTRACT_SETTINGS: {
28
+ ...jest.requireActual('settings').CONTRACT_SETTINGS,
29
+ pollInterval: 10,
30
+ dummySignatureSignTimeout: 10,
31
+ },
32
+ }));
33
+
34
+ describe('<AbstractContractFrame />', () => {
35
+ const Wrapper = ({ children }: PropsWithChildren) => {
36
+ return (
37
+ <QueryClientProvider client={createTestQueryClient({ user: true })}>
38
+ <IntlProvider locale="en">
39
+ <JoanieSessionProvider>{children}</JoanieSessionProvider>
40
+ </IntlProvider>
41
+ </QueryClientProvider>
42
+ );
43
+ };
44
+
45
+ beforeEach(() => {
46
+ // SessionProvider api calls
47
+ fetchMock.get('https://joanie.endpoint/api/v1.0/addresses/', []);
48
+ fetchMock.get('https://joanie.endpoint/api/v1.0/credit-cards/', []);
49
+ fetchMock.get('https://joanie.endpoint/api/v1.0/orders/', []);
50
+ });
51
+
52
+ afterEach(() => {
53
+ jest.restoreAllMocks();
54
+ fetchMock.restore();
55
+ });
56
+
57
+ it('should render an ContractFrame and close it', async () => {
58
+ const mockOnClose = jest.fn();
59
+ const mockGetInvitationLink = jest.fn(
60
+ async () => 'https://dummysignaturebackend.fr/contract/1/sign',
61
+ );
62
+ const mockCheckSignature = jest.fn(async () => ({ isSigned: true }));
63
+
64
+ await act(async () => {
65
+ render(
66
+ <Wrapper>
67
+ <AbstractContractFrame
68
+ getInvitationLink={mockGetInvitationLink}
69
+ checkSignature={mockCheckSignature}
70
+ isOpen={true}
71
+ onDone={jest.fn()}
72
+ onClose={mockOnClose}
73
+ />
74
+ </Wrapper>,
75
+ );
76
+ });
77
+
78
+ expect(mockGetInvitationLink).toHaveBeenCalledTimes(1);
79
+ expect(await screen.findByTestId('dashboard-contract-frame')).toBeInTheDocument();
80
+
81
+ const user = userEvent.setup();
82
+ await user.click(screen.getByRole('button', { name: 'Close dialog' }));
83
+ expect(mockOnClose).toHaveBeenCalled();
84
+ });
85
+
86
+ it('should not render the modal when `isOpen` is false', async () => {
87
+ const mockGetInvitationLink = jest.fn(
88
+ async () => 'https://dummysignaturebackend.fr/contract/1/sign',
89
+ );
90
+ const mockCheckSignature = jest.fn(async () => ({ isSigned: true }));
91
+
92
+ await act(async () => {
93
+ render(
94
+ <Wrapper>
95
+ <AbstractContractFrame
96
+ getInvitationLink={mockGetInvitationLink}
97
+ checkSignature={mockCheckSignature}
98
+ isOpen={false}
99
+ onDone={jest.fn()}
100
+ onClose={jest.fn()}
101
+ />
102
+ </Wrapper>,
103
+ );
104
+ });
105
+
106
+ expect(mockGetInvitationLink).not.toHaveBeenCalled();
107
+ expect(screen.queryByTestId('dashboard-contract-frame')).not.toBeInTheDocument();
108
+ });
109
+
110
+ it('should render the modal and allow user to complete signature process', async () => {
111
+ const user = userEvent.setup();
112
+ const mockGetInvitationLink = jest.fn(
113
+ async () => 'https://dummysignaturebackend.fr/contract/1/sign',
114
+ );
115
+ const checkSignatureDeferred = new Deferred<{ isSigned: boolean }>();
116
+ const mockCheckSignature = jest.fn(async () => checkSignatureDeferred.promise);
117
+ const mockOnDone = jest.fn();
118
+ const mockOnClose = jest.fn();
119
+
120
+ await act(async () => {
121
+ render(
122
+ <Wrapper>
123
+ <AbstractContractFrame
124
+ getInvitationLink={mockGetInvitationLink}
125
+ checkSignature={mockCheckSignature}
126
+ isOpen
127
+ onDone={mockOnDone}
128
+ onClose={mockOnClose}
129
+ />
130
+ </Wrapper>,
131
+ );
132
+ });
133
+
134
+ expect(mockGetInvitationLink).toHaveBeenCalledTimes(1);
135
+ expect(await screen.findByTestId('dashboard-contract-frame')).toBeInTheDocument();
136
+
137
+ // Dummy signature interface should have been rendered
138
+ let button = screen.getByRole('button', { name: 'Sign' });
139
+ await user.click(button);
140
+
141
+ // The dummy interface should be loading
142
+ screen.getByRole('heading', { name: 'Signing the contract ...' });
143
+
144
+ // Then the signature check polling should be started
145
+ await waitFor(() => {
146
+ expect(screen.getByRole('heading', { name: 'Verifying signature ...' })).toBeInTheDocument();
147
+ });
148
+ expect(
149
+ screen.getByText(
150
+ 'We are waiting for the signature to be validated from our signature platform. It can take up to few minutes. Do not close this page.',
151
+ ),
152
+ ).toBeInTheDocument();
153
+ expect(screen.getByRole('status')).toBeInTheDocument();
154
+
155
+ checkSignatureDeferred.resolve({ isSigned: true });
156
+ await waitFor(() => {
157
+ expect(mockCheckSignature).toHaveBeenCalledTimes(1);
158
+ });
159
+
160
+ // As the first call confirm the document has been signed,
161
+ // the ContractFrame should go to the finish step and the onDone callback should
162
+ // have been called
163
+ expect(mockOnDone).toHaveBeenCalledTimes(1);
164
+ await screen.findByRole('heading', { name: 'Congratulations!' });
165
+ button = screen.getByRole('button', { name: 'Next' });
166
+ await user.click(button);
167
+
168
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
169
+ });
170
+
171
+ it('retrieves invitation link but fails during signature checking', async () => {
172
+ const user = userEvent.setup();
173
+ const mockGetInvitationLink = jest.fn(
174
+ async () => 'https://dummysignaturebackend.fr/contract/1/sign',
175
+ );
176
+ const checkSignatureDeferred = new Deferred<{ isSigned: boolean }>();
177
+ const mockCheckSignature = jest.fn(async () => checkSignatureDeferred.promise);
178
+ const mockOnDone = jest.fn();
179
+ const mockOnClose = jest.fn();
180
+
181
+ await act(async () => {
182
+ render(
183
+ <Wrapper>
184
+ <AbstractContractFrame
185
+ getInvitationLink={mockGetInvitationLink}
186
+ checkSignature={mockCheckSignature}
187
+ isOpen={true}
188
+ onDone={mockOnDone}
189
+ onClose={mockOnClose}
190
+ />
191
+ </Wrapper>,
192
+ );
193
+ });
194
+
195
+ expect(mockGetInvitationLink).toHaveBeenCalledTimes(1);
196
+ expect(await screen.findByTestId('dashboard-contract-frame')).toBeInTheDocument();
197
+
198
+ // Dummy signature interface should have been rendered
199
+ let button = screen.getByRole('button', { name: 'Sign' });
200
+ await user.click(button);
201
+
202
+ // The dummy interface should be loading
203
+ screen.getByRole('heading', { name: 'Signing the contract ...' });
204
+
205
+ // Then the signature check polling should be started
206
+ await waitFor(() => {
207
+ expect(screen.getByRole('heading', { name: 'Verifying signature ...' })).toBeInTheDocument();
208
+ });
209
+ expect(
210
+ screen.getByText(
211
+ 'We are waiting for the signature to be validated from our signature platform. It can take up to few minutes. Do not close this page.',
212
+ ),
213
+ ).toBeInTheDocument();
214
+ expect(screen.getByRole('status')).toBeInTheDocument();
215
+
216
+ checkSignatureDeferred.reject(new HttpError(500, 'Interval server error'));
217
+ await waitFor(() => {
218
+ expect(mockCheckSignature).toHaveBeenCalledTimes(1);
219
+ });
220
+
221
+ // As the first call confirm the document has been signed,
222
+ // the ContractFrame should go to the finish step and the onDone callback should
223
+ // have been called
224
+ await expectBannerError('An error happened while verifying signature. Please come back later.');
225
+ expect(mockOnDone).not.toHaveBeenCalled();
226
+ button = screen.getByRole('button', { name: 'Close dialog' });
227
+ await user.click(button);
228
+
229
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
230
+ });
231
+
232
+ it('retrieves invitation link but exceeds polling max attemps', async () => {
233
+ const user = userEvent.setup();
234
+ const mockGetInvitationLink = jest.fn(
235
+ async () => 'https://dummysignaturebackend.fr/contract/1/sign',
236
+ );
237
+ const checkSignatureDeferred = new Deferred<{ isSigned: boolean }>();
238
+ const mockCheckSignature = jest.fn(async () => checkSignatureDeferred.promise);
239
+ const mockOnDone = jest.fn();
240
+ const mockOnClose = jest.fn();
241
+
242
+ await act(async () => {
243
+ render(
244
+ <Wrapper>
245
+ <AbstractContractFrame
246
+ getInvitationLink={mockGetInvitationLink}
247
+ checkSignature={mockCheckSignature}
248
+ isOpen={true}
249
+ onDone={mockOnDone}
250
+ onClose={mockOnClose}
251
+ />
252
+ </Wrapper>,
253
+ );
254
+ });
255
+
256
+ expect(mockGetInvitationLink).toHaveBeenCalledTimes(1);
257
+ expect(await screen.findByTestId('dashboard-contract-frame')).toBeInTheDocument();
258
+
259
+ // Dummy signature interface should have been rendered
260
+ let button = screen.getByRole('button', { name: 'Sign' });
261
+ await user.click(button);
262
+
263
+ // The dummy interface should be loading
264
+ screen.getByRole('heading', { name: 'Signing the contract ...' });
265
+
266
+ // Then the signature check polling should be started
267
+ await waitFor(() => {
268
+ expect(screen.getByRole('heading', { name: 'Verifying signature ...' })).toBeInTheDocument();
269
+ });
270
+ expect(
271
+ screen.getByText(
272
+ 'We are waiting for the signature to be validated from our signature platform. It can take up to few minutes. Do not close this page.',
273
+ ),
274
+ ).toBeInTheDocument();
275
+ expect(screen.getByRole('status')).toBeInTheDocument();
276
+
277
+ checkSignatureDeferred.resolve({ isSigned: false });
278
+ await waitFor(() => {
279
+ expect(mockCheckSignature).toHaveBeenCalledTimes(CONTRACT_SETTINGS.pollLimit);
280
+ });
281
+
282
+ // As the first call confirm the document has been signed,
283
+ // the ContractFrame should go to the finish step and the onDone callback should
284
+ // have been called
285
+ await expectBannerError(
286
+ 'The signature is taking more time than expected ... please come back later.',
287
+ );
288
+ expect(mockOnDone).not.toHaveBeenCalled();
289
+ button = screen.getByRole('button', { name: 'Close dialog' });
290
+ await user.click(button);
291
+
292
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
293
+ });
294
+
295
+ it('fails to retrieve invitation link', async () => {
296
+ const user = userEvent.setup();
297
+ const mockGetInvitationLink = jest.fn(async () => {
298
+ throw new HttpError(404, 'Not found');
299
+ });
300
+ const checkSignatureDeferred = new Deferred<{ isSigned: boolean }>();
301
+ const mockCheckSignature = jest.fn(async () => checkSignatureDeferred.promise);
302
+ const mockOnDone = jest.fn();
303
+ const mockOnClose = jest.fn();
304
+
305
+ await act(async () => {
306
+ render(
307
+ <Wrapper>
308
+ <AbstractContractFrame
309
+ getInvitationLink={mockGetInvitationLink}
310
+ checkSignature={mockCheckSignature}
311
+ isOpen={true}
312
+ onDone={mockOnDone}
313
+ onClose={mockOnClose}
314
+ />
315
+ </Wrapper>,
316
+ );
317
+ });
318
+
319
+ expect(mockGetInvitationLink).toHaveBeenCalledTimes(1);
320
+ expect(await screen.findByTestId('dashboard-contract-frame')).toBeInTheDocument();
321
+
322
+ await expectBannerError(
323
+ 'An error happened while initializing the signature process. Please retry later.',
324
+ );
325
+
326
+ // Dummy signature interface should have been rendered
327
+ const button = screen.getByRole('button', { name: 'Close dialog' });
328
+ await user.click(button);
329
+
330
+ expect(mockOnClose).toHaveBeenCalledTimes(1);
331
+ });
332
+ });
@@ -0,0 +1,289 @@
1
+ import React, { lazy, Suspense, useEffect, useRef, useState } from 'react';
2
+ import { Button, Loader } from '@openfun/cunningham-react';
3
+ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
4
+ import { Modal } from 'components/Modal';
5
+ import { Maybe } from 'types/utils';
6
+ import { CONTRACT_SETTINGS } from 'settings';
7
+ import Banner, { BannerType } from 'components/Banner';
8
+ import { SuccessIcon } from 'components/SuccessIcon';
9
+
10
+ /*
11
+ /!\ This component should not be used directly, only its implementations should be.
12
+ Take a look at:
13
+ - ./OrganizationContractFrame.tsx
14
+ - ./LearnerContractFrame.tsx
15
+ */
16
+
17
+ const LazySignatureDummy = lazy(
18
+ () => import('widgets/Dashboard/components/Signature/SignatureDummy'),
19
+ );
20
+ const LazySignatureLexPersona = lazy(
21
+ () => import('widgets/Dashboard/components/Signature/SignatureLexPersona'),
22
+ );
23
+
24
+ export const messages = defineMessages({
25
+ errorSubmitForSignature: {
26
+ defaultMessage:
27
+ 'An error happened while initializing the signature process. Please retry later.',
28
+ description: 'Message displayed inside the contract signin modal while an error occurred.',
29
+ id: 'components.DashboardItem.Order.ContractFrame.errorSubmitForSignature',
30
+ },
31
+ errorMaxPolling: {
32
+ defaultMessage: 'The signature is taking more time than expected ... please come back later.',
33
+ description:
34
+ 'Message displayed inside the contract signin modal if the order polling has reached its maximum attempts.',
35
+ id: 'components.DashboardItem.Order.ContractFrame.errorMaxPolling',
36
+ },
37
+ errorPolling: {
38
+ defaultMessage: 'An error happened while verifying signature. Please come back later.',
39
+ description:
40
+ 'Message displayed inside the contract signin modal if the order polling failed a request.',
41
+ id: 'components.DashboardItem.Order.ContractFrame.errorPolling',
42
+ },
43
+ loadingContract: {
44
+ defaultMessage: 'Loading your contract ...',
45
+ description: 'Message displayed inside the contract signin modal when loading the contract.',
46
+ id: 'components.DashboardItem.Order.ContractFrame.loadingContract',
47
+ },
48
+ polling: {
49
+ defaultMessage: 'Verifying signature ...',
50
+ description: 'Message displayed inside the contract signin modal when polling the order.',
51
+ id: 'components.DashboardItem.Order.ContractFrame.polling',
52
+ },
53
+ pollingDescription: {
54
+ defaultMessage:
55
+ 'We are waiting for the signature to be validated from our signature platform. It can take up to few minutes. Do not close this page.',
56
+ description: 'Message displayed inside the contract signin modal when polling the order.',
57
+ id: 'components.DashboardItem.Order.ContractFrame.pollingDescription',
58
+ },
59
+ finishedCaption: {
60
+ defaultMessage: 'Congratulations!',
61
+ description: 'Title displayed inside the contract signin modal when the contract is signed.',
62
+ id: 'components.DashboardItem.Order.ContractFrame.finishedCaption',
63
+ },
64
+ finishedDescription: {
65
+ defaultMessage:
66
+ 'You will receive an email once your contract will be fully signed. You can now enroll in your course runs!',
67
+ description: 'Message displayed inside the contract signin modal when the contract is signed.',
68
+ id: 'components.DashboardItem.Order.ContractFrame.finishedDescription',
69
+ },
70
+ finishedButton: {
71
+ defaultMessage: 'Next',
72
+ description: 'Button displayed inside the contract signin modal when the contract is signed.',
73
+ id: 'components.DashboardItem.Order.ContractFrame.finishedButton',
74
+ },
75
+ });
76
+
77
+ const DUMMY_REGEX = /https:\/\/dummysignaturebackend.fr/;
78
+
79
+ enum SignatureType {
80
+ LEX = 'lex',
81
+ DUMMY = 'dummy',
82
+ }
83
+
84
+ enum ContractSteps {
85
+ LOADING_CONTRACT,
86
+ SIGNING,
87
+ POLLING,
88
+ FINISHED,
89
+ ERROR,
90
+ NONE,
91
+ }
92
+
93
+ export interface AbstractProps {
94
+ isOpen: boolean;
95
+ onDone?: () => void;
96
+ onClose?: () => void;
97
+ }
98
+
99
+ export interface Props extends AbstractProps {
100
+ getInvitationLink: () => Promise<string>;
101
+ checkSignature: () => Promise<{ isSigned: boolean }>;
102
+ }
103
+
104
+ type FrameContentProps = Omit<Props, 'isOpen'>;
105
+
106
+ export interface SignatureProps {
107
+ onDone: () => void;
108
+ onError: (error: string) => void;
109
+ invitationLink: string;
110
+ }
111
+
112
+ const AbstractContractFrame = ({ isOpen, ...props }: Props) => {
113
+ return (
114
+ <Modal
115
+ isOpen={isOpen}
116
+ shouldCloseOnOverlayClick={false}
117
+ shouldCloseOnEsc={false}
118
+ onRequestClose={props.onClose}
119
+ >
120
+ <ContractFrameContent {...props} />
121
+ </Modal>
122
+ );
123
+ };
124
+
125
+ const ContractFrameContent = ({
126
+ getInvitationLink,
127
+ checkSignature,
128
+ onClose,
129
+ onDone,
130
+ }: FrameContentProps) => {
131
+ const intl = useIntl();
132
+ const [step, setStep] = useState(ContractSteps.LOADING_CONTRACT);
133
+ const [signatureType, setSignatureType] = useState<SignatureType>();
134
+ const [invitationLink, setInvitationLink] = useState<Maybe<string>>();
135
+ const [error, setError] = useState<Maybe<string>>();
136
+ const timeoutRef = useRef<NodeJS.Timeout>();
137
+
138
+ const setErrored = (e: string) => {
139
+ setStep(ContractSteps.ERROR);
140
+ setError(e);
141
+ };
142
+
143
+ const onSigned = () => {
144
+ startStepPoll();
145
+ };
146
+
147
+ const onSignatureError = (e: string) => {
148
+ setErrored(e);
149
+ };
150
+
151
+ /*
152
+ 1. Start the signature process.
153
+ Retrieve the signature link then go to the signature step.
154
+ */
155
+ const start = async () => {
156
+ try {
157
+ const link = await getInvitationLink();
158
+ startStepSign(link);
159
+ } catch (e) {
160
+ setErrored(intl.formatMessage(messages.errorSubmitForSignature));
161
+ }
162
+ };
163
+
164
+ /*
165
+ 2. Instantiate the right signature interface according to the invitation link.
166
+ Then go to signing step.
167
+ */
168
+ const startStepSign = async (signatureLink: string) => {
169
+ setInvitationLink(signatureLink);
170
+ if (signatureLink.match(DUMMY_REGEX)) {
171
+ // Nothing to do, in dummy mode submitting for signature automatically signs the contract.
172
+ setSignatureType(SignatureType.DUMMY);
173
+ } else {
174
+ // Open iframe then wait for it to close.
175
+ setSignatureType(SignatureType.LEX);
176
+ }
177
+ setStep(ContractSteps.SIGNING);
178
+ };
179
+
180
+ /*
181
+ 3. Once the signature is done, start the polling step.
182
+ Poll the backend until it has been notified by signature provided.
183
+ Then, when it's done, finish the signature process.
184
+ */
185
+ const startStepPoll = async () => {
186
+ setStep(ContractSteps.POLLING);
187
+ let round = 0;
188
+
189
+ const poll = async () => {
190
+ if (round >= CONTRACT_SETTINGS.pollLimit) {
191
+ timeoutRef.current = undefined;
192
+ setErrored(intl.formatMessage(messages.errorMaxPolling));
193
+ } else {
194
+ try {
195
+ const { isSigned } = await checkSignature();
196
+ if (isSigned) {
197
+ timeoutRef.current = undefined;
198
+ finish();
199
+ } else {
200
+ round++;
201
+ timeoutRef.current = setTimeout(poll, CONTRACT_SETTINGS.pollInterval);
202
+ }
203
+ } catch (e) {
204
+ setErrored(intl.formatMessage(messages.errorPolling));
205
+ }
206
+ }
207
+ };
208
+
209
+ poll();
210
+ };
211
+
212
+ /*
213
+ 4. Finish the signature process.
214
+ And exec the onDone callback if there is.
215
+ */
216
+ const finish = () => {
217
+ setStep(ContractSteps.FINISHED);
218
+ onDone?.();
219
+ };
220
+
221
+ const renderLoadingContract = () => {
222
+ return (
223
+ <div className="ContractFrame__loading-container">
224
+ <h3 className="ContractFrame__caption">
225
+ <FormattedMessage {...messages.loadingContract} />
226
+ </h3>
227
+ <Loader />
228
+ </div>
229
+ );
230
+ };
231
+
232
+ useEffect(() => {
233
+ start();
234
+ }, []);
235
+
236
+ return (
237
+ <div className="ContractFrame__modal-body" data-testid="dashboard-contract-frame">
238
+ {error && <Banner message={error} type={BannerType.ERROR} />}
239
+ {step === ContractSteps.LOADING_CONTRACT && renderLoadingContract()}
240
+ {step === ContractSteps.SIGNING && (
241
+ <Suspense fallback={renderLoadingContract()}>
242
+ {signatureType === SignatureType.DUMMY && (
243
+ <LazySignatureDummy
244
+ onDone={onSigned}
245
+ onError={onSignatureError}
246
+ invitationLink={invitationLink!}
247
+ />
248
+ )}
249
+ {signatureType === SignatureType.LEX && (
250
+ <LazySignatureLexPersona
251
+ onDone={onSigned}
252
+ onError={onSignatureError}
253
+ invitationLink={invitationLink!}
254
+ />
255
+ )}
256
+ </Suspense>
257
+ )}
258
+ {step === ContractSteps.POLLING && (
259
+ <div className="ContractFrame__loading-container">
260
+ <div>
261
+ <h3 className="ContractFrame__caption">
262
+ <FormattedMessage {...messages.polling} />
263
+ </h3>
264
+ <p className="ContractFrame__content">
265
+ <FormattedMessage {...messages.pollingDescription} />
266
+ </p>
267
+ </div>
268
+ <Loader />
269
+ </div>
270
+ )}
271
+ {step === ContractSteps.FINISHED && (
272
+ <div className="ContractFrame__finished">
273
+ <SuccessIcon />
274
+ <h3 className="ContractFrame__caption">
275
+ <FormattedMessage {...messages.finishedCaption} />
276
+ </h3>
277
+ <p className="ContractFrame__content">
278
+ <FormattedMessage {...messages.finishedDescription} />
279
+ </p>
280
+ <Button onClick={onClose}>
281
+ <FormattedMessage {...messages.finishedButton} />
282
+ </Button>
283
+ </div>
284
+ )}
285
+ </div>
286
+ );
287
+ };
288
+
289
+ export default AbstractContractFrame;