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
@@ -1,486 +0,0 @@
1
- import {
2
- getByText,
3
- render,
4
- screen,
5
- waitFor,
6
- waitForElementToBeRemoved,
7
- } from '@testing-library/react';
8
- import fetchMock from 'fetch-mock';
9
- import type { PropsWithChildren } from 'react';
10
- import { IntlProvider } from 'react-intl';
11
- import { QueryClientProvider } from '@tanstack/react-query';
12
- import { RichieContextFactory as mockRichieContextFactory } from 'utils/test/factories/richie';
13
- import {
14
- CertificateProductFactory,
15
- EnrollmentFactory,
16
- OrderFactory,
17
- ProductFactory,
18
- } from 'utils/test/factories/joanie';
19
- import JoanieApiProvider from 'contexts/JoanieApiContext';
20
- import { CourseRun, Enrollment, Order, OrderState, Product } from 'types/Joanie';
21
- import { createTestQueryClient } from 'utils/test/createTestQueryClient';
22
- import { Deferred } from 'utils/test/deferred';
23
- import JoanieSessionProvider from 'contexts/SessionContext/JoanieSessionProvider';
24
- import CourseProductItem from '.';
25
-
26
- jest.mock('utils/context', () => ({
27
- __esModule: true,
28
- default: mockRichieContextFactory({
29
- authentication: { backend: 'fonzie', endpoint: 'https://auth.test' },
30
- joanie_backend: { endpoint: 'https://joanie.test' },
31
- }).one(),
32
- }));
33
-
34
- jest.mock('./components/CourseProductCertificateItem', () => ({
35
- __esModule: true,
36
- default: () => <div data-testid="CertificateItem" />,
37
- }));
38
-
39
- jest.mock('./components/CourseProductCourseRuns', () => ({
40
- CourseRunList: ({ courseRuns }: { courseRuns: CourseRun[] }) => (
41
- <div data-testid={`CourseRunList-${courseRuns.map(({ id }) => id).join('-')}`} />
42
- ),
43
- EnrollableCourseRunList: ({ courseRuns, order }: { courseRuns: CourseRun[]; order: Order }) => (
44
- <div
45
- data-testid={`EnrollableCourseRunList-${courseRuns.map(({ id }) => id).join('-')}-${
46
- order.id
47
- }`}
48
- />
49
- ),
50
- EnrolledCourseRun: ({ enrollment }: { enrollment: Enrollment }) => (
51
- <div data-testid={`EnrolledCourseRun-${enrollment.id}`} />
52
- ),
53
- }));
54
-
55
- describe('CourseProductItem', () => {
56
- const priceFormatter = (currency: string, price: number) =>
57
- new Intl.NumberFormat('en', {
58
- currency,
59
- style: 'currency',
60
- }).format(price);
61
-
62
- beforeAll(() => {
63
- // As dialog is rendered through a Portal, we have to add the DOM element in which the dialog will be rendered.
64
- const modalExclude = document.createElement('div');
65
- modalExclude.setAttribute('id', 'modal-exclude');
66
- document.body.appendChild(modalExclude);
67
- });
68
-
69
- beforeEach(() => {
70
- fetchMock.get('https://joanie.test/api/v1.0/addresses/', []);
71
- fetchMock.get('https://joanie.test/api/v1.0/credit-cards/', []);
72
- });
73
-
74
- afterEach(() => {
75
- fetchMock.restore();
76
- });
77
-
78
- const Wrapper = ({ withSession, children }: PropsWithChildren<{ withSession?: boolean }>) => (
79
- <IntlProvider locale="en">
80
- <JoanieApiProvider>
81
- <QueryClientProvider client={createTestQueryClient({ user: withSession || null })}>
82
- <JoanieSessionProvider>{children}</JoanieSessionProvider>
83
- </QueryClientProvider>
84
- </JoanieApiProvider>
85
- </IntlProvider>
86
- );
87
-
88
- it('renders product information', async () => {
89
- const product: Product = ProductFactory().one();
90
- const productDeferred = new Deferred();
91
- fetchMock.get(
92
- `https://joanie.test/api/v1.0/products/${product.id}/?course=00000`,
93
- productDeferred.promise,
94
- );
95
-
96
- render(
97
- <Wrapper>
98
- <CourseProductItem courseCode="00000" productId={product.id} />
99
- </Wrapper>,
100
- );
101
-
102
- // - A loader should be displayed while product information are fetching
103
- screen.getByRole('status', { name: 'Loading product information...' });
104
-
105
- productDeferred.resolve(product);
106
-
107
- await screen.findByRole('heading', { level: 3, name: product.title });
108
- // the price shouldn't be a heading to prevent misdirection for screen reader users,
109
- // but we want to it to visually look like a h6
110
-
111
- const $price = screen.getByText(
112
- // the price formatter generates non-breaking spaces and getByText doesn't seem to handle that well, replace it
113
- // with a regular space. We replace NNBSP (\u202F) and NBSP (\u00a0) with a regular space
114
- priceFormatter(product.price_currency, product.price).replace(/(\u202F|\u00a0)/g, ' '),
115
- );
116
- expect($price.tagName).toBe('STRONG');
117
- expect($price.classList.contains('h6')).toBe(true);
118
-
119
- // Languages and date range should not be displayed
120
- expect(screen.queryByTestId('product-widget__header-metadata-dates')).not.toBeInTheDocument();
121
- expect(
122
- screen.queryByTestId('product-widget__header-metadata-languages'),
123
- ).not.toBeInTheDocument();
124
-
125
- // - Render all target courses information
126
- product.target_courses.forEach((course) => {
127
- const $item = screen.getByTestId(`course-item-${course.code}`);
128
- // the course title shouldn't be a heading to prevent misdirection for screen reader users,
129
- // but we want to it to visually look like a h5
130
- const $courseTitle = getByText($item, course.title);
131
- expect($courseTitle.tagName).toBe('STRONG');
132
- expect($courseTitle.classList.contains('h5')).toBe(true);
133
- screen.getByTestId(`CourseRunList-${course.course_runs.map(({ id }) => id).join('-')}`);
134
- });
135
-
136
- // - Render <CertificateItem />
137
- screen.getByTestId('CertificateItem');
138
-
139
- // - Render a login button
140
- screen.getByRole('button', { name: `Login to purchase "${product.title}"` });
141
- // - Does not render PurchaseButton cta
142
- expect(screen.queryByTestId('PurchaseButton__cta')).toBeNull();
143
- });
144
-
145
- it('does not render <CertificateItem /> if product do not have a certificate', async () => {
146
- const product: Product = ProductFactory({ certificate_definition: undefined }).one();
147
- fetchMock.get(`https://joanie.test/api/v1.0/products/${product.id}/?course=00000`, product);
148
-
149
- render(
150
- <Wrapper>
151
- <CourseProductItem productId={product.id} courseCode="00000" />
152
- </Wrapper>,
153
- );
154
-
155
- // Wait for product information to be fetched
156
- await screen.findByRole('heading', { level: 3, name: product.title });
157
-
158
- // - Does not render <CertificateItem />
159
- expect(screen.queryByTestId('CertificateItem')).toBeNull();
160
- });
161
-
162
- it('adapts its layout if compact is set', async () => {
163
- const product: Product = ProductFactory().one();
164
- const productDeferred = new Deferred();
165
- fetchMock.get(
166
- `https://joanie.test/api/v1.0/products/${product.id}/?course=00000`,
167
- productDeferred.promise,
168
- );
169
-
170
- const { container } = render(
171
- <Wrapper>
172
- <CourseProductItem courseCode="00000" productId={product.id} compact />
173
- </Wrapper>,
174
- );
175
-
176
- // - A loader should be displayed while product information are fetching
177
- screen.getByRole('status', { name: 'Loading product information...' });
178
-
179
- productDeferred.resolve(product);
180
-
181
- // In the header, we should display the product title, the product price
182
- // and product date range and languages
183
- await screen.findByRole('heading', { level: 3, name: product.title });
184
- // the price shouldn't be a heading to prevent misdirection for screen reader users,
185
- // but we want to it to visually look like a h6
186
-
187
- const $price = screen.getByText(
188
- // the price formatter generates non-breaking spaces and getByText doesn't seem to handle that well, replace it
189
- // with a regular space. We replace NNBSP (\u202F) and NBSP (\u00a0) with a regular space
190
- priceFormatter(product.price_currency, product.price).replace(/(\u202F|\u00a0)/g, ' '),
191
- );
192
- expect($price.tagName).toBe('STRONG');
193
- expect($price.classList.contains('h6')).toBe(true);
194
-
195
- screen.getByTestId('product-widget__header-metadata-dates');
196
- screen.getByTestId('product-widget__header-metadata-languages');
197
-
198
- // Then the content block should only display the purchase button.
199
- const $productWidgetContent = container.querySelector('.product-widget__content');
200
- expect($productWidgetContent).not.toBeInTheDocument();
201
-
202
- // - Any target courses information should be displayed
203
- product.target_courses.forEach((course) => {
204
- const $item = screen.queryByTestId(`course-item-${course.code}`);
205
- expect($item).not.toBeInTheDocument();
206
- });
207
-
208
- // - Any <CertificateItem /> should be displayed
209
- expect(screen.queryByTestId('CertificateItem')).not.toBeInTheDocument();
210
-
211
- // - Render a login button
212
- screen.getByRole('button', { name: `Login to purchase "${product.title}"` });
213
- // - Does not render PurchaseButton cta
214
- expect(screen.queryByTestId('PurchaseButton__cta')).not.toBeInTheDocument();
215
- });
216
-
217
- it('adapts information when user purchased the product', async () => {
218
- const product: Product = ProductFactory().one();
219
- const order: Order = OrderFactory({
220
- product: product.id,
221
- course: '00000',
222
- target_courses: product.target_courses,
223
- }).one();
224
-
225
- fetchMock.get(`https://joanie.test/api/v1.0/products/${product.id}/?course=00000`, product);
226
- fetchMock.get(`https://joanie.test/api/v1.0/orders/`, [order]);
227
-
228
- render(
229
- <Wrapper withSession>
230
- <CourseProductItem productId={product.id} courseCode="00000" />
231
- </Wrapper>,
232
- );
233
-
234
- // Wait for product information to be fetched
235
- const loadingMessage = screen.getByRole('status', { name: 'Loading product information...' });
236
- await waitForElementToBeRemoved(loadingMessage);
237
- await screen.findByRole('heading', { level: 3, name: product.title });
238
-
239
- // - In place of product price, a label should be displayed
240
- const $enrolledInfo = await screen.findByText('Purchased');
241
- expect($enrolledInfo.tagName).toBe('STRONG');
242
- expect($enrolledInfo.classList.contains('h6')).toBe(true);
243
-
244
- // - Render all order's target courses information with EnrollableCourseRunList component
245
- await waitFor(() => {
246
- order.target_courses.forEach((course) => {
247
- const $item = screen.getByTestId(`course-item-${course.code}`);
248
- // the course title shouldn't be a heading to prevent misdirection for screen reader users,
249
- // but we want to it to visually look like a h5
250
- const $courseTitle = getByText($item, course.title);
251
- expect($courseTitle.tagName).toBe('STRONG');
252
- expect($courseTitle.classList.contains('h5')).toBe(true);
253
- screen.getByTestId(
254
- `EnrollableCourseRunList-${course.course_runs.map(({ id }) => id).join('-')}-${order.id}`,
255
- );
256
- });
257
- });
258
-
259
- // - Render <CertificateItem />
260
- screen.getByTestId('CertificateItem');
261
-
262
- // - Does not Render PurchaseButton cta
263
- expect(screen.queryByTestId('PurchaseButton__cta')).toBeNull();
264
- });
265
-
266
- it('adapts information when user purchased the product even if compact is set', async () => {
267
- const product: Product = ProductFactory().one();
268
- const order: Order = OrderFactory({
269
- product: product.id,
270
- course: '00000',
271
- target_courses: product.target_courses,
272
- }).one();
273
-
274
- fetchMock.get(`https://joanie.test/api/v1.0/products/${product.id}/?course=00000`, product);
275
- fetchMock.get(`https://joanie.test/api/v1.0/orders/`, [order]);
276
-
277
- render(
278
- <Wrapper withSession>
279
- <CourseProductItem productId={product.id} courseCode="00000" compact />
280
- </Wrapper>,
281
- );
282
-
283
- // Wait for product information to be fetched
284
- const loadingMessage = screen.getByRole('status', { name: 'Loading product information...' });
285
- await waitForElementToBeRemoved(loadingMessage);
286
- await screen.findByRole('heading', { level: 3, name: product.title });
287
-
288
- // - In place of product price, a label should be displayed
289
- const $enrolledInfo = await screen.findByText('Purchased');
290
- expect($enrolledInfo.tagName).toBe('STRONG');
291
- expect($enrolledInfo.classList.contains('h6')).toBe(true);
292
-
293
- // - Product date range and languages should not be displayed anymore
294
- expect(screen.queryByTestId('product-widget__header-metadata-dates')).not.toBeInTheDocument();
295
- expect(
296
- screen.queryByTestId('product-widget__header-metadata-languages'),
297
- ).not.toBeInTheDocument();
298
-
299
- // - Render all order's target courses information with EnrollableCourseRunList component
300
- await waitFor(() => {
301
- order.target_courses.forEach((course) => {
302
- const $item = screen.getByTestId(`course-item-${course.code}`);
303
- // the course title shouldn't be a heading to prevent misdirection for screen reader users,
304
- // but we want to it to visually look like a h5
305
- const $courseTitle = getByText($item, course.title);
306
- expect($courseTitle.tagName).toBe('STRONG');
307
- expect($courseTitle.classList.contains('h5')).toBe(true);
308
- screen.getByTestId(
309
- `EnrollableCourseRunList-${course.course_runs.map(({ id }) => id).join('-')}-${order.id}`,
310
- );
311
- });
312
- });
313
-
314
- // - Render <CertificateItem />
315
- screen.getByTestId('CertificateItem');
316
-
317
- // - Does not Render PurchaseButton cta
318
- expect(screen.queryByTestId('PurchaseButton__cta')).toBeNull();
319
- });
320
-
321
- it('renders enrollment information when user is enrolled to a course run', async () => {
322
- const product: Product = CertificateProductFactory().one();
323
- // - Create an order with an active enrollment
324
- const enrollment: Enrollment = EnrollmentFactory({
325
- course_run: product.target_courses[0]!.course_runs[0]! as CourseRun,
326
- }).one();
327
- const order: Order = OrderFactory({
328
- product: product.id,
329
- course: '00000',
330
- target_courses: product.target_courses,
331
- enrollments: [enrollment],
332
- }).one();
333
-
334
- fetchMock.get(`https://joanie.test/api/v1.0/products/${product.id}/?course=00000`, product);
335
- fetchMock.get(`https://joanie.test/api/v1.0/orders/`, [order]);
336
-
337
- render(
338
- <Wrapper withSession>
339
- <CourseProductItem productId={product.id} courseCode="00000" />
340
- </Wrapper>,
341
- );
342
-
343
- // Wait for product information to be fetched
344
- const loadingMessage = screen.getByRole('status', { name: 'Loading product information...' });
345
- await waitForElementToBeRemoved(loadingMessage);
346
- await screen.findByRole('heading', { level: 3, name: product.title });
347
-
348
- // - In place of product price, a label should be displayed
349
- const $enrolledInfo: HTMLElement = await screen.findByText('Purchased');
350
- expect($enrolledInfo!.tagName).toBe('STRONG');
351
- expect($enrolledInfo!.classList.contains('h6')).toBe(true);
352
-
353
- const [targetCourse, ...targetCourses] = product.target_courses;
354
- // - The first target course should display the EnrolledCourseRun component
355
- const $courseTitle = screen.getByText(targetCourse.title);
356
- expect($courseTitle.tagName).toBe('STRONG');
357
- expect($courseTitle.classList.contains('h5')).toBe(true);
358
- await waitFor(() => {
359
- screen.getByTestId(`EnrolledCourseRun-${enrollment.id}`);
360
- });
361
-
362
- // - Other target courses should display EnrollableCourseRunList component
363
- targetCourses.forEach((course) => {
364
- const $item = screen.getByTestId(`course-item-${course.code}`);
365
- const $itemTitle = getByText($item, course.title);
366
- expect($itemTitle.tagName).toBe('STRONG');
367
- expect($itemTitle.classList.contains('h5')).toBe(true);
368
-
369
- screen.getByTestId(
370
- `EnrollableCourseRunList-${course.course_runs.map(({ id }) => id).join('-')}-${order.id}`,
371
- );
372
- });
373
- });
374
-
375
- it('does not render sale tunnel button if user already has a pending order', async () => {
376
- const product: Product = ProductFactory().one();
377
- const order: Order = OrderFactory({
378
- product: product.id,
379
- course: '00000',
380
- target_courses: product.target_courses,
381
- state: OrderState.PENDING,
382
- }).one();
383
- fetchMock.get(`https://joanie.test/api/v1.0/products/${product.id}/?course=00000`, product);
384
- fetchMock.get(`https://joanie.test/api/v1.0/orders/`, [order]);
385
-
386
- render(
387
- <Wrapper withSession>
388
- <CourseProductItem productId={product.id} courseCode="00000" />
389
- </Wrapper>,
390
- );
391
-
392
- // Wait for product information to be fetched
393
- const loadingMessage = screen.getByRole('status', { name: 'Loading product information...' });
394
- await waitForElementToBeRemoved(loadingMessage);
395
- await screen.findByRole('heading', { level: 3, name: product.title });
396
-
397
- // - In place of product price, a label "Pending" should be displayed
398
- const $enrolledInfo = await screen.findByText('Pending');
399
- expect($enrolledInfo.tagName).toBe('STRONG');
400
- expect($enrolledInfo.classList.contains('h6')).toBe(true);
401
-
402
- // - As order is pending, the user should not be able to enroll to course runs.
403
- await waitFor(() => {
404
- order.target_courses.forEach((course) => {
405
- const $item = screen.getByTestId(`course-item-${course.code}`);
406
- // the course title shouldn't be a heading to prevent misdirection for screen reader users,
407
- // but we want to it to visually look like a h5
408
- const $courseTitle = getByText($item, course.title);
409
- expect($courseTitle.tagName).toBe('STRONG');
410
- expect($courseTitle.classList.contains('h5')).toBe(true);
411
- screen.getByTestId(`CourseRunList-${course.course_runs.map(({ id }) => id).join('-')}`);
412
- });
413
- });
414
-
415
- // - Render <CertificateItem />
416
- screen.getByTestId('CertificateItem');
417
-
418
- // - Does not Render PurchaseButton cta
419
- expect(screen.queryByTestId('PurchaseButton__cta')).toBeNull();
420
- });
421
-
422
- it('adapts layout when user has a pending order and compact prop is set', async () => {
423
- const product: Product = ProductFactory().one();
424
- const order: Order = OrderFactory({
425
- product: product.id,
426
- course: '00000',
427
- target_courses: product.target_courses,
428
- state: OrderState.PENDING,
429
- }).one();
430
- fetchMock.get(`https://joanie.test/api/v1.0/products/${product.id}/?course=00000`, product);
431
- fetchMock.get(`https://joanie.test/api/v1.0/orders/`, [order]);
432
-
433
- render(
434
- <Wrapper withSession>
435
- <CourseProductItem productId={product.id} courseCode="00000" compact />
436
- </Wrapper>,
437
- );
438
-
439
- // Wait for product information to be fetched
440
- const loadingMessage = screen.getByRole('status', { name: 'Loading product information...' });
441
- await waitForElementToBeRemoved(loadingMessage);
442
- await screen.findByRole('heading', { level: 3, name: product.title });
443
-
444
- // - In place of product price, a label should be displayed
445
- const $enrolledInfo = await screen.findByText('Pending');
446
- expect($enrolledInfo.tagName).toBe('STRONG');
447
- expect($enrolledInfo.classList.contains('h6')).toBe(true);
448
-
449
- // - Product date range and languages should be displayed
450
- screen.getByTestId('product-widget__header-metadata-dates');
451
- screen.getByTestId('product-widget__header-metadata-languages');
452
-
453
- // - Target courses should not be rendered
454
- await waitFor(() => {
455
- order.target_courses.forEach((course) => {
456
- const $item = screen.queryByTestId(`course-item-${course.code}`);
457
- expect($item).not.toBeInTheDocument();
458
- });
459
- });
460
-
461
- // - <CertificateItem /> should not be rendered
462
- expect(screen.queryByTestId('CertificateItem')).not.toBeInTheDocument();
463
-
464
- // - Does not Render PurchaseButton cta
465
- expect(screen.queryByTestId('PurchaseButton__cta')).toBeNull();
466
- });
467
-
468
- it('renders error message when product fetching has failed', async () => {
469
- const product: Product = ProductFactory().one();
470
-
471
- fetchMock.get(`https://joanie.test/api/v1.0/products/${product.id}/?course=00000`, 404, {});
472
-
473
- render(
474
- <Wrapper>
475
- <CourseProductItem productId={product.id} courseCode="00000" />
476
- </Wrapper>,
477
- );
478
-
479
- // Wait for product information to be fetched
480
- const loadingMessage = screen.getByRole('status', { name: 'Loading product information...' });
481
- await waitForElementToBeRemoved(loadingMessage);
482
-
483
- // - As product fetching has failed, an error message should be displayed
484
- await screen.findByText('An error occurred while fetching product. Please retry later.');
485
- });
486
- });
@@ -1,205 +0,0 @@
1
- import { Children, useMemo } from 'react';
2
- import { defineMessages, FormattedMessage, FormattedNumber, useIntl } from 'react-intl';
3
- import c from 'classnames';
4
- import type * as Joanie from 'types/Joanie';
5
- import { OrderState } from 'types/Joanie';
6
- import { useProduct } from 'hooks/useProduct';
7
- import { Spinner } from 'components/Spinner';
8
- import { useOrders } from 'hooks/useOrders';
9
- import { Icon, IconTypeEnum } from 'components/Icon';
10
- import { Maybe } from 'types/utils';
11
- import useDateFormat from 'hooks/useDateFormat';
12
- import { ProductHelper } from 'utils/ProductHelper';
13
- import CertificateItem from './components/CourseProductCertificateItem';
14
- import CourseRunItem from './components/CourseRunItem';
15
- import PurchaseButton from './components/PurchaseButton';
16
- import { CourseProductProvider } from './contexts/CourseProductContext';
17
-
18
- const messages = defineMessages({
19
- purchased: {
20
- defaultMessage: 'Purchased',
21
- description: 'Message displayed when authenticated user owned the product',
22
- id: 'components.CourseProductItem.purchased',
23
- },
24
- pending: {
25
- defaultMessage: 'Pending',
26
- description:
27
- 'Message displayed when authenticated user has purchased the product but order is still pending',
28
- id: 'components.CourseProductItem.pending',
29
- },
30
- loading: {
31
- defaultMessage: 'Loading product information...',
32
- description:
33
- 'Accessible text for the initial loading spinner displayed when product is fetching',
34
- id: 'components.CourseProductItem.loadingInitial',
35
- },
36
- fromTo: {
37
- defaultMessage: 'From {from} to {to}',
38
- description: 'Course run date range',
39
- id: 'components.CourseProductItem.fromTo',
40
- },
41
- availableIn: {
42
- defaultMessage: 'Available in {languages}',
43
- description: 'Course run languages',
44
- id: 'components.CourseProductItem.availableIn',
45
- },
46
- });
47
-
48
- export interface Props {
49
- compact?: boolean;
50
- courseCode: Joanie.CourseLight['code'];
51
- productId: Joanie.Product['id'];
52
- }
53
-
54
- type HeaderProps = {
55
- compact: boolean;
56
- hasPurchased: boolean;
57
- order: Maybe<Joanie.Order>;
58
- product: Joanie.Product;
59
- };
60
- const Header = ({ product, order, hasPurchased, compact }: HeaderProps) => {
61
- const intl = useIntl();
62
- const formatDate = useDateFormat();
63
-
64
- const canShowMetadata = useMemo(() => {
65
- return compact && !order;
66
- }, [compact, hasPurchased]);
67
-
68
- const [minDate, maxDate] = useMemo(() => {
69
- if (!canShowMetadata) return [undefined, undefined];
70
- return ProductHelper.getDateRange(product);
71
- }, [canShowMetadata, product]);
72
-
73
- const languages = useMemo(() => {
74
- if (!canShowMetadata) return '';
75
- return ProductHelper.getLanguages(product, true, intl);
76
- }, [canShowMetadata, product, intl]);
77
-
78
- return (
79
- <header className="product-widget__header">
80
- <div className="product-widget__header-main">
81
- <h3 className="product-widget__title">{product.title}</h3>
82
- <strong className="product-widget__price h6">
83
- {order && <FormattedMessage {...messages.purchased} />}
84
- {hasPurchased && !order && <FormattedMessage {...messages.pending} />}
85
- {!hasPurchased && (
86
- <FormattedNumber
87
- currency={product.price_currency}
88
- value={product.price}
89
- style="currency"
90
- />
91
- )}
92
- </strong>
93
- </div>
94
- {canShowMetadata && (
95
- <>
96
- <p
97
- className="product-widget__header-metadata"
98
- data-testid="product-widget__header-metadata-dates"
99
- >
100
- <Icon name={IconTypeEnum.CALENDAR} size="small" />
101
- <FormattedMessage
102
- {...messages.fromTo}
103
- values={{
104
- from: formatDate(minDate!),
105
- to: formatDate(maxDate!),
106
- }}
107
- />
108
- </p>
109
- <p
110
- className="product-widget__header-metadata"
111
- data-testid="product-widget__header-metadata-languages"
112
- >
113
- <Icon name={IconTypeEnum.LANGUAGES} size="small" />
114
- <FormattedMessage {...messages.availableIn} values={{ languages }} />
115
- </p>
116
- </>
117
- )}
118
- </header>
119
- );
120
- };
121
- const Content = ({ product, order }: { product: Joanie.Product; order?: Joanie.Order }) => {
122
- const targetCourses = useMemo(() => {
123
- if (order) {
124
- return order.target_courses;
125
- }
126
-
127
- if (product) {
128
- return product.target_courses;
129
- }
130
-
131
- return [];
132
- }, [product, order]);
133
-
134
- return (
135
- <ol className="product-widget__content">
136
- {Children.toArray(
137
- targetCourses.map((target_course) => (
138
- <CourseRunItem targetCourse={target_course} order={order} />
139
- )),
140
- )}
141
- {product.certificate_definition && (
142
- <CertificateItem certificateDefinition={product.certificate_definition} order={order} />
143
- )}
144
- </ol>
145
- );
146
- };
147
-
148
- const CourseProductItem = ({ productId, courseCode, compact = false }: Props) => {
149
- const productQuery = useProduct(productId, { course: courseCode });
150
- const product = productQuery.item;
151
- const ordersQuery = useOrders({
152
- product: productId,
153
- course: courseCode,
154
- state: [OrderState.VALIDATED, OrderState.PENDING],
155
- });
156
-
157
- const order = useMemo(
158
- () => ordersQuery.items.find(({ state }) => state === OrderState.VALIDATED),
159
- [ordersQuery.items],
160
- );
161
-
162
- const hasPurchased = useMemo(() => ordersQuery.items?.length > 0, [ordersQuery.items]);
163
- const hasError = Boolean(productQuery.states.error);
164
- const isFetching = productQuery.states.fetching || ordersQuery.states.fetching;
165
- const canShowContent = !compact || order;
166
-
167
- return (
168
- <CourseProductProvider courseCode={courseCode} productId={productId}>
169
- <section
170
- className={c('product-widget', {
171
- 'product-widget--has-error': hasError,
172
- 'product-widget--compact': compact,
173
- 'product-widget--purchased': hasPurchased,
174
- })}
175
- >
176
- {isFetching && (
177
- <div className="product-widget__overlay">
178
- <Spinner aria-labelledby="loading-course" theme="light" size="large">
179
- <span id="loading-course">
180
- <FormattedMessage {...messages.loading} />
181
- </span>
182
- </Spinner>
183
- </div>
184
- )}
185
- {hasError && (
186
- <p className="product-widget__content">
187
- <Icon name={IconTypeEnum.WARNING} size="small" />
188
- {productQuery.states.error}
189
- </p>
190
- )}
191
- {!hasError && product && (
192
- <>
193
- <Header product={product} order={order} hasPurchased={hasPurchased} compact={compact} />
194
- {canShowContent && <Content product={product} order={order} />}
195
- <footer className="product-widget__footer">
196
- <PurchaseButton product={product} disabled={hasPurchased} />
197
- </footer>
198
- </>
199
- )}
200
- </section>
201
- </CourseProductProvider>
202
- );
203
- };
204
-
205
- export default CourseProductItem;