integration-widget.tsx 5.7 KB

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