integration-widget.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { useCallback, useState, ReactNode } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import AccessibleModal from '../../../../shared/components/accessible-modal'
  4. import { Button, Modal } from 'react-bootstrap'
  5. import getMeta from '../../../../utils/meta'
  6. import { sendMB } from '../../../../infrastructure/event-tracking'
  7. function trackUpgradeClick() {
  8. sendMB('settings-upgrade-click')
  9. }
  10. type IntegrationLinkingWidgetProps = {
  11. logo: ReactNode
  12. title: string
  13. description: string
  14. helpPath: string
  15. hasFeature?: boolean
  16. statusIndicator?: ReactNode
  17. linked?: boolean
  18. linkPath: string
  19. unlinkPath: string
  20. unlinkConfirmationTitle: string
  21. unlinkConfirmationText: string
  22. disabled?: boolean
  23. }
  24. export function IntegrationLinkingWidget({
  25. logo,
  26. title,
  27. description,
  28. helpPath,
  29. hasFeature,
  30. statusIndicator,
  31. linked,
  32. linkPath,
  33. unlinkPath,
  34. unlinkConfirmationTitle,
  35. unlinkConfirmationText,
  36. disabled,
  37. }: IntegrationLinkingWidgetProps) {
  38. const { t } = useTranslation()
  39. const [showModal, setShowModal] = useState(false)
  40. const handleUnlinkClick = useCallback(() => {
  41. setShowModal(true)
  42. }, [])
  43. const handleModalHide = useCallback(() => {
  44. setShowModal(false)
  45. }, [])
  46. return (
  47. <div className="settings-widget-container">
  48. <div>{logo}</div>
  49. <div className="description-container">
  50. <div className="title-row">
  51. <h4>{title}</h4>
  52. {!hasFeature && (
  53. <span className="label label-info">{t('premium_feature')}</span>
  54. )}
  55. </div>
  56. <p className="small">
  57. {description}{' '}
  58. <a href={helpPath} target="_blank" rel="noreferrer">
  59. {t('learn_more')}
  60. </a>
  61. </p>
  62. {hasFeature && statusIndicator}
  63. </div>
  64. <div>
  65. <ActionButton
  66. hasFeature={hasFeature}
  67. linked={linked}
  68. handleUnlinkClick={handleUnlinkClick}
  69. linkPath={linkPath}
  70. disabled={disabled}
  71. />
  72. </div>
  73. <UnlinkConfirmationModal
  74. show={showModal}
  75. title={unlinkConfirmationTitle}
  76. content={unlinkConfirmationText}
  77. unlinkPath={unlinkPath}
  78. handleHide={handleModalHide}
  79. />
  80. </div>
  81. )
  82. }
  83. type ActionButtonProps = {
  84. hasFeature?: boolean
  85. linked?: boolean
  86. handleUnlinkClick: () => void
  87. linkPath: string
  88. disabled?: boolean
  89. }
  90. function ActionButton({
  91. hasFeature,
  92. linked,
  93. handleUnlinkClick,
  94. linkPath,
  95. disabled,
  96. }: ActionButtonProps) {
  97. const { t } = useTranslation()
  98. if (!hasFeature) {
  99. return (
  100. <Button
  101. bsStyle={null}
  102. className="btn-primary"
  103. href="/user/subscription/plans"
  104. onClick={trackUpgradeClick}
  105. >
  106. <span className="text-capitalize">{t('upgrade')}</span>
  107. </Button>
  108. )
  109. } else if (linked) {
  110. return (
  111. <Button bsStyle="danger" onClick={handleUnlinkClick} disabled={disabled}>
  112. {t('unlink')}
  113. </Button>
  114. )
  115. } else {
  116. return (
  117. <>
  118. {disabled ? (
  119. <button
  120. disabled
  121. className="btn btn-secondary-info btn-secondary text-capitalize"
  122. >
  123. {t('link')}
  124. </button>
  125. ) : (
  126. <a
  127. className="btn btn-secondary-info btn-secondary text-capitalize"
  128. href={linkPath}
  129. >
  130. {t('link')}
  131. </a>
  132. )}
  133. </>
  134. )
  135. }
  136. }
  137. type UnlinkConfirmModalProps = {
  138. show: boolean
  139. title: string
  140. content: string
  141. unlinkPath: string
  142. handleHide: () => void
  143. }
  144. function UnlinkConfirmationModal({
  145. show,
  146. title,
  147. content,
  148. unlinkPath,
  149. handleHide,
  150. }: UnlinkConfirmModalProps) {
  151. const { t } = useTranslation()
  152. const handleCancel = (
  153. event: React.MouseEvent<HTMLButtonElement & Button>
  154. ) => {
  155. event.preventDefault()
  156. handleHide()
  157. }
  158. return (
  159. <AccessibleModal show={show} onHide={handleHide}>
  160. <Modal.Header closeButton>
  161. <Modal.Title>{title}</Modal.Title>
  162. </Modal.Header>
  163. <Modal.Body className="modal-body-share">
  164. <p>{content}</p>
  165. </Modal.Body>
  166. <Modal.Footer>
  167. <form action={unlinkPath} method="POST" className="form-inline">
  168. <input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
  169. <Button
  170. className="btn-secondary-info btn-secondary"
  171. onClick={handleCancel}
  172. >
  173. {t('cancel')}
  174. </Button>
  175. <Button type="submit" bsStyle="danger">
  176. {t('unlink')}
  177. </Button>
  178. </form>
  179. </Modal.Footer>
  180. </AccessibleModal>
  181. )
  182. }