default-navbar.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import React, { useState } from 'react'
  2. import { sendMB } from '@/infrastructure/event-tracking'
  3. import { useTranslation } from 'react-i18next'
  4. import { Button, Container, Nav, Navbar } from 'react-bootstrap'
  5. import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n'
  6. import AdminMenu from '@/features/ui/components/bootstrap-5/navbar/admin-menu'
  7. import type { DefaultNavbarMetadata } from '@/features/ui/components/types/default-navbar-metadata'
  8. import NavItemFromData from '@/features/ui/components/bootstrap-5/navbar/nav-item-from-data'
  9. import LoggedInItems from '@/features/ui/components/bootstrap-5/navbar/logged-in-items'
  10. import LoggedOutItems from '@/features/ui/components/bootstrap-5/navbar/logged-out-items'
  11. import HeaderLogoOrTitle from '@/features/ui/components/bootstrap-5/navbar/header-logo-or-title'
  12. import MaterialIcon from '@/shared/components/material-icon'
  13. import { useContactUsModal } from '@/shared/hooks/use-contact-us-modal'
  14. import { UserProvider } from '@/shared/context/user-context'
  15. import { X } from '@phosphor-icons/react'
  16. import overleafWhiteLogo from '@/shared/svgs/overleaf-white.svg'
  17. import overleafBlackLogo from '@/shared/svgs/overleaf-black.svg'
  18. import type { CSSPropertiesWithVariables } from '../../../../../../../types/css-properties-with-variables'
  19. function DefaultNavbar(
  20. props: DefaultNavbarMetadata & { overleafLogo?: string }
  21. ) {
  22. const {
  23. overleafLogo,
  24. customLogo,
  25. title,
  26. canDisplayAdminMenu,
  27. canDisplayAdminRedirect,
  28. canDisplaySplitTestMenu,
  29. canDisplaySurveyMenu,
  30. canDisplayScriptLogMenu,
  31. enableUpgradeButton,
  32. suppressNavbarRight,
  33. suppressNavContentLinks,
  34. showCloseIcon = false,
  35. showSubscriptionLink,
  36. showSignUpLink,
  37. sessionUser,
  38. adminUrl,
  39. items,
  40. } = props
  41. const { t } = useTranslation()
  42. const [expanded, setExpanded] = useState(false)
  43. // The Contact us modal is rendered at this level rather than inside the nav
  44. // bar because otherwise the help wiki search results dropdown doesn't show up
  45. const { modal: contactUsModal, showModal: showContactUsModal } =
  46. useContactUsModal({
  47. autofillProjectUrl: false,
  48. })
  49. return (
  50. <>
  51. <Navbar
  52. className="navbar-default navbar-main"
  53. expand="lg"
  54. onToggle={expanded => setExpanded(expanded)}
  55. style={
  56. {
  57. '--navbar-brand-image-default-url': `url("${overleafWhiteLogo}")`,
  58. '--navbar-brand-image-redesign-url': `url("${overleafBlackLogo}")`,
  59. } as CSSPropertiesWithVariables
  60. }
  61. aria-label={t('primary')}
  62. >
  63. <Container className="navbar-container" fluid>
  64. <div className="navbar-header">
  65. <HeaderLogoOrTitle
  66. title={title}
  67. overleafLogo={overleafLogo}
  68. customLogo={customLogo}
  69. />
  70. {enableUpgradeButton ? (
  71. <Button
  72. as="a"
  73. href="/user/subscription/plans"
  74. className="me-2 d-md-none"
  75. onClick={() => {
  76. sendMB('upgrade-button-click', {
  77. source: 'dashboard-top',
  78. 'project-dashboard-react': 'enabled',
  79. 'is-dashboard-sidebar-hidden': 'true',
  80. 'is-screen-width-less-than-768px': 'true',
  81. })
  82. }}
  83. >
  84. {t('upgrade')}
  85. </Button>
  86. ) : null}
  87. </div>
  88. {suppressNavbarRight ? null : (
  89. <>
  90. <Navbar.Toggle
  91. aria-controls="navbar-main-collapse"
  92. aria-expanded="false"
  93. aria-label={t('primary')}
  94. >
  95. {showCloseIcon && expanded ? (
  96. <X />
  97. ) : (
  98. <MaterialIcon type="menu" />
  99. )}
  100. </Navbar.Toggle>
  101. <Navbar.Collapse
  102. id="navbar-main-collapse"
  103. className="justify-content-end"
  104. >
  105. <Nav as="ul" className="ms-auto" role="menubar">
  106. {canDisplayAdminMenu ||
  107. canDisplayAdminRedirect ||
  108. canDisplaySplitTestMenu ? (
  109. <AdminMenu
  110. canDisplayAdminMenu={canDisplayAdminMenu}
  111. canDisplayAdminRedirect={canDisplayAdminRedirect}
  112. canDisplaySplitTestMenu={canDisplaySplitTestMenu}
  113. canDisplaySurveyMenu={canDisplaySurveyMenu}
  114. canDisplayScriptLogMenu={canDisplayScriptLogMenu}
  115. adminUrl={adminUrl}
  116. />
  117. ) : null}
  118. {items.map((item, index) => {
  119. const showNavItem =
  120. (item.only_when_logged_in && sessionUser) ||
  121. (item.only_when_logged_out && sessionUser) ||
  122. (!item.only_when_logged_out &&
  123. !item.only_when_logged_in &&
  124. !item.only_content_pages) ||
  125. (item.only_content_pages && !suppressNavContentLinks)
  126. return showNavItem ? (
  127. <NavItemFromData
  128. item={item}
  129. key={index}
  130. showContactUsModal={showContactUsModal}
  131. />
  132. ) : null
  133. })}
  134. {sessionUser ? (
  135. <LoggedInItems
  136. sessionUser={sessionUser}
  137. showSubscriptionLink={showSubscriptionLink}
  138. />
  139. ) : (
  140. <LoggedOutItems showSignUpLink={showSignUpLink} />
  141. )}
  142. </Nav>
  143. </Navbar.Collapse>
  144. </>
  145. )}
  146. </Container>
  147. </Navbar>
  148. <UserProvider>{contactUsModal}</UserProvider>
  149. </>
  150. )
  151. }
  152. export const DefaultNavbarRoot = (props: DefaultNavbarMetadata) => {
  153. const { isReady } = useWaitForI18n()
  154. if (!isReady) {
  155. return null
  156. }
  157. return <DefaultNavbar {...props} />
  158. }
  159. export default DefaultNavbar