integration-widget.tsx 5.0 KB

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