integration-widget.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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 OLModal, {
  8. OLModalBody,
  9. OLModalFooter,
  10. OLModalHeader,
  11. OLModalTitle,
  12. } from '@/shared/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 href={helpPath} target="_blank" rel="noreferrer">
  68. {t('learn_more_about', { appName: title })}
  69. </a>
  70. </p>
  71. {hasFeature && statusIndicator}
  72. </div>
  73. <div>
  74. <ActionButton
  75. titleId={id}
  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. titleId: string
  103. }
  104. function ActionButton({
  105. hasFeature,
  106. linked,
  107. handleUnlinkClick,
  108. linkPath,
  109. disabled,
  110. integration,
  111. titleId,
  112. }: ActionButtonProps) {
  113. const { t } = useTranslation()
  114. const upgradeTextId = `${titleId}-upgrade`
  115. const linkTextId = `${titleId}-link`
  116. const unlinkTextId = `${titleId}-unlink`
  117. if (!hasFeature) {
  118. return (
  119. <OLButton
  120. variant="primary"
  121. href="/user/subscription/plans"
  122. onClick={() => trackUpgradeClick(integration)}
  123. aria-labelledby={`${titleId} ${upgradeTextId}`}
  124. >
  125. <span id={upgradeTextId}>{t('upgrade')}</span>
  126. </OLButton>
  127. )
  128. } else if (linked) {
  129. return (
  130. <OLButton
  131. variant="danger-ghost"
  132. aria-labelledby={`${unlinkTextId} ${titleId}`}
  133. onClick={handleUnlinkClick}
  134. disabled={disabled}
  135. id={unlinkTextId}
  136. >
  137. {t('unlink')}
  138. </OLButton>
  139. )
  140. } else {
  141. return (
  142. <>
  143. {disabled ? (
  144. <OLButton
  145. disabled
  146. variant="secondary"
  147. aria-labelledby={`${linkTextId} ${titleId}`}
  148. id={linkTextId}
  149. >
  150. {t('link')}
  151. </OLButton>
  152. ) : (
  153. <OLButton
  154. variant="secondary"
  155. href={linkPath}
  156. onClick={() => trackLinkingClick(integration)}
  157. aria-labelledby={`${linkTextId} ${titleId}`}
  158. id={linkTextId}
  159. >
  160. {t('link')}
  161. </OLButton>
  162. )}
  163. </>
  164. )
  165. }
  166. }
  167. type UnlinkConfirmModalProps = {
  168. show: boolean
  169. title: string
  170. integration: string
  171. content: string
  172. unlinkPath: string
  173. handleHide: () => void
  174. }
  175. function UnlinkConfirmationModal({
  176. show,
  177. title,
  178. integration,
  179. content,
  180. unlinkPath,
  181. handleHide,
  182. }: UnlinkConfirmModalProps) {
  183. const { t } = useTranslation()
  184. const handleCancel = (event: React.MouseEvent<HTMLButtonElement>) => {
  185. event.preventDefault()
  186. handleHide()
  187. }
  188. const handleConfirm = () => {
  189. sendMB('unlink-integration-click', {
  190. integration,
  191. })
  192. }
  193. return (
  194. <OLModal show={show} onHide={handleHide}>
  195. <OLModalHeader closeButton>
  196. <OLModalTitle>{title}</OLModalTitle>
  197. </OLModalHeader>
  198. <OLModalBody>
  199. <p>{content}</p>
  200. </OLModalBody>
  201. <OLModalFooter>
  202. <form action={unlinkPath} method="POST" className="form-inline">
  203. <input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
  204. <OLButton variant="secondary" onClick={handleCancel}>
  205. {t('cancel')}
  206. </OLButton>
  207. <OLButton
  208. type="submit"
  209. variant="danger-ghost"
  210. onClick={handleConfirm}
  211. >
  212. {t('unlink')}
  213. </OLButton>
  214. </form>
  215. </OLModalFooter>
  216. </OLModal>
  217. )
  218. }