integration-widget.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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. id: string
  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. id,
  36. logo,
  37. title,
  38. description,
  39. helpPath,
  40. hasFeature,
  41. statusIndicator,
  42. linked,
  43. linkPath,
  44. unlinkPath,
  45. unlinkConfirmationTitle,
  46. unlinkConfirmationText,
  47. disabled,
  48. }: IntegrationLinkingWidgetProps) {
  49. const { t } = useTranslation()
  50. const [showModal, setShowModal] = useState(false)
  51. const handleUnlinkClick = useCallback(() => {
  52. setShowModal(true)
  53. }, [])
  54. const handleModalHide = useCallback(() => {
  55. setShowModal(false)
  56. }, [])
  57. return (
  58. <div className="settings-widget-container">
  59. <div>{logo}</div>
  60. <div className="description-container">
  61. <div className="title-row">
  62. <h4 id={id}>{title}</h4>
  63. {!hasFeature && <OLBadge bg="info">{t('premium_feature')}</OLBadge>}
  64. </div>
  65. <p className="small">
  66. {description}{' '}
  67. <a
  68. href={helpPath}
  69. target="_blank"
  70. rel="noreferrer"
  71. aria-label={t('learn_more_about', { integrationName: title })}
  72. >
  73. {t('learn_more')}
  74. </a>
  75. </p>
  76. {hasFeature && statusIndicator}
  77. </div>
  78. <div>
  79. <ActionButton
  80. titleId={id}
  81. integration={title}
  82. hasFeature={hasFeature}
  83. linked={linked}
  84. handleUnlinkClick={handleUnlinkClick}
  85. linkPath={linkPath}
  86. disabled={disabled}
  87. />
  88. </div>
  89. <UnlinkConfirmationModal
  90. integration={title}
  91. show={showModal}
  92. title={unlinkConfirmationTitle}
  93. content={unlinkConfirmationText}
  94. unlinkPath={unlinkPath}
  95. handleHide={handleModalHide}
  96. />
  97. </div>
  98. )
  99. }
  100. type ActionButtonProps = {
  101. integration: string
  102. hasFeature?: boolean
  103. linked?: boolean
  104. handleUnlinkClick: () => void
  105. linkPath: string
  106. disabled?: boolean
  107. titleId: string
  108. }
  109. function ActionButton({
  110. hasFeature,
  111. linked,
  112. handleUnlinkClick,
  113. linkPath,
  114. disabled,
  115. integration,
  116. titleId,
  117. }: ActionButtonProps) {
  118. const { t } = useTranslation()
  119. const linkTextId = `${titleId}-link`
  120. if (!hasFeature) {
  121. return (
  122. <OLButton
  123. variant="primary"
  124. href="/user/subscription/plans"
  125. onClick={() => trackUpgradeClick(integration)}
  126. aria-labelledby={`${titleId} ${linkTextId}`}
  127. >
  128. <span id={linkTextId} className="text-capitalize">
  129. {t('upgrade')}
  130. </span>
  131. </OLButton>
  132. )
  133. } else if (linked) {
  134. return (
  135. <OLButton
  136. variant="danger-ghost"
  137. onClick={handleUnlinkClick}
  138. disabled={disabled}
  139. >
  140. {t('unlink')}
  141. </OLButton>
  142. )
  143. } else {
  144. return (
  145. <>
  146. {disabled ? (
  147. <OLButton disabled variant="secondary" className="text-capitalize">
  148. {t('link')}
  149. </OLButton>
  150. ) : (
  151. <OLButton
  152. variant="secondary"
  153. href={linkPath}
  154. className="text-capitalize"
  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 variant="secondary" onClick={handleCancel}>
  202. {t('cancel')}
  203. </OLButton>
  204. <OLButton
  205. type="submit"
  206. variant="danger-ghost"
  207. onClick={handleConfirm}
  208. >
  209. {t('unlink')}
  210. </OLButton>
  211. </form>
  212. </OLModalFooter>
  213. </OLModal>
  214. )
  215. }