new-project-button.tsx 7.2 KB

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