new-project-button.tsx 8.8 KB

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