integration-widget.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. <Button
  118. bsStyle={null}
  119. className="btn-secondary-info btn-secondary"
  120. href={linkPath}
  121. disabled={disabled}
  122. >
  123. <span className="text-capitalize">{t('link')}</span>
  124. </Button>
  125. )
  126. }
  127. }
  128. type UnlinkConfirmModalProps = {
  129. show: boolean
  130. title: string
  131. content: string
  132. unlinkPath: string
  133. handleHide: () => void
  134. }
  135. function UnlinkConfirmationModal({
  136. show,
  137. title,
  138. content,
  139. unlinkPath,
  140. handleHide,
  141. }: UnlinkConfirmModalProps) {
  142. const { t } = useTranslation()
  143. const handleCancel = (
  144. event: React.MouseEvent<HTMLButtonElement & Button>
  145. ) => {
  146. event.preventDefault()
  147. handleHide()
  148. }
  149. return (
  150. <AccessibleModal show={show} onHide={handleHide}>
  151. <Modal.Header closeButton>
  152. <Modal.Title>{title}</Modal.Title>
  153. </Modal.Header>
  154. <Modal.Body className="modal-body-share">
  155. <p>{content}</p>
  156. </Modal.Body>
  157. <Modal.Footer>
  158. <form action={unlinkPath} method="POST" className="form-inline">
  159. <input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
  160. <Button
  161. className="btn-secondary-info btn-secondary"
  162. onClick={handleCancel}
  163. >
  164. {t('cancel')}
  165. </Button>
  166. <Button type="submit" bsStyle="danger">
  167. {t('unlink')}
  168. </Button>
  169. </form>
  170. </Modal.Footer>
  171. </AccessibleModal>
  172. )
  173. }