linking-section.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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. const hasSSOLinkingSection = Object.keys(subscriptions).length > 0
  38. if (
  39. !hasIntegrationLinkingSection &&
  40. !hasReferencesLinkingSection &&
  41. !hasSSOLinkingSection
  42. ) {
  43. return null
  44. }
  45. return (
  46. <>
  47. <h3>{t('integrations')}</h3>
  48. <p className="small">{t('linked_accounts_explained')}</p>
  49. {hasIntegrationLinkingSection ? (
  50. <>
  51. <h3 id="project-sync" className="text-capitalize">
  52. {t('project_synchronisation')}
  53. </h3>
  54. {projectSyncSuccessMessage ? (
  55. <Alert bsStyle="success">{projectSyncSuccessMessage}</Alert>
  56. ) : null}
  57. <div className="settings-widgets-container">
  58. {allIntegrationLinkingWidgets.map(
  59. ({ import: importObject, path }, widgetIndex) => (
  60. <ModuleLinkingWidget
  61. key={Object.keys(importObject)[0]}
  62. ModuleComponent={Object.values(importObject)[0]}
  63. isLast={
  64. widgetIndex === allIntegrationLinkingWidgets.length - 1
  65. }
  66. />
  67. )
  68. )}
  69. </div>
  70. </>
  71. ) : null}
  72. {hasReferencesLinkingSection ? (
  73. <>
  74. <h3 id="references" className="text-capitalize">
  75. {t('reference_managers')}
  76. </h3>
  77. <div className="settings-widgets-container">
  78. {referenceLinkingWidgets.map(
  79. ({ import: importObject, path }, widgetIndex) => (
  80. <ModuleLinkingWidget
  81. key={Object.keys(importObject)[0]}
  82. ModuleComponent={Object.values(importObject)[0]}
  83. isLast={widgetIndex === referenceLinkingWidgets.length - 1}
  84. />
  85. )
  86. )}
  87. </div>
  88. </>
  89. ) : null}
  90. {hasSSOLinkingSection ? (
  91. <>
  92. <h3 id="linked-accounts" className="text-capitalize">
  93. {t('linked_accounts')}
  94. </h3>
  95. {ssoErrorMessage ? (
  96. <Alert bsStyle="danger">
  97. {t('sso_link_error')}: {ssoErrorMessage}
  98. </Alert>
  99. ) : null}
  100. <div className="settings-widgets-container">
  101. {Object.values(subscriptions).map(
  102. (subscription, subscriptionIndex) => (
  103. <SSOLinkingWidgetContainer
  104. key={subscription.providerId}
  105. subscription={subscription}
  106. isLast={
  107. subscriptionIndex === Object.keys(subscriptions).length - 1
  108. }
  109. />
  110. )
  111. )}
  112. </div>
  113. </>
  114. ) : null}
  115. {hasIntegrationLinkingSection ||
  116. hasReferencesLinkingSection ||
  117. hasSSOLinkingSection ? (
  118. <hr />
  119. ) : null}
  120. </>
  121. )
  122. }
  123. type LinkingWidgetProps = {
  124. ModuleComponent: any
  125. isLast: boolean
  126. }
  127. function ModuleLinkingWidget({ ModuleComponent, isLast }: LinkingWidgetProps) {
  128. return (
  129. <>
  130. <ModuleComponent />
  131. {isLast ? null : <hr />}
  132. </>
  133. )
  134. }
  135. type SSOLinkingWidgetContainerProps = {
  136. subscription: SSOSubscription
  137. isLast: boolean
  138. }
  139. function SSOLinkingWidgetContainer({
  140. subscription,
  141. isLast,
  142. }: SSOLinkingWidgetContainerProps) {
  143. const { t } = useTranslation()
  144. const { unlink } = useSSOContext()
  145. let description = ''
  146. switch (subscription.providerId) {
  147. case 'collabratec':
  148. description = t('linked_collabratec_description')
  149. break
  150. case 'google':
  151. case 'twitter':
  152. description = `${t('login_with_service', {
  153. service: subscription.provider.name,
  154. })}.`
  155. break
  156. case 'orcid':
  157. description = t('oauth_orcid_description')
  158. break
  159. }
  160. return (
  161. <>
  162. <SSOLinkingWidget
  163. providerId={subscription.providerId}
  164. title={subscription.provider.name}
  165. description={description}
  166. helpPath={subscription.provider.descriptionOptions?.link}
  167. linked={subscription.linked}
  168. linkPath={subscription.provider.linkPath}
  169. onUnlink={() => unlink(subscription.providerId)}
  170. />
  171. {isLast ? null : <hr />}
  172. </>
  173. )
  174. }
  175. export default LinkingSection