new-project-button.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import { type JSXElementConstructor, useCallback, useState } from 'react'
  2. import classnames from 'classnames'
  3. import { useTranslation } from 'react-i18next'
  4. import getMeta from '../../../utils/meta'
  5. import NewProjectButtonModal, {
  6. NewProjectButtonModalVariant,
  7. } from './new-project-button/new-project-button-modal'
  8. import AddAffiliation, { useAddAffiliation } from './add-affiliation'
  9. import { Nullable } from '../../../../../types/utils'
  10. import { sendMB } from '../../../infrastructure/event-tracking'
  11. import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
  12. import {
  13. Dropdown,
  14. DropdownDivider,
  15. DropdownHeader,
  16. DropdownItem,
  17. DropdownMenu,
  18. DropdownToggle,
  19. } from '@/features/ui/components/bootstrap-5/dropdown-menu'
  20. import { useSendProjectListMB } from '@/features/project-list/components/project-list-events'
  21. import type { PortalTemplate } from '../../../../../types/portal-template'
  22. type SendTrackingEvent = {
  23. dropdownMenu: string
  24. dropdownOpen: boolean
  25. institutionTemplateName?: string
  26. }
  27. type Segmentation = SendTrackingEvent & {
  28. 'welcome-page-redesign': 'default'
  29. }
  30. type ModalMenuClickOptions = {
  31. modalVariant: NewProjectButtonModalVariant
  32. dropdownMenuEvent: string
  33. }
  34. type NewProjectButtonProps = {
  35. id: string
  36. buttonText?: string
  37. className?: string
  38. trackingKey?: string
  39. showAddAffiliationWidget?: boolean
  40. }
  41. function NewProjectButton({
  42. id,
  43. buttonText,
  44. className,
  45. trackingKey,
  46. showAddAffiliationWidget,
  47. }: NewProjectButtonProps) {
  48. const { t } = useTranslation()
  49. const { templateLinks } = getMeta('ol-ExposedSettings')
  50. const [modal, setModal] =
  51. useState<Nullable<NewProjectButtonModalVariant>>(null)
  52. const portalTemplates = getMeta('ol-portalTemplates') || []
  53. const { show: enableAddAffiliationWidget } = useAddAffiliation()
  54. const sendProjectListMB = useSendProjectListMB()
  55. const sendTrackingEvent = useCallback(
  56. ({
  57. dropdownMenu,
  58. dropdownOpen,
  59. institutionTemplateName,
  60. }: SendTrackingEvent) => {
  61. if (trackingKey) {
  62. let segmentation: Segmentation = {
  63. 'welcome-page-redesign': 'default',
  64. dropdownMenu,
  65. dropdownOpen,
  66. }
  67. if (institutionTemplateName) {
  68. segmentation = {
  69. ...segmentation,
  70. institutionTemplateName,
  71. }
  72. }
  73. sendMB(trackingKey, segmentation)
  74. }
  75. },
  76. [trackingKey]
  77. )
  78. const handleMainButtonClick = useCallback(
  79. (dropdownOpen: boolean) => {
  80. sendTrackingEvent({
  81. dropdownMenu: 'main-button',
  82. dropdownOpen,
  83. })
  84. },
  85. [sendTrackingEvent]
  86. )
  87. const handleModalMenuClick = useCallback(
  88. (
  89. e: React.MouseEvent,
  90. { modalVariant, dropdownMenuEvent }: ModalMenuClickOptions
  91. ) => {
  92. // avoid invoking the "onClick" callback on the main dropdown button
  93. e.stopPropagation()
  94. sendTrackingEvent({
  95. dropdownMenu: dropdownMenuEvent,
  96. dropdownOpen: true,
  97. })
  98. sendProjectListMB('new-project-click', { item: dropdownMenuEvent })
  99. setModal(modalVariant)
  100. },
  101. [sendProjectListMB, sendTrackingEvent]
  102. )
  103. const handlePortalTemplateClick = useCallback(
  104. (e: React.MouseEvent, template: PortalTemplate) => {
  105. // avoid invoking the "onClick" callback on the main dropdown button
  106. e.stopPropagation()
  107. sendTrackingEvent({
  108. dropdownMenu: 'institution-template',
  109. dropdownOpen: true,
  110. institutionTemplateName: template.name,
  111. })
  112. sendProjectListMB('new-project-click', {
  113. item: template.name,
  114. destinationURL: template.url,
  115. })
  116. },
  117. [sendProjectListMB, sendTrackingEvent]
  118. )
  119. const handleStaticTemplateClick = useCallback(
  120. (e: React.MouseEvent, template: { trackingKey: string; url: string }) => {
  121. // avoid invoking the "onClick" callback on the main dropdown button
  122. e.stopPropagation()
  123. sendTrackingEvent({
  124. dropdownMenu: template.trackingKey,
  125. dropdownOpen: true,
  126. })
  127. sendProjectListMB('new-project-click', {
  128. item: template.trackingKey,
  129. destinationURL: template.url,
  130. })
  131. },
  132. [sendProjectListMB, sendTrackingEvent]
  133. )
  134. const [importProjectFromGithubMenu] = importOverleafModules(
  135. 'importProjectFromGithubMenu'
  136. )
  137. const ImportProjectFromGithubMenu: JSXElementConstructor<{
  138. onClick: (e: React.MouseEvent) => void
  139. }> = importProjectFromGithubMenu?.import.default
  140. return (
  141. <>
  142. <Dropdown
  143. className={classnames('new-project-dropdown', className)}
  144. onSelect={handleMainButtonClick}
  145. onToggle={nextShow => {
  146. if (nextShow) sendProjectListMB('new-project-expand', undefined)
  147. }}
  148. >
  149. <DropdownToggle
  150. id={id}
  151. className="new-project-button"
  152. variant="primary"
  153. >
  154. {buttonText || t('new_project')}
  155. </DropdownToggle>
  156. <DropdownMenu>
  157. <li role="none">
  158. <DropdownItem
  159. onClick={e =>
  160. handleModalMenuClick(e, {
  161. modalVariant: 'blank_project',
  162. dropdownMenuEvent: 'blank-project',
  163. })
  164. }
  165. >
  166. {t('blank_project')}
  167. </DropdownItem>
  168. </li>
  169. <li role="none">
  170. <DropdownItem
  171. onClick={e =>
  172. handleModalMenuClick(e, {
  173. modalVariant: 'example_project',
  174. dropdownMenuEvent: 'example-project',
  175. })
  176. }
  177. >
  178. {t('example_project')}
  179. </DropdownItem>
  180. </li>
  181. <li role="none">
  182. <DropdownItem
  183. onClick={e =>
  184. handleModalMenuClick(e, {
  185. modalVariant: 'upload_project',
  186. dropdownMenuEvent: 'upload-project',
  187. })
  188. }
  189. >
  190. {t('upload_project')}
  191. </DropdownItem>
  192. </li>
  193. <li role="none">
  194. {ImportProjectFromGithubMenu && (
  195. <ImportProjectFromGithubMenu
  196. onClick={e =>
  197. handleModalMenuClick(e, {
  198. modalVariant: 'import_from_github',
  199. dropdownMenuEvent: 'import-from-github',
  200. })
  201. }
  202. />
  203. )}
  204. </li>
  205. {portalTemplates.length > 0 ? (
  206. <>
  207. <DropdownDivider />
  208. <DropdownHeader aria-hidden="true">
  209. {`${t('institution')} ${t('templates')}`}
  210. </DropdownHeader>
  211. {portalTemplates.map((portalTemplate, index) => (
  212. <li role="none" key={`portal-template-${index}`}>
  213. <DropdownItem
  214. key={`portal-template-${index}`}
  215. href={`${portalTemplate.url}#templates`}
  216. onClick={e => handlePortalTemplateClick(e, portalTemplate)}
  217. aria-label={`${portalTemplate.name} ${t('template')}`}
  218. >
  219. {portalTemplate.name}
  220. </DropdownItem>
  221. </li>
  222. ))}
  223. </>
  224. ) : null}
  225. {templateLinks && templateLinks.length > 0 && (
  226. <>
  227. <DropdownDivider />
  228. <DropdownHeader aria-hidden="true">
  229. {t('templates')}
  230. </DropdownHeader>
  231. </>
  232. )}
  233. {templateLinks?.map((templateLink, index) => (
  234. <li role="none" key={`new-project-button-template-${index}`}>
  235. <DropdownItem
  236. href={templateLink.url}
  237. onClick={e => handleStaticTemplateClick(e, templateLink)}
  238. aria-label={`${templateLink.name} ${t('template')}`}
  239. >
  240. {templateLink.name === 'view_all'
  241. ? t('view_all')
  242. : templateLink.name}
  243. </DropdownItem>
  244. </li>
  245. ))}
  246. {showAddAffiliationWidget && enableAddAffiliationWidget ? (
  247. <>
  248. <DropdownDivider />
  249. <li className="add-affiliation-mobile-wrapper">
  250. <AddAffiliation className="is-mobile" />
  251. </li>
  252. </>
  253. ) : null}
  254. </DropdownMenu>
  255. </Dropdown>
  256. <NewProjectButtonModal modal={modal} onHide={() => setModal(null)} />
  257. </>
  258. )
  259. }
  260. export default NewProjectButton