integration-widget.tsx 5.9 KB

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