integration-widget.tsx 4.6 KB

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