integration-widget.tsx 5.5 KB

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