@openedx/frontend-base 1.0.0-alpha.0

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 (356) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +112 -0
  3. package/config/babel/babel.config.js +28 -0
  4. package/config/config-helpers/createConfig.js +13 -0
  5. package/config/config-helpers/createLintConfig.js +16 -0
  6. package/config/config-helpers/getBaseConfig.js +12 -0
  7. package/config/defaultConfigPaths.js +35 -0
  8. package/config/eslint/base.eslint.config.js +113 -0
  9. package/config/index.js +12 -0
  10. package/config/jest/jest.config.js +30 -0
  11. package/config/tsconfig.json +32 -0
  12. package/config/types.js +25 -0
  13. package/config/webpack/common-config/all/getCodeRules.js +52 -0
  14. package/config/webpack/common-config/all/getFileLoaderRules.js +26 -0
  15. package/config/webpack/common-config/all/getIgnoreWarnings.js +14 -0
  16. package/config/webpack/common-config/all/getImageMinimizer.js +25 -0
  17. package/config/webpack/common-config/all/getStylesheetRule.js +112 -0
  18. package/config/webpack/common-config/dev/getDevServer.js +38 -0
  19. package/config/webpack/common-config/index.js +18 -0
  20. package/config/webpack/common-config/site/getHtmlWebpackPlugin.js +16 -0
  21. package/config/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.js +91 -0
  22. package/config/webpack/plugins/html-webpack-new-relic-plugin/index.js +7 -0
  23. package/config/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +3 -0
  24. package/config/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +108 -0
  25. package/config/webpack/plugins/paragon-webpack-plugin/index.js +7 -0
  26. package/config/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +64 -0
  27. package/config/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +53 -0
  28. package/config/webpack/plugins/paragon-webpack-plugin/utils/index.js +9 -0
  29. package/config/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +114 -0
  30. package/config/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +146 -0
  31. package/config/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +126 -0
  32. package/config/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +57 -0
  33. package/config/webpack/types.js +2 -0
  34. package/config/webpack/utils/getLocalAliases.js +65 -0
  35. package/config/webpack/utils/getPublicPath.js +6 -0
  36. package/config/webpack/utils/getResolvedSiteConfigPath.js +32 -0
  37. package/config/webpack/utils/paragonUtils.js +138 -0
  38. package/config/webpack/webpack.config.build.js +80 -0
  39. package/config/webpack/webpack.config.dev.js +76 -0
  40. package/config/webpack/webpack.config.dev.shell.js +110 -0
  41. package/eslint.config.js +18 -0
  42. package/frontend-base.d.ts +8 -0
  43. package/index.ts +7 -0
  44. package/jest.config.js +7 -0
  45. package/openedx-frontend-base.tgz +0 -0
  46. package/package.json +149 -0
  47. package/runtime/analytics/MockAnalyticsService.js +71 -0
  48. package/runtime/analytics/SegmentAnalyticsService.js +243 -0
  49. package/runtime/analytics/index.ts +12 -0
  50. package/runtime/analytics/interface.js +145 -0
  51. package/runtime/auth/AxiosCsrfTokenService.js +60 -0
  52. package/runtime/auth/AxiosJwtAuthService.js +363 -0
  53. package/runtime/auth/AxiosJwtTokenService.js +134 -0
  54. package/runtime/auth/LocalForageCache.js +76 -0
  55. package/runtime/auth/MockAuthService.js +278 -0
  56. package/runtime/auth/index.ts +19 -0
  57. package/runtime/auth/interceptors/createCsrfTokenProviderInterceptor.js +36 -0
  58. package/runtime/auth/interceptors/createJwtTokenProviderInterceptor.js +37 -0
  59. package/runtime/auth/interceptors/createProcessAxiosRequestErrorInterceptor.js +20 -0
  60. package/runtime/auth/interceptors/createRetryInterceptor.js +74 -0
  61. package/runtime/auth/interface.js +309 -0
  62. package/runtime/auth/utils.js +105 -0
  63. package/runtime/babel.config.js +3 -0
  64. package/runtime/config/index.ts +295 -0
  65. package/runtime/constants.ts +68 -0
  66. package/runtime/i18n/index.js +118 -0
  67. package/runtime/i18n/injectIntlWithShim.jsx +48 -0
  68. package/runtime/i18n/lib.ts +272 -0
  69. package/runtime/index.ts +134 -0
  70. package/runtime/initialize.js +352 -0
  71. package/runtime/jest.config.js +32 -0
  72. package/runtime/logging/MockLoggingService.js +31 -0
  73. package/runtime/logging/NewRelicLoggingService.js +184 -0
  74. package/runtime/logging/index.ts +9 -0
  75. package/runtime/logging/interface.js +109 -0
  76. package/runtime/logging/types.ts +4 -0
  77. package/runtime/react/AuthenticatedPageRoute.jsx +43 -0
  78. package/runtime/react/CombinedAppProvider.tsx +46 -0
  79. package/runtime/react/CurrentAppContext.tsx +25 -0
  80. package/runtime/react/CurrentAppProvider.tsx +46 -0
  81. package/runtime/react/Divider.tsx +5 -0
  82. package/runtime/react/ErrorBoundary.jsx +47 -0
  83. package/runtime/react/ErrorPage.jsx +72 -0
  84. package/runtime/react/LoginRedirect.jsx +16 -0
  85. package/runtime/react/PageWrap.jsx +24 -0
  86. package/runtime/react/SiteContext.tsx +32 -0
  87. package/runtime/react/SiteProvider.tsx +78 -0
  88. package/runtime/react/hooks.ts +106 -0
  89. package/runtime/react/index.ts +19 -0
  90. package/runtime/routing/index.ts +1 -0
  91. package/runtime/routing/utils.ts +34 -0
  92. package/runtime/scripts/GoogleAnalyticsLoader.ts +59 -0
  93. package/runtime/scripts/index.ts +1 -0
  94. package/runtime/setupTest.js +49 -0
  95. package/runtime/slots/Slot.tsx +32 -0
  96. package/runtime/slots/SlotContext.tsx +8 -0
  97. package/runtime/slots/hooks.ts +35 -0
  98. package/runtime/slots/index.ts +7 -0
  99. package/runtime/slots/layout/DefaultSlotLayout.tsx +9 -0
  100. package/runtime/slots/layout/hooks.ts +65 -0
  101. package/runtime/slots/layout/index.ts +5 -0
  102. package/runtime/slots/layout/types.ts +45 -0
  103. package/runtime/slots/layout/utils.ts +14 -0
  104. package/runtime/slots/types.ts +23 -0
  105. package/runtime/slots/utils.ts +59 -0
  106. package/runtime/slots/widget/WidgetContext.tsx +9 -0
  107. package/runtime/slots/widget/WidgetProvider.tsx +30 -0
  108. package/runtime/slots/widget/hooks.ts +105 -0
  109. package/runtime/slots/widget/iframe/IFrameContentWrapper.messages.tsx +16 -0
  110. package/runtime/slots/widget/iframe/IFrameContentWrapper.tsx +84 -0
  111. package/runtime/slots/widget/iframe/IFrameWidget.tsx +59 -0
  112. package/runtime/slots/widget/iframe/constants.ts +19 -0
  113. package/runtime/slots/widget/iframe/hooks.ts +179 -0
  114. package/runtime/slots/widget/iframe/index.ts +6 -0
  115. package/runtime/slots/widget/iframe/types.ts +7 -0
  116. package/runtime/slots/widget/index.ts +6 -0
  117. package/runtime/slots/widget/types.ts +134 -0
  118. package/runtime/slots/widget/utils.tsx +201 -0
  119. package/runtime/subscriptions.ts +60 -0
  120. package/runtime/testing/index.ts +9 -0
  121. package/runtime/testing/initializeMockApp.ts +81 -0
  122. package/runtime/testing/mockMessages.ts +23 -0
  123. package/runtime/utils.js +178 -0
  124. package/shell/DefaultLayout.tsx +18 -0
  125. package/shell/DefaultMain.tsx +7 -0
  126. package/shell/Logo.tsx +28 -0
  127. package/shell/Shell.messages.ts +61 -0
  128. package/shell/Shell.tsx +18 -0
  129. package/shell/app.scss +149 -0
  130. package/shell/app.ts +24 -0
  131. package/shell/babel.config.js +3 -0
  132. package/shell/dev/devFooter/app.tsx +43 -0
  133. package/shell/dev/devFooter/index.ts +1 -0
  134. package/shell/dev/devHeader/BarContext.tsx +13 -0
  135. package/shell/dev/devHeader/BarLink.tsx +16 -0
  136. package/shell/dev/devHeader/BarProvider.tsx +25 -0
  137. package/shell/dev/devHeader/CoursesLink.tsx +16 -0
  138. package/shell/dev/devHeader/FooContext.tsx +13 -0
  139. package/shell/dev/devHeader/FooLink.tsx +16 -0
  140. package/shell/dev/devHeader/FooProvider.tsx +25 -0
  141. package/shell/dev/devHeader/app.tsx +53 -0
  142. package/shell/dev/devHeader/index.ts +1 -0
  143. package/shell/dev/devHeader/providers.tsx +11 -0
  144. package/shell/dev/devHome/HomePage.tsx +28 -0
  145. package/shell/dev/devHome/app.ts +18 -0
  146. package/shell/dev/devHome/i18n/index.ts +27 -0
  147. package/shell/dev/devHome/index.ts +1 -0
  148. package/shell/dev/devHome/messages.ts +11 -0
  149. package/shell/dev/devUser/app.tsx +24 -0
  150. package/shell/dev/devUser/index.ts +1 -0
  151. package/shell/dev/index.ts +5 -0
  152. package/shell/dev/slotShowcase/HorizontalSlotLayout.tsx +11 -0
  153. package/shell/dev/slotShowcase/LayoutWithOptions.tsx +17 -0
  154. package/shell/dev/slotShowcase/SlotShowcasePage.tsx +66 -0
  155. package/shell/dev/slotShowcase/WidgetWithOptions.tsx +11 -0
  156. package/shell/dev/slotShowcase/app.tsx +373 -0
  157. package/shell/dev/slotShowcase/index.ts +1 -0
  158. package/shell/footer/CenterLinks.tsx +11 -0
  159. package/shell/footer/CopyrightNotice.tsx +36 -0
  160. package/shell/footer/Footer.tsx +34 -0
  161. package/shell/footer/LabeledLinkColumn.tsx +19 -0
  162. package/shell/footer/LanguageMenu.tsx +35 -0
  163. package/shell/footer/LanguageMenuItem.tsx +23 -0
  164. package/shell/footer/LeftLinks.tsx +11 -0
  165. package/shell/footer/LegalNotices.tsx +17 -0
  166. package/shell/footer/PoweredBy.tsx +17 -0
  167. package/shell/footer/RevealLinks.tsx +43 -0
  168. package/shell/footer/RightLinks.tsx +11 -0
  169. package/shell/footer/app.tsx +73 -0
  170. package/shell/footer/data/api.ts +48 -0
  171. package/shell/footer/index.ts +2 -0
  172. package/shell/header/AuthenticatedMenu.tsx +32 -0
  173. package/shell/header/Header.tsx +17 -0
  174. package/shell/header/anonymous-menu/AnonymousMenu.tsx +14 -0
  175. package/shell/header/anonymous-menu/LoginButton.tsx +14 -0
  176. package/shell/header/anonymous-menu/RegisterButton.tsx +15 -0
  177. package/shell/header/app.tsx +142 -0
  178. package/shell/header/desktop/DesktopLayout.tsx +22 -0
  179. package/shell/header/desktop/PrimaryNavLinks.tsx +10 -0
  180. package/shell/header/desktop/SecondaryNavLinks.tsx +10 -0
  181. package/shell/header/index.ts +2 -0
  182. package/shell/header/mobile/MobileLayout.tsx +47 -0
  183. package/shell/header/mobile/MobileNavLinks.tsx +10 -0
  184. package/shell/i18n/index.ts +25 -0
  185. package/shell/index.ts +7 -0
  186. package/shell/jest.config.js +30 -0
  187. package/shell/menus/LinkMenuItem.tsx +64 -0
  188. package/shell/menus/NavDropdownMenuSlot.tsx +29 -0
  189. package/shell/menus/ProfileLinkMenuItem.tsx +33 -0
  190. package/shell/menus/data/utils.ts +19 -0
  191. package/shell/public/index.html +10 -0
  192. package/shell/router/createRouter.ts +17 -0
  193. package/shell/router/getAppRoutes.ts +21 -0
  194. package/shell/setupTest.js +48 -0
  195. package/shell/site.config.dev.tsx +49 -0
  196. package/shell/site.tsx +41 -0
  197. package/test-site/app.d.ts +15 -0
  198. package/test-site/dist/176.436443549ebb858db483.js +2 -0
  199. package/test-site/dist/176.436443549ebb858db483.js.map +1 -0
  200. package/test-site/dist/362.536eff787d2380fe246c.js +2 -0
  201. package/test-site/dist/362.536eff787d2380fe246c.js.map +1 -0
  202. package/test-site/dist/653.486966b108d224551296.js +2 -0
  203. package/test-site/dist/653.486966b108d224551296.js.map +1 -0
  204. package/test-site/dist/74e025d3fe9a7b7f8503054e2563b353.jpg +0 -0
  205. package/test-site/dist/806.323cf6496ad0a7fe73a7.js +3 -0
  206. package/test-site/dist/806.323cf6496ad0a7fe73a7.js.LICENSE.txt +106 -0
  207. package/test-site/dist/806.323cf6496ad0a7fe73a7.js.map +1 -0
  208. package/test-site/dist/95ec738c0b7faac5b5c9126794446bbd.svg +4 -0
  209. package/test-site/dist/app.612058b36c74787759ac.css +61 -0
  210. package/test-site/dist/app.612058b36c74787759ac.css.map +1 -0
  211. package/test-site/dist/app.612058b36c74787759ac.js +2 -0
  212. package/test-site/dist/app.612058b36c74787759ac.js.map +1 -0
  213. package/test-site/dist/cb28cdb1468c915e27e5cec9af64f22f.svg +1 -0
  214. package/test-site/dist/index.html +1 -0
  215. package/test-site/dist/report.html +39 -0
  216. package/test-site/dist/runtime.c7aeaf7b967496cb076f.js +2 -0
  217. package/test-site/dist/runtime.c7aeaf7b967496cb076f.js.map +1 -0
  218. package/test-site/eslint.config.js +12 -0
  219. package/test-site/package-lock.json +19226 -0
  220. package/test-site/package.json +29 -0
  221. package/test-site/public/index.html +10 -0
  222. package/test-site/site.config.build.tsx +27 -0
  223. package/test-site/site.config.dev.tsx +27 -0
  224. package/test-site/src/authenticated-page/AuthenticatedPage.tsx +18 -0
  225. package/test-site/src/authenticated-page/i18n/index.ts +27 -0
  226. package/test-site/src/authenticated-page/index.tsx +28 -0
  227. package/test-site/src/example-page/ExamplePage.tsx +79 -0
  228. package/test-site/src/example-page/Image.tsx +11 -0
  229. package/test-site/src/example-page/ParagonPreview.jsx +66 -0
  230. package/test-site/src/example-page/apple.jpg +0 -0
  231. package/test-site/src/example-page/apple.svg +1 -0
  232. package/test-site/src/example-page/index.ts +16 -0
  233. package/test-site/src/i18n/README.md +3 -0
  234. package/test-site/src/i18n/messages/frontend-app-sample/ar.json +4 -0
  235. package/test-site/src/i18n/messages/frontend-app-sample/eo.json +1 -0
  236. package/test-site/src/i18n/messages/frontend-app-sample/es_419.json +4 -0
  237. package/test-site/src/i18n/messages/frontend-component-emptylangs/ar.json +1 -0
  238. package/test-site/src/i18n/messages/frontend-component-singlelang/ar.json +3 -0
  239. package/test-site/src/iframe-widget/IframeWidget.tsx +14 -0
  240. package/test-site/src/iframe-widget/index.ts +16 -0
  241. package/test-site/src/index.tsx +3 -0
  242. package/test-site/src/messages.js +11 -0
  243. package/test-site/src/site.scss +11 -0
  244. package/test-site/tsconfig.json +14 -0
  245. package/tools/babel/babel.config.js +27 -0
  246. package/tools/babel.config.js +3 -0
  247. package/tools/cli/README.md +29 -0
  248. package/tools/cli/commands/pack.ts +9 -0
  249. package/tools/cli/commands/release.ts +27 -0
  250. package/tools/cli/commands/serve.ts +43 -0
  251. package/tools/cli/intl-imports.ts +274 -0
  252. package/tools/cli/openedx.ts +101 -0
  253. package/tools/cli/transifex-utils.ts +75 -0
  254. package/tools/cli/utils/ensureConfigFilenameOption.ts +40 -0
  255. package/tools/cli/utils/formatter.ts +10 -0
  256. package/tools/cli/utils/getResolvedConfigPath.ts +23 -0
  257. package/tools/cli/utils/prettyPrintTitle.ts +15 -0
  258. package/tools/cli/utils/printUsage.ts +53 -0
  259. package/tools/config-helpers/createConfig.ts +8 -0
  260. package/tools/config-helpers/createLintConfig.ts +14 -0
  261. package/tools/config-helpers/getBaseConfig.ts +11 -0
  262. package/tools/defaultConfigPaths.ts +30 -0
  263. package/tools/dist/babel/babel.config.js +28 -0
  264. package/tools/dist/cli/commands/pack.js +14 -0
  265. package/tools/dist/cli/commands/release.js +28 -0
  266. package/tools/dist/cli/commands/serve.js +44 -0
  267. package/tools/dist/cli/intl-imports.js +233 -0
  268. package/tools/dist/cli/openedx.js +100 -0
  269. package/tools/dist/cli/transifex-utils.js +68 -0
  270. package/tools/dist/cli/utils/ensureConfigFilenameOption.js +42 -0
  271. package/tools/dist/cli/utils/formatter.js +10 -0
  272. package/tools/dist/cli/utils/getResolvedConfigPath.js +28 -0
  273. package/tools/dist/cli/utils/prettyPrintTitle.js +17 -0
  274. package/tools/dist/cli/utils/printUsage.js +48 -0
  275. package/tools/dist/config-helpers/createConfig.js +13 -0
  276. package/tools/dist/config-helpers/createLintConfig.js +16 -0
  277. package/tools/dist/config-helpers/getBaseConfig.js +12 -0
  278. package/tools/dist/defaultConfigPaths.js +35 -0
  279. package/tools/dist/eslint/base.eslint.config.js +113 -0
  280. package/tools/dist/eslint.config.js +11 -0
  281. package/tools/dist/index.js +12 -0
  282. package/tools/dist/jest/jest.config.js +30 -0
  283. package/tools/dist/jest.config.js +20 -0
  284. package/tools/dist/types.js +25 -0
  285. package/tools/dist/typescript/tsconfig.json +32 -0
  286. package/tools/dist/webpack/common-config/all/getCodeRules.js +52 -0
  287. package/tools/dist/webpack/common-config/all/getFileLoaderRules.js +26 -0
  288. package/tools/dist/webpack/common-config/all/getIgnoreWarnings.js +14 -0
  289. package/tools/dist/webpack/common-config/all/getImageMinimizer.js +25 -0
  290. package/tools/dist/webpack/common-config/all/getStylesheetRule.js +112 -0
  291. package/tools/dist/webpack/common-config/dev/getDevServer.js +38 -0
  292. package/tools/dist/webpack/common-config/index.js +18 -0
  293. package/tools/dist/webpack/common-config/site/getHtmlWebpackPlugin.js +16 -0
  294. package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.js +91 -0
  295. package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/index.js +7 -0
  296. package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +3 -0
  297. package/tools/dist/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +108 -0
  298. package/tools/dist/webpack/plugins/paragon-webpack-plugin/index.js +7 -0
  299. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +64 -0
  300. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +53 -0
  301. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/index.js +9 -0
  302. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +114 -0
  303. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +146 -0
  304. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +126 -0
  305. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +57 -0
  306. package/tools/dist/webpack/types.js +2 -0
  307. package/tools/dist/webpack/utils/getLocalAliases.js +65 -0
  308. package/tools/dist/webpack/utils/getPublicPath.js +6 -0
  309. package/tools/dist/webpack/utils/getResolvedSiteConfigPath.js +32 -0
  310. package/tools/dist/webpack/utils/paragonUtils.js +138 -0
  311. package/tools/dist/webpack/webpack.config.build.js +80 -0
  312. package/tools/dist/webpack/webpack.config.dev.js +76 -0
  313. package/tools/dist/webpack/webpack.config.dev.shell.js +110 -0
  314. package/tools/eslint/base.eslint.config.js +124 -0
  315. package/tools/eslint/modules.d.ts +5 -0
  316. package/tools/eslint.config.js +15 -0
  317. package/tools/index.ts +3 -0
  318. package/tools/jest/jest.config.js +30 -0
  319. package/tools/jest.config.js +19 -0
  320. package/tools/tsconfig.json +24 -0
  321. package/tools/types.ts +21 -0
  322. package/tools/typescript/tsconfig.json +32 -0
  323. package/tools/webpack/common-config/README.md +15 -0
  324. package/tools/webpack/common-config/all/getCodeRules.ts +51 -0
  325. package/tools/webpack/common-config/all/getFileLoaderRules.ts +23 -0
  326. package/tools/webpack/common-config/all/getIgnoreWarnings.ts +13 -0
  327. package/tools/webpack/common-config/all/getImageMinimizer.ts +26 -0
  328. package/tools/webpack/common-config/all/getStylesheetRule.ts +111 -0
  329. package/tools/webpack/common-config/dev/getDevServer.ts +35 -0
  330. package/tools/webpack/common-config/index.ts +6 -0
  331. package/tools/webpack/common-config/site/getHtmlWebpackPlugin.ts +11 -0
  332. package/tools/webpack/modules.d.ts +6 -0
  333. package/tools/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.ts +102 -0
  334. package/tools/webpack/plugins/html-webpack-new-relic-plugin/LICENSE +21 -0
  335. package/tools/webpack/plugins/html-webpack-new-relic-plugin/README.md +7 -0
  336. package/tools/webpack/plugins/html-webpack-new-relic-plugin/index.js +3 -0
  337. package/tools/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +1 -0
  338. package/tools/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.ts +134 -0
  339. package/tools/webpack/plugins/paragon-webpack-plugin/index.ts +3 -0
  340. package/tools/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.ts +71 -0
  341. package/tools/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.ts +72 -0
  342. package/tools/webpack/plugins/paragon-webpack-plugin/utils/index.ts +6 -0
  343. package/tools/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.ts +131 -0
  344. package/tools/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.ts +144 -0
  345. package/tools/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.ts +106 -0
  346. package/tools/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.ts +54 -0
  347. package/tools/webpack/types.ts +69 -0
  348. package/tools/webpack/utils/getLocalAliases.ts +65 -0
  349. package/tools/webpack/utils/getPublicPath.ts +3 -0
  350. package/tools/webpack/utils/getResolvedSiteConfigPath.ts +28 -0
  351. package/tools/webpack/utils/paragonUtils.ts +152 -0
  352. package/tools/webpack/webpack.config.build.ts +93 -0
  353. package/tools/webpack/webpack.config.dev.shell.ts +122 -0
  354. package/tools/webpack/webpack.config.dev.ts +90 -0
  355. package/tsconfig.json +23 -0
  356. package/types.ts +99 -0
