new-project-button.tsx 7.2 KB

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