linking-section.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { useState, ElementType } from 'react'
  2. import { Alert } from 'react-bootstrap'
  3. import { useTranslation } from 'react-i18next'
  4. import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
  5. import { useSSOContext, SSOSubscription } from '../context/sso-context'
  6. import { SSOLinkingWidget } from './linking/sso-widget'
  7. import getMeta from '../../../utils/meta'
  8. function LinkingSection() {
  9. const { t } = useTranslation()
  10. const { subscriptions } = useSSOContext()
  11. const ssoErrorMessage = getMeta('ol-ssoErrorMessage') as string
  12. const projectSyncSuccessMessage = getMeta(
  13. 'ol-projectSyncSuccessMessage'
  14. ) as string
  15. const [integrationLinkingWidgets] = useState<any[]>(
  16. () =>
  17. getMeta('integrationLinkingWidgets') ||
  18. importOverleafModules('integrationLinkingWidgets')
  19. )
  20. const [referenceLinkingWidgets] = useState<any[]>(
  21. () =>
  22. getMeta('referenceLinkingWidgets') ||
  23. importOverleafModules('referenceLinkingWidgets')
  24. )
  25. const oauth2ServerComponents = importOverleafModules('oauth2Server') as {
  26. import: { default: ElementType }
  27. path: string
  28. }[]
  29. const showPersonalAccessToken = getMeta(
  30. 'ol-showPersonalAccessToken'
  31. ) as boolean
  32. const allIntegrationLinkingWidgets = showPersonalAccessToken
  33. ? integrationLinkingWidgets.concat(oauth2ServerComponents)
  34. : integrationLinkingWidgets
  35. const hasIntegrationLinkingSection = allIntegrationLinkingWidgets.length
  36. const hasReferencesLinkingSection = referenceLinkingWidgets.length
  37. // Filter out SSO providers that are not allowed to be linked by
  38. // managed users. Allow unlinking them if they are already linked.
  39. const hideGoogleSSO = getMeta('ol-cannot-link-google-sso')
  40. const hideOtherThirdPartySSO = getMeta('ol-cannot-link-other-third-party-sso')
  41. for (const providerId in subscriptions) {
  42. const isLinked = subscriptions[providerId].linked
  43. if (providerId === 'google') {
  44. if (hideGoogleSSO && !isLinked) {
  45. delete subscriptions[providerId]
  46. }
  47. } else {
  48. if (hideOtherThirdPartySSO && !isLinked) {
  49. delete subscriptions[providerId]
  50. }
  51. }
  52. }
  53. const hasSSOLinkingSection = Object.keys(subscriptions).length > 0
  54. if (
  55. !hasIntegrationLinkingSection &&
  56. !hasReferencesLinkingSection &&
  57. !hasSSOLinkingSection
  58. ) {
  59. return null
  60. }
  61. return (
  62. <>
  63. <h3>{t('integrations')}</h3>
  64. <p className="small">{t('linked_accounts_explained')}</p>
  65. {hasIntegrationLinkingSection ? (
  66. <>
  67. <h3 id="project-sync" className="text-capitalize">
  68. {t('project_synchronisation')}
  69. </h3>
  70. {projectSyncSuccessMessage ? (
  71. <Alert bsStyle="success">{projectSyncSuccessMessage}</Alert>
  72. ) : null}
  73. <div className="settings-widgets-container">
  74. {allIntegrationLinkingWidgets.map(
  75. ({ import: importObject, path }, widgetIndex) => (
  76. <ModuleLinkingWidget
  77. key={Object.keys(importObject)[0]}
  78. ModuleComponent={Object.values(importObject)[0]}
  79. isLast={
  80. widgetIndex === allIntegrationLinkingWidgets.length - 1
  81. }
  82. />
  83. )
  84. )}
  85. </div>
  86. </>
  87. ) : null}
  88. {hasReferencesLinkingSection ? (
  89. <>
  90. <h3 id="references" className="text-capitalize">
  91. {t('reference_managers')}
  92. </h3>
  93. <div className="settings-widgets-container">
  94. {referenceLinkingWidgets.map(
  95. ({ import: importObject, path }, widgetIndex) => (
  96. <ModuleLinkingWidget
  97. key={Object.keys(importObject)[0]}
  98. ModuleComponent={Object.values(importObject)[0]}
  99. isLast={widgetIndex === referenceLinkingWidgets.length - 1}
  100. />
  101. )
  102. )}
  103. </div>
  104. </>
  105. ) : null}
  106. {hasSSOLinkingSection ? (
  107. <>
  108. <h3 id="linked-accounts" className="text-capitalize">
  109. {t('linked_accounts')}
  110. </h3>
  111. {ssoErrorMessage ? (
  112. <Alert bsStyle="danger">
  113. {t('sso_link_error')}: {ssoErrorMessage}
  114. </Alert>
  115. ) : null}
  116. <div className="settings-widgets-container">
  117. {Object.values(subscriptions).map(
  118. (subscription, subscriptionIndex) => (
  119. <SSOLinkingWidgetContainer
  120. key={subscription.providerId}
  121. subscription={subscription}
  122. isLast={
  123. subscriptionIndex === Object.keys(subscriptions).length - 1
  124. }
  125. />
  126. )
  127. )}
  128. </div>
  129. </>
  130. ) : null}
  131. {hasIntegrationLinkingSection ||
  132. hasReferencesLinkingSection ||
  133. hasSSOLinkingSection ? (
  134. <hr />
  135. ) : null}
  136. </>
  137. )
  138. }
  139. type LinkingWidgetProps = {
  140. ModuleComponent: any
  141. isLast: boolean
  142. }
  143. function ModuleLinkingWidget({ ModuleComponent, isLast }: LinkingWidgetProps) {
  144. return (
  145. <>
  146. <ModuleComponent />
  147. {isLast ? null : <hr />}
  148. </>
  149. )
  150. }
  151. type SSOLinkingWidgetContainerProps = {
  152. subscription: SSOSubscription
  153. isLast: boolean
  154. }
  155. function SSOLinkingWidgetContainer({
  156. subscription,
  157. isLast,
  158. }: SSOLinkingWidgetContainerProps) {
  159. const { t } = useTranslation()
  160. const { unlink } = useSSOContext()
  161. let description = ''
  162. switch (subscription.providerId) {
  163. case 'collabratec':
  164. description = t('linked_collabratec_description')
  165. break
  166. case 'google':
  167. description = `${t('login_with_service', {
  168. service: subscription.provider.name,
  169. })}.`
  170. break
  171. case 'twitter':
  172. description = t('login_with_service_will_stop_working_soon', {
  173. service: subscription.provider.name,
  174. })
  175. break
  176. case 'orcid':
  177. description = t('oauth_orcid_description')
  178. break
  179. }
  180. return (
  181. <>
  182. <SSOLinkingWidget
  183. providerId={subscription.providerId}
  184. title={subscription.provider.name}
  185. description={description}
  186. helpPath={subscription.provider.descriptionOptions?.link}
  187. linked={subscription.linked}
  188. linkPath={subscription.provider.linkPath}
  189. onUnlink={() => unlink(subscription.providerId)}
  190. />
  191. {isLast ? null : <hr />}
  192. </>
  193. )
  194. }
  195. export default LinkingSection