sso-widget.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { useCallback, useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import { Button, Modal } from 'react-bootstrap'
  4. import { FetchError } from '../../../../infrastructure/fetch-json'
  5. import AccessibleModal from '../../../../shared/components/accessible-modal'
  6. import IEEELogo from '../../../../shared/svgs/ieee-logo'
  7. import GoogleLogo from '../../../../shared/svgs/google-logo'
  8. import OrcidLogo from '../../../../shared/svgs/orcid-logo'
  9. import LinkingStatus from './status'
  10. const providerLogos: { readonly [p: string]: JSX.Element } = {
  11. collabratec: <IEEELogo />,
  12. google: <GoogleLogo />,
  13. orcid: <OrcidLogo />,
  14. }
  15. type SSOLinkingWidgetProps = {
  16. providerId: string
  17. title: string
  18. description: string
  19. helpPath?: string
  20. linked?: boolean
  21. linkPath: string
  22. onUnlink: () => Promise<void>
  23. }
  24. export function SSOLinkingWidget({
  25. providerId,
  26. title,
  27. description,
  28. helpPath,
  29. linked,
  30. linkPath,
  31. onUnlink,
  32. }: SSOLinkingWidgetProps) {
  33. const { t } = useTranslation()
  34. const [showModal, setShowModal] = useState(false)
  35. const [unlinkRequestInflight, setUnlinkRequestInflight] = useState(false)
  36. const [errorMessage, setErrorMessage] = useState('')
  37. const handleUnlinkClick = useCallback(() => {
  38. setShowModal(true)
  39. setErrorMessage('')
  40. }, [])
  41. const handleUnlinkConfirmationClick = useCallback(() => {
  42. setShowModal(false)
  43. setUnlinkRequestInflight(true)
  44. onUnlink()
  45. .catch((error: FetchError) => {
  46. setErrorMessage(error.getUserFacingMessage())
  47. })
  48. .finally(() => {
  49. setUnlinkRequestInflight(false)
  50. })
  51. }, [onUnlink])
  52. const handleModalHide = useCallback(() => {
  53. setShowModal(false)
  54. }, [])
  55. return (
  56. <div className="settings-widget-container">
  57. <div>{providerLogos[providerId]}</div>
  58. <div className="description-container">
  59. <div className="title-row">
  60. <h4>{title}</h4>
  61. </div>
  62. <p className="small">
  63. {description?.replace(/<[^>]+>/g, '')}{' '}
  64. {helpPath ? (
  65. <a href={helpPath} target="_blank" rel="noreferrer">
  66. {t('learn_more')}
  67. </a>
  68. ) : null}
  69. </p>
  70. {errorMessage ? (
  71. <LinkingStatus status="error" description={errorMessage} />
  72. ) : null}
  73. </div>
  74. <div>
  75. <ActionButton
  76. unlinkRequestInflight={unlinkRequestInflight}
  77. accountIsLinked={linked}
  78. linkPath={`${linkPath}?intent=link`}
  79. onUnlinkClick={handleUnlinkClick}
  80. />
  81. </div>
  82. <UnlinkConfirmModal
  83. title={title}
  84. show={showModal}
  85. handleConfirmation={handleUnlinkConfirmationClick}
  86. handleHide={handleModalHide}
  87. />
  88. </div>
  89. )
  90. }
  91. type ActionButtonProps = {
  92. unlinkRequestInflight: boolean
  93. accountIsLinked?: boolean
  94. linkPath: string
  95. onUnlinkClick: () => void
  96. }
  97. function ActionButton({
  98. unlinkRequestInflight,
  99. accountIsLinked,
  100. linkPath,
  101. onUnlinkClick,
  102. }: ActionButtonProps) {
  103. const { t } = useTranslation()
  104. if (unlinkRequestInflight) {
  105. return (
  106. <Button bsStyle="danger" disabled>
  107. {t('unlinking')}
  108. </Button>
  109. )
  110. } else if (accountIsLinked) {
  111. return (
  112. <Button bsStyle="danger" onClick={onUnlinkClick}>
  113. {t('unlink')}
  114. </Button>
  115. )
  116. } else {
  117. return (
  118. <a href={linkPath} className="btn btn-info text-capitalize">
  119. {t('link')}
  120. </a>
  121. )
  122. }
  123. }
  124. type UnlinkConfirmModalProps = {
  125. title: string
  126. show: boolean
  127. handleConfirmation: () => void
  128. handleHide: () => void
  129. }
  130. function UnlinkConfirmModal({
  131. title,
  132. show,
  133. handleConfirmation,
  134. handleHide,
  135. }: UnlinkConfirmModalProps) {
  136. const { t } = useTranslation()
  137. return (
  138. <AccessibleModal show={show} onHide={handleHide}>
  139. <Modal.Header closeButton>
  140. <Modal.Title>
  141. {t('unlink_provider_account_title', { provider: title })}
  142. </Modal.Title>
  143. </Modal.Header>
  144. <Modal.Body className="modal-body-share">
  145. <p>{t('unlink_provider_account_warning', { provider: title })}</p>
  146. </Modal.Body>
  147. <Modal.Footer>
  148. <Button bsStyle="default" onClick={handleHide}>
  149. {t('cancel')}
  150. </Button>
  151. <Button bsStyle="danger" onClick={handleConfirmation}>
  152. {t('unlink')}
  153. </Button>
  154. </Modal.Footer>
  155. </AccessibleModal>
  156. )
  157. }