integration-widget.tsx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import { useCallback, useState, ReactNode } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import BadgeWrapper from '@/features/ui/components/bootstrap-5/wrappers/badge-wrapper'
  4. import getMeta from '../../../../utils/meta'
  5. import { sendMB } from '../../../../infrastructure/event-tracking'
  6. import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
  7. import { bsVersion } from '@/features/utils/bootstrap-5'
  8. import OLModal, {
  9. OLModalBody,
  10. OLModalFooter,
  11. OLModalHeader,
  12. OLModalTitle,
  13. } from '@/features/ui/components/bootstrap-5/wrappers/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 && (
  63. <BadgeWrapper bg="info">{t('premium_feature')}</BadgeWrapper>
  64. )}
  65. </div>
  66. <p className="small">
  67. {description}{' '}
  68. <a href={helpPath} target="_blank" rel="noreferrer">
  69. {t('learn_more')}
  70. </a>
  71. </p>
  72. {hasFeature && statusIndicator}
  73. </div>
  74. <div>
  75. <ActionButton
  76. integration={title}
  77. hasFeature={hasFeature}
  78. linked={linked}
  79. handleUnlinkClick={handleUnlinkClick}
  80. linkPath={linkPath}
  81. disabled={disabled}
  82. />
  83. </div>
  84. <UnlinkConfirmationModal
  85. integration={title}
  86. show={showModal}
  87. title={unlinkConfirmationTitle}
  88. content={unlinkConfirmationText}
  89. unlinkPath={unlinkPath}
  90. handleHide={handleModalHide}
  91. />
  92. </div>
  93. )
  94. }
  95. type ActionButtonProps = {
  96. integration: string
  97. hasFeature?: boolean
  98. linked?: boolean
  99. handleUnlinkClick: () => void
  100. linkPath: string
  101. disabled?: boolean
  102. }
  103. function ActionButton({
  104. hasFeature,
  105. linked,
  106. handleUnlinkClick,
  107. linkPath,
  108. disabled,
  109. integration,
  110. }: ActionButtonProps) {
  111. const { t } = useTranslation()
  112. if (!hasFeature) {
  113. return (
  114. <ButtonWrapper
  115. variant="primary"
  116. href="/user/subscription/plans"
  117. onClick={() => trackUpgradeClick(integration)}
  118. bs3Props={{ bsStyle: null, className: 'btn-primary' }}
  119. >
  120. <span className="text-capitalize">{t('upgrade')}</span>
  121. </ButtonWrapper>
  122. )
  123. } else if (linked) {
  124. return (
  125. <ButtonWrapper
  126. variant="danger-ghost"
  127. onClick={handleUnlinkClick}
  128. disabled={disabled}
  129. bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
  130. >
  131. {t('unlink')}
  132. </ButtonWrapper>
  133. )
  134. } else {
  135. return (
  136. <>
  137. {disabled ? (
  138. <ButtonWrapper
  139. disabled
  140. variant="secondary"
  141. className={bsVersion({
  142. bs3: 'btn btn-secondary-info btn-secondary text-capitalize',
  143. bs5: 'text-capitalize',
  144. })}
  145. >
  146. {t('link')}
  147. </ButtonWrapper>
  148. ) : (
  149. <ButtonWrapper
  150. variant="secondary"
  151. href={linkPath}
  152. className={bsVersion({
  153. bs3: 'btn btn-secondary-info btn-secondary text-capitalize',
  154. bs5: 'text-capitalize',
  155. })}
  156. bs3Props={{ bsStyle: null }}
  157. onClick={() => trackLinkingClick(integration)}
  158. >
  159. {t('link')}
  160. </ButtonWrapper>
  161. )}
  162. </>
  163. )
  164. }
  165. }
  166. type UnlinkConfirmModalProps = {
  167. show: boolean
  168. title: string
  169. integration: string
  170. content: string
  171. unlinkPath: string
  172. handleHide: () => void
  173. }
  174. function UnlinkConfirmationModal({
  175. show,
  176. title,
  177. integration,
  178. content,
  179. unlinkPath,
  180. handleHide,
  181. }: UnlinkConfirmModalProps) {
  182. const { t } = useTranslation()
  183. const handleCancel = (event: React.MouseEvent<HTMLButtonElement>) => {
  184. event.preventDefault()
  185. handleHide()
  186. }
  187. const handleConfirm = () => {
  188. sendMB('unlink-integration-click', {
  189. integration,
  190. })
  191. }
  192. return (
  193. <OLModal show={show} onHide={handleHide}>
  194. <OLModalHeader closeButton>
  195. <OLModalTitle>{title}</OLModalTitle>
  196. </OLModalHeader>
  197. <OLModalBody>
  198. <p>{content}</p>
  199. </OLModalBody>
  200. <OLModalFooter>
  201. <form action={unlinkPath} method="POST" className="form-inline">
  202. <input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
  203. <ButtonWrapper
  204. variant="secondary"
  205. onClick={handleCancel}
  206. bs3Props={{
  207. bsStyle: null,
  208. className: 'btn-secondary-info btn-secondary',
  209. }}
  210. >
  211. {t('cancel')}
  212. </ButtonWrapper>
  213. <ButtonWrapper
  214. type="submit"
  215. variant="danger-ghost"
  216. bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
  217. onClick={handleConfirm}
  218. >
  219. {t('unlink')}
  220. </ButtonWrapper>
  221. </form>
  222. </OLModalFooter>
  223. </OLModal>
  224. )
  225. }