@@ -0,0 +1,73 @@
1
+ import { Slot, WidgetOperationTypes } from '../../runtime';
2
+ import { App } from '../../types';
3
+ import Logo from '../Logo';
4
+ import CopyrightNotice from './CopyrightNotice';
5
+ import LabeledLinkColumn from './LabeledLinkColumn';
6
+ import LanguageMenu from './LanguageMenu';
7
+
8
+ const app: App = {
9
+ appId: 'org.openedx.frontend.app.footer',
10
+ slots: [
11
+ // Center links
12
+ {
13
+ slotId: 'org.openedx.frontend.slot.footer.desktopCenterLinks.v1',
14
+ id: 'org.openedx.frontend.widget.footer.desktopCenterLink1.v1',
15
+ op: WidgetOperationTypes.APPEND,
16
+ element: (
17
+ <Slot id="org.openedx.frontend.slot.footer.desktopCenterLink1.v1" layout={LabeledLinkColumn} />
18
+ ),
19
+ },
20
+ {
21
+ slotId: 'org.openedx.frontend.slot.footer.desktopCenterLinks.v1',
22
+ id: 'org.openedx.frontend.widget.footer.desktopCenterLink2.v1',
23
+ op: WidgetOperationTypes.APPEND,
24
+ element: (
25
+ <Slot id="org.openedx.frontend.slot.footer.desktopCenterLink2.v1" layout={LabeledLinkColumn} />
26
+ ),
27
+ },
28
+ {
29
+ slotId: 'org.openedx.frontend.slot.footer.desktopCenterLinks.v1',
30
+ id: 'org.openedx.frontend.widget.footer.desktopCenterLink3.v1',
31
+ op: WidgetOperationTypes.APPEND,
32
+ element: (
33
+ <Slot id="org.openedx.frontend.slot.footer.desktopCenterLink3.v1" layout={LabeledLinkColumn} />
34
+ ),
35
+ },
36
+ {
37
+ slotId: 'org.openedx.frontend.slot.footer.desktopCenterLinks.v1',
38
+ id: 'org.openedx.frontend.widget.footer.desktopCenterLink4.v1',
39
+ op: WidgetOperationTypes.APPEND,
40
+ element: (
41
+ <Slot id="org.openedx.frontend.slot.footer.desktopCenterLink4.v1" layout={LabeledLinkColumn} />
42
+ ),
43
+ },
44
+
45
+ // Left Links
46
+ {
47
+ slotId: 'org.openedx.frontend.slot.footer.desktopLeftLinks.v1',
48
+ id: 'org.openedx.frontend.widget.footer.desktopLeftLinksLogo.v1',
49
+ op: WidgetOperationTypes.APPEND,
50
+ element: <Logo />,
51
+ },
52
+
53
+ // Right Links
54
+ {
55
+ slotId: 'org.openedx.frontend.slot.footer.desktopRightLinks.v1',
56
+ id: 'org.openedx.frontend.widget.footer.desktopRightLinksLanguageMenu.v1',
57
+ op: WidgetOperationTypes.APPEND,
58
+ component: LanguageMenu,
59
+ },
60
+
61
+ // Copyright Notice
62
+ {
63
+ slotId: 'org.openedx.frontend.slot.footer.desktopLegalNotices.v1',
64
+ id: 'org.openedx.frontend.widget.footer.desktopCopyrightNotice.v1',
65
+ op: WidgetOperationTypes.APPEND,
66
+ element: (
67
+ <CopyrightNotice />
68
+ ),
69
+ }
70
+ ]
71
+ };
72
+
73
+ export default app;
@@ -0,0 +1,48 @@
1
+ import {
2
+ getAuthenticatedHttpClient,
3
+ getAuthenticatedUser,
4
+ getSiteConfig,
5
+ updateLocale
6
+ } from '../../../runtime';
7
+
8
+ export async function updateSiteLanguage(locale: string) {
9
+ const user = getAuthenticatedUser();
10
+
11
+ if (user !== null) {
12
+ const { username } = getAuthenticatedUser();
13
+ await patchUserPreferences(username, locale);
14
+ }
15
+ await postSetlang(locale);
16
+
17
+ updateLocale();
18
+ }
19
+
20
+ async function patchUserPreferences(username: string, locale: string) {
21
+ await getAuthenticatedHttpClient().patch(
22
+ `${getSiteConfig().lmsBaseUrl}/api/user/v1/preferences/${username}`,
23
+ {
24
+ 'pref-lang': locale
25
+ },
26
+ {
27
+ headers: {
28
+ 'Content-Type': 'application/merge-patch+json'
29
+ },
30
+ }
31
+ );
32
+ }
33
+
34
+ async function postSetlang(locale: string) {
35
+ const formData = new FormData();
36
+ formData.append('language', locale);
37
+
38
+ await getAuthenticatedHttpClient().post(
39
+ `${getSiteConfig().lmsBaseUrl}/i18n/setlang/`,
40
+ formData,
41
+ {
42
+ headers: {
43
+ Accept: 'application/json',
44
+ 'X-Requested-With': 'XMLHttpRequest',
45
+ },
46
+ }
47
+ );
48
+ }
@@ -0,0 +1,2 @@
1
+ export { default as footerApp } from './app';
2
+ export { default as Footer } from './Footer';
@@ -0,0 +1,32 @@
1
+ import { DropdownButton } from '@openedx/paragon';
2
+ import { Person } from '@openedx/paragon/icons';
3
+
4
+ import {
5
+ Slot,
6
+ useAuthenticatedUser
7
+ } from '../../runtime';
8
+
9
+ interface AuthenticatedMenuProps {
10
+ className?: string,
11
+ }
12
+
13
+ export default function AuthenticatedMenu({ className }: AuthenticatedMenuProps) {
14
+ const authenticatedUser = useAuthenticatedUser();
15
+
16
+ // We're using '||' on purpose to detect an empty string, so ignore eslint's warning:
17
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
18
+ const displayUserName = authenticatedUser?.name || authenticatedUser?.username;
19
+
20
+ const title = (
21
+ <div className="d-flex mr-2 align-items-center gap-2">
22
+ <Person />
23
+ {displayUserName}
24
+ </div>
25
+ );
26
+
27
+ return (
28
+ <DropdownButton size="sm" id="user-nav-dropdown" title={title} variant="outline-primary" className={className}>
29
+ <Slot id="org.openedx.frontend.slot.header.authenticatedMenu.v1" />
30
+ </DropdownButton>
31
+ );
32
+ }
@@ -0,0 +1,17 @@
1
+ import { Slot, useIntl } from '../../runtime';
2
+
3
+ import messages from '../Shell.messages';
4
+
5
+ export default function Header() {
6
+ const intl = useIntl();
7
+
8
+ return (
9
+ <header className="border-bottom py-2">
10
+ <nav className="py-2">
11
+ <a className="sr-only sr-only-focusable" href="#main-content">{intl.formatMessage(messages.skipNavLink)}</a>
12
+ <Slot id="org.openedx.frontend.slot.header.desktop.v1" />
13
+ <Slot id="org.openedx.frontend.slot.header.mobile.v1" />
14
+ </nav>
15
+ </header>
16
+ );
17
+ }
@@ -0,0 +1,14 @@
1
+ import classNames from 'classnames';
2
+ import { Slot } from '../../../runtime';
3
+
4
+ interface AnonymousMenuProps {
5
+ className?: string,
6
+ }
7
+
8
+ export default function AnonymousMenu({ className }: AnonymousMenuProps) {
9
+ return (
10
+ <div className={classNames('d-flex flex-nowrap align-items-center flex-shrink-0 gap-3', className)}>
11
+ <Slot id="org.openedx.frontend.slot.header.anonymousMenu.v1" />
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1,14 @@
1
+ import { Button } from '@openedx/paragon';
2
+ import { useSiteConfig, useIntl } from '../../../runtime';
3
+ import messages from '../../Shell.messages';
4
+
5
+ export default function LoginButton({ ...props }) {
6
+ const config = useSiteConfig();
7
+ const intl = useIntl();
8
+
9
+ return (
10
+ <Button size="sm" href={config.loginUrl} {...props}>
11
+ {intl.formatMessage(messages['header.user.menu.login'])}
12
+ </Button>
13
+ );
14
+ }
@@ -0,0 +1,15 @@
1
+ import { Button } from '@openedx/paragon';
2
+
3
+ import { useSiteConfig, useIntl } from '../../../runtime';
4
+ import messages from '../../Shell.messages';
5
+
6
+ export default function RegisterButton({ ...props }) {
7
+ const config = useSiteConfig();
8
+ const intl = useIntl();
9
+
10
+ return (
11
+ <Button size="sm" variant="outline-primary" href={`${config.lmsBaseUrl}/register`} {...props}>
12
+ {intl.formatMessage(messages['header.user.menu.register'])}
13
+ </Button>
14
+ );
15
+ }
@@ -0,0 +1,142 @@
1
+ import { WidgetOperationTypes } from '../../runtime';
2
+ import { App } from '../../types';
3
+ import Logo from '../Logo';
4
+ import LinkMenuItem from '../menus/LinkMenuItem';
5
+ import ProfileLinkMenuItem from '../menus/ProfileLinkMenuItem';
6
+ import AnonymousMenu from './anonymous-menu/AnonymousMenu';
7
+ import AuthenticatedMenu from './AuthenticatedMenu';
8
+ import DesktopLayout from './desktop/DesktopLayout';
9
+ import PrimaryNavLinks from './desktop/PrimaryNavLinks';
10
+ import SecondaryNavLinks from './desktop/SecondaryNavLinks';
11
+ import MobileLayout from './mobile/MobileLayout';
12
+ import MobileNavLinks from './mobile/MobileNavLinks';
13
+
14
+ import messages from '../Shell.messages';
15
+
16
+ const config: App = {
17
+ appId: 'org.openedx.frontend.app.header',
18
+ slots: [
19
+
20
+ // Layouts
21
+ {
22
+ slotId: 'org.openedx.frontend.slot.header.desktop.v1',
23
+ id: 'org.openedx.frontend.widget.header.desktopLayout.v1',
24
+ op: WidgetOperationTypes.APPEND,
25
+ component: DesktopLayout
26
+ },
27
+ {
28
+ slotId: 'org.openedx.frontend.slot.header.mobile.v1',
29
+ id: 'org.openedx.frontend.widget.header.mobileLayout.v1',
30
+ op: WidgetOperationTypes.APPEND,
31
+ component: MobileLayout
32
+ },
33
+
34
+ // Desktop
35
+ {
36
+ slotId: 'org.openedx.frontend.slot.header.desktopLeft.v1',
37
+ id: 'org.openedx.frontend.widget.header.desktopLogo.v1',
38
+ op: WidgetOperationTypes.APPEND,
39
+ element: <Logo />,
40
+ },
41
+ {
42
+ slotId: 'org.openedx.frontend.slot.header.desktopLeft.v1',
43
+ id: 'org.openedx.frontend.widget.header.desktopPrimaryLinks.v1',
44
+ op: WidgetOperationTypes.APPEND,
45
+ component: PrimaryNavLinks
46
+ },
47
+ {
48
+ slotId: 'org.openedx.frontend.slot.header.desktopRight.v1',
49
+ id: 'org.openedx.frontend.widget.header.desktopSecondaryLinks.v1',
50
+ op: WidgetOperationTypes.APPEND,
51
+ component: SecondaryNavLinks
52
+ },
53
+ {
54
+ slotId: 'org.openedx.frontend.slot.header.desktopRight.v1',
55
+ id: 'org.openedx.frontend.widget.header.desktopAuthenticatedMenu.v1',
56
+ op: WidgetOperationTypes.APPEND,
57
+ element: <AuthenticatedMenu />,
58
+ condition: {
59
+ authenticated: true,
60
+ }
61
+ },
62
+ {
63
+ slotId: 'org.openedx.frontend.slot.header.authenticatedMenu.v1',
64
+ id: 'org.openedx.frontend.widget.header.desktopAuthenticatedMenuProfile.v1',
65
+ op: WidgetOperationTypes.APPEND,
66
+ element: (
67
+ <ProfileLinkMenuItem
68
+ label={messages['header.user.menu.profile']}
69
+ role="profile"
70
+ variant="dropdownItem"
71
+ />
72
+ )
73
+ },
74
+ {
75
+ slotId: 'org.openedx.frontend.slot.header.authenticatedMenu.v1',
76
+ id: 'org.openedx.frontend.widget.header.desktopAuthenticatedMenuAccount.v1',
77
+ op: WidgetOperationTypes.APPEND,
78
+ element: (
79
+ <LinkMenuItem
80
+ label={messages['header.user.menu.account']}
81
+ role="account"
82
+ variant="dropdownItem"
83
+ />
84
+ )
85
+ },
86
+ {
87
+ slotId: 'org.openedx.frontend.slot.header.authenticatedMenu.v1',
88
+ id: 'org.openedx.frontend.widget.header.desktopAuthenticatedMenuLogout.v1',
89
+ op: WidgetOperationTypes.APPEND,
90
+ element: (
91
+ <LinkMenuItem
92
+ label={messages['header.user.menu.logout']}
93
+ role="logout"
94
+ variant="dropdownItem"
95
+ />
96
+ )
97
+ },
98
+ {
99
+ slotId: 'org.openedx.frontend.slot.header.desktopRight.v1',
100
+ id: 'org.openedx.frontend.widget.header.desktopAnonymousMenu.v1',
101
+ op: WidgetOperationTypes.APPEND,
102
+ element: <AnonymousMenu />,
103
+ condition: {
104
+ authenticated: false,
105
+ }
106
+ },
107
+
108
+ // Mobile
109
+ {
110
+ slotId: 'org.openedx.frontend.slot.header.mobileCenter.v1',
111
+ id: 'org.openedx.frontend.widget.header.mobileLogo.v1',
112
+ op: WidgetOperationTypes.APPEND,
113
+ element: <Logo />,
114
+ },
115
+ {
116
+ slotId: 'org.openedx.frontend.slot.header.mobileMenu.v1',
117
+ id: 'org.openedx.frontend.widget.header.mobileMenuLinks.v1',
118
+ op: WidgetOperationTypes.APPEND,
119
+ component: MobileNavLinks
120
+ },
121
+ {
122
+ slotId: 'org.openedx.frontend.slot.header.mobileRight.v1',
123
+ id: 'org.openedx.frontend.widget.header.mobileAuthenticatedMenu.v1',
124
+ op: WidgetOperationTypes.APPEND,
125
+ element: <AuthenticatedMenu />,
126
+ condition: {
127
+ authenticated: true,
128
+ }
129
+ },
130
+ {
131
+ slotId: 'org.openedx.frontend.slot.header.mobileRight.v1',
132
+ id: 'org.openedx.frontend.widget.header.mobileAnonymousMenu.v1',
133
+ op: WidgetOperationTypes.APPEND,
134
+ element: <AnonymousMenu />,
135
+ condition: {
136
+ authenticated: false,
137
+ }
138
+ },
139
+ ]
140
+ };
141
+
142
+ export default config;
@@ -0,0 +1,22 @@
1
+ import classNames from 'classnames';
2
+ import { useMediaQuery } from 'react-responsive';
3
+ import { Slot } from '../../../runtime';
4
+
5
+ export default function DesktopLayout() {
6
+ const isMobile = useMediaQuery({ maxWidth: 768 });
7
+
8
+ return (
9
+ <div className={classNames(
10
+ 'align-items-center justify-content-between px-3',
11
+ isMobile ? 'd-none' : 'd-flex'
12
+ )}
13
+ >
14
+ <div className="d-flex flex-grow-1 align-items-center">
15
+ <Slot id="org.openedx.frontend.slot.header.desktopLeft.v1" />
16
+ </div>
17
+ <div className="d-flex align-items-center">
18
+ <Slot id="org.openedx.frontend.slot.header.desktopRight.v1" />
19
+ </div>
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,10 @@
1
+ import { Nav } from '@openedx/paragon';
2
+ import { Slot } from '../../../runtime';
3
+
4
+ export default function PrimaryNavLinks() {
5
+ return (
6
+ <Nav className="flex-nowrap align-items-center">
7
+ <Slot id="org.openedx.frontend.slot.header.primaryLinks.v1" />
8
+ </Nav>
9
+ );
10
+ }
@@ -0,0 +1,10 @@
1
+ import { Nav } from '@openedx/paragon';
2
+ import { Slot } from '../../../runtime';
3
+
4
+ export default function SecondaryNavLinks() {
5
+ return (
6
+ <Nav className="flex-nowrap">
7
+ <Slot id="org.openedx.frontend.slot.header.secondaryLinks.v1" />
8
+ </Nav>
9
+ );
10
+ }
@@ -0,0 +1,2 @@
1
+ export { default as headerApp } from './app';
2
+ export { default as Header } from './Header';
@@ -0,0 +1,47 @@
1
+ import { Button, Nav } from '@openedx/paragon';
2
+ import { MenuIcon } from '@openedx/paragon/icons';
3
+ import classNames from 'classnames';
4
+ import { useCallback, useState } from 'react';
5
+ import { FocusOn } from 'react-focus-on';
6
+ import { useMediaQuery } from 'react-responsive';
7
+ import { Slot } from '../../../runtime';
8
+
9
+ export default function MobileLayout() {
10
+ const isMobile = useMediaQuery({ maxWidth: 768 });
11
+ const [mobileOpen, setMobileOpen] = useState<boolean>(false);
12
+
13
+ const handleMobileButtonClick = useCallback(() => {
14
+ setMobileOpen((value) => !value);
15
+ }, []);
16
+
17
+ return (
18
+ <>
19
+ <div
20
+ className={classNames(
21
+ 'align-items-center justify-content-between pr-3 booyah',
22
+ isMobile ? 'd-flex' : 'd-none',
23
+ )}
24
+ >
25
+ <div className="d-flex flex-grow-1 flex-basis-0 justify-content-start align-items-center">
26
+ <Button onClick={handleMobileButtonClick} variant="outline">
27
+ <MenuIcon />
28
+ </Button>
29
+ <Slot id="org.openedx.frontend.slot.header.mobileLeft.v1" />
30
+ </div>
31
+ <div className="d-flex flex-grow-1 flex-basis-0 justify-content-center align-items-center">
32
+ <Slot id="org.openedx.frontend.slot.header.mobileCenter.v1" />
33
+ </div>
34
+ <div className="d-flex flex-grow-1 flex-basis-0 justify-content-end align-items-center">
35
+ <Slot id="org.openedx.frontend.slot.header.mobileRight.v1" />
36
+ </div>
37
+ </div>
38
+ {mobileOpen && (
39
+ <FocusOn onClickOutside={() => setMobileOpen(false)} onEscapeKey={() => setMobileOpen(false)}>
40
+ <Nav className="flex-column">
41
+ <Slot id="org.openedx.frontend.slot.header.mobileMenuLinks.v1" />
42
+ </Nav>
43
+ </FocusOn>
44
+ )}
45
+ </>
46
+ );
47
+ }
@@ -0,0 +1,10 @@
1
+ import { Nav } from '@openedx/paragon';
2
+ import { Slot } from '../../../runtime';
3
+
4
+ export default function MobileNavLinks() {
5
+ return (
6
+ <Nav className="flex-column">
7
+ <Slot id="org.openedx.frontend.slot.header.mobileMenuLinks.v1" />
8
+ </Nav>
9
+ );
10
+ }
@@ -0,0 +1,25 @@
1
+ // Placeholder be overridden by `make pull_translations`
2
+ export default {
3
+ ar: {},
4
+ 'zh-hk': {},
5
+ 'zh-cn': {},
6
+ uk: {},
7
+ 'tr-tr': {},
8
+ th: {},
9
+ te: {},
10
+ ru: {},
11
+ 'pt-pt': {},
12
+ 'pt-br': {},
13
+ 'it-it': {},
14
+ id: {},
15
+ hi: {},
16
+ he: {},
17
+ 'fr-ca': {},
18
+ fa: {},
19
+ 'es-es': {},
20
+ 'es-419': {},
21
+ el: {},
22
+ 'de-de': {},
23
+ da: {},
24
+ bo: {},
25
+ };
package/shell/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { default as DefaultLayout } from './DefaultLayout';
2
+ export { default as DefaultMain } from './DefaultMain';
3
+ export { default as shellApp } from './app';
4
+ export { Footer, footerApp } from './footer';
5
+ export { Header, headerApp } from './header';
6
+ export { default as LinkMenuItem } from './menus/LinkMenuItem';
7
+ export { default as NavDropdownMenuSlot } from './menus/NavDropdownMenuSlot';
@@ -0,0 +1,30 @@
1
+ module.exports = {
2
+ setupFilesAfterEnv: [
3
+ './setupTest.js',
4
+ ],
5
+ moduleNameMapper: {
6
+ '\\.svg$': '<rootDir>/__mocks__/svg.js',
7
+ '\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/__mocks__/file.js',
8
+ '\\.(css|scss)$': require.resolve('identity-obj-proxy'),
9
+ 'site.config': '<rootDir>/site.config.test.tsx',
10
+ },
11
+ testEnvironment: 'jsdom',
12
+ testEnvironmentOptions: {
13
+ url: 'http://localhost/',
14
+ },
15
+ collectCoverageFrom: [
16
+ '<rootDir>/**/*.{js,jsx,ts,tsx}',
17
+ ],
18
+ coveragePathIgnorePatterns: [
19
+ '/node_modules/',
20
+ 'setupTest.js',
21
+ ],
22
+ transformIgnorePatterns: [
23
+ '/node_modules/(?!(@openedx|@edx)/)',
24
+ ],
25
+ testPathIgnorePatterns: [
26
+ '/site.config.test.tsx',
27
+ '/node_modules/',
28
+ '/dist/',
29
+ ],
30
+ };
@@ -0,0 +1,64 @@
1
+ import { Dropdown, Hyperlink, NavDropdown, NavLink } from '@openedx/paragon';
2
+ import { useIntl } from 'react-intl';
3
+
4
+ import { getUrlByRouteRole } from '../../runtime/routing';
5
+ import {
6
+ MenuItemName
7
+ } from '../../types';
8
+ import {
9
+ getItemLabel
10
+ } from './data/utils';
11
+
12
+ interface LinkMenuItemProps {
13
+ label: MenuItemName,
14
+ role?: string,
15
+ url?: string,
16
+ variant?: 'hyperlink' | 'navLink' | 'navDropdownItem' | 'dropdownItem',
17
+ }
18
+
19
+ export default function LinkMenuItem({ label, role, url, variant = 'hyperlink' }: LinkMenuItemProps) {
20
+ const intl = useIntl();
21
+ const finalLabel = getItemLabel(label, intl);
22
+
23
+ let finalUrl: string | null | undefined;
24
+ if (role !== undefined) {
25
+ finalUrl = getUrlByRouteRole(role);
26
+ } else if (url !== undefined) {
27
+ finalUrl = url;
28
+ }
29
+
30
+ // The URL will only be null if the item is an "app" menu item, and if the app is not loaded.
31
+ // We automatically hide the link if this is the case.
32
+ if (!finalUrl) {
33
+ return null;
34
+ }
35
+
36
+ if (variant === 'hyperlink') {
37
+ return (
38
+ <Hyperlink destination={finalUrl}>
39
+ {finalLabel}
40
+ </Hyperlink>
41
+ );
42
+ } else if (variant === 'navLink') {
43
+ return (
44
+ <NavLink href={finalUrl}>
45
+ {finalLabel}
46
+ </NavLink>
47
+ );
48
+ } else if (variant === 'navDropdownItem') {
49
+ return (
50
+ <NavDropdown.Item href={finalUrl}>
51
+ {finalLabel}
52
+ </NavDropdown.Item>
53
+ );
54
+ } else if (variant === 'dropdownItem') {
55
+ return (
56
+ <Dropdown.Item href={finalUrl}>
57
+ {finalLabel}
58
+ </Dropdown.Item>
59
+ );
60
+ }
61
+
62
+ // Just return null if the item is something we haven't accounted for above.
63
+ return null;
64
+ }
@@ -0,0 +1,29 @@
1
+ import { NavDropdown } from '@openedx/paragon';
2
+ import { useIntl } from 'react-intl';
3
+
4
+ import { useLayoutOptionsForId, useWidgetsForId } from '../../runtime';
5
+ import {
6
+ MenuItemName
7
+ } from '../../types';
8
+ import {
9
+ getItemLabel
10
+ } from './data/utils';
11
+
12
+ interface DropdownMenuSlotProps {
13
+ id: string,
14
+ label: MenuItemName,
15
+ }
16
+
17
+ export default function DropdownMenuSlot({ id, label }: DropdownMenuSlotProps) {
18
+ const intl = useIntl();
19
+ const options = useLayoutOptionsForId(id);
20
+ const widgets = useWidgetsForId(id);
21
+
22
+ const finalLabel = getItemLabel(options.label ?? label, intl);
23
+
24
+ return (
25
+ <NavDropdown key={id} title={finalLabel} id={`${id}-dropdown`}>
26
+ {widgets}
27
+ </NavDropdown>
28
+ );
29
+ }
@@ -0,0 +1,33 @@
1
+ import { useAuthenticatedUser } from '../../runtime';
2
+ import { getUrlByRouteRole } from '../../runtime/routing';
3
+ import { MenuItemName } from '../../types';
4
+
5
+ import LinkMenuItem from './LinkMenuItem';
6
+
7
+ interface ProfileLinkMenuItemProps {
8
+ label: MenuItemName,
9
+ role: string,
10
+ variant?: 'hyperlink' | 'navLink' | 'navDropdownItem' | 'dropdownItem',
11
+ }
12
+
13
+ export default function ProfileLinkMenuItem({ label, role, variant = 'hyperlink' }: ProfileLinkMenuItemProps) {
14
+ const authenticatedUser = useAuthenticatedUser();
15
+ let baseUrl = getUrlByRouteRole(role);
16
+
17
+ if (!baseUrl)
18
+ return null;
19
+
20
+ if (baseUrl.endsWith('/')) {
21
+ baseUrl = baseUrl.slice(0, -1);
22
+ }
23
+
24
+ const url = `${baseUrl}/u/${authenticatedUser?.username}`;
25
+
26
+ return (
27
+ <LinkMenuItem
28
+ label={label}
29
+ url={url}
30
+ variant={variant}
31
+ />
32
+ );
33
+ }