integration-widget.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. const classNames = require('classnames')
  99. const classes = classNames(
  100. 'btn',
  101. 'btn-secondary-info',
  102. 'btn-secondary',
  103. 'text-capitalize'
  104. )
  105. if (!hasFeature) {
  106. return (
  107. <Button
  108. bsStyle={null}
  109. className="btn-primary"
  110. href="/user/subscription/plans"
  111. onClick={trackUpgradeClick}
  112. >
  113. <span className="text-capitalize">{t('upgrade')}</span>
  114. </Button>
  115. )
  116. } else if (linked) {
  117. return (
  118. <Button bsStyle="danger" onClick={handleUnlinkClick} disabled={disabled}>
  119. {t('unlink')}
  120. </Button>
  121. )
  122. } else {
  123. return (
  124. <>
  125. {disabled ? (
  126. <button disabled className={classes}>
  127. {t('link')}
  128. </button>
  129. ) : (
  130. <a className={classes} href={linkPath}>
  131. {t('link')}
  132. </a>
  133. )}
  134. </>
  135. )
  136. }
  137. }
  138. type UnlinkConfirmModalProps = {
  139. show: boolean
  140. title: string
  141. content: string
  142. unlinkPath: string
  143. handleHide: () => void
  144. }
  145. function UnlinkConfirmationModal({
  146. show,
  147. title,
  148. content,
  149. unlinkPath,
  150. handleHide,
  151. }: UnlinkConfirmModalProps) {
  152. const { t } = useTranslation()
  153. const handleCancel = (
  154. event: React.MouseEvent<HTMLButtonElement & Button>
  155. ) => {
  156. event.preventDefault()
  157. handleHide()
  158. }
  159. return (
  160. <AccessibleModal show={show} onHide={handleHide}>
  161. <Modal.Header closeButton>
  162. <Modal.Title>{title}</Modal.Title>
  163. </Modal.Header>
  164. <Modal.Body className="modal-body-share">
  165. <p>{content}</p>
  166. </Modal.Body>
  167. <Modal.Footer>
  168. <form action={unlinkPath} method="POST" className="form-inline">
  169. <input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
  170. <Button
  171. className="btn-secondary-info btn-secondary"
  172. onClick={handleCancel}
  173. >
  174. {t('cancel')}
  175. </Button>
  176. <Button type="submit" bsStyle="danger">
  177. {t('unlink')}
  178. </Button>
  179. </form>
  180. </Modal.Footer>
  181. </AccessibleModal>
  182. )
  183. }