integration-widget.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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 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 linkTextId = `${titleId}-link`
  115. if (!hasFeature) {
  116. return (
  117. <OLButton
  118. variant="primary"
  119. href="/user/subscription/plans"
  120. onClick={() => trackUpgradeClick(integration)}
  121. aria-labelledby={`${titleId} ${linkTextId}`}
  122. >
  123. <span id={linkTextId}>{t('upgrade')}</span>
  124. </OLButton>
  125. )
  126. } else if (linked) {
  127. return (
  128. <OLButton
  129. variant="danger-ghost"
  130. onClick={handleUnlinkClick}
  131. disabled={disabled}
  132. >
  133. {t('unlink')}
  134. </OLButton>
  135. )
  136. } else {
  137. return (
  138. <>
  139. {disabled ? (
  140. <OLButton disabled variant="secondary">
  141. {t('link')}
  142. </OLButton>
  143. ) : (
  144. <OLButton
  145. variant="secondary"
  146. href={linkPath}
  147. onClick={() => trackLinkingClick(integration)}
  148. >
  149. {t('link')}
  150. </OLButton>
  151. )}
  152. </>
  153. )
  154. }
  155. }
  156. type UnlinkConfirmModalProps = {
  157. show: boolean
  158. title: string
  159. integration: string
  160. content: string
  161. unlinkPath: string
  162. handleHide: () => void
  163. }
  164. function UnlinkConfirmationModal({
  165. show,
  166. title,
  167. integration,
  168. content,
  169. unlinkPath,
  170. handleHide,
  171. }: UnlinkConfirmModalProps) {
  172. const { t } = useTranslation()
  173. const handleCancel = (event: React.MouseEvent<HTMLButtonElement>) => {
  174. event.preventDefault()
  175. handleHide()
  176. }
  177. const handleConfirm = () => {
  178. sendMB('unlink-integration-click', {
  179. integration,
  180. })
  181. }
  182. return (
  183. <OLModal show={show} onHide={handleHide}>
  184. <OLModalHeader closeButton>
  185. <OLModalTitle>{title}</OLModalTitle>
  186. </OLModalHeader>
  187. <OLModalBody>
  188. <p>{content}</p>
  189. </OLModalBody>
  190. <OLModalFooter>
  191. <form action={unlinkPath} method="POST" className="form-inline">
  192. <input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
  193. <OLButton variant="secondary" onClick={handleCancel}>
  194. {t('cancel')}
  195. </OLButton>
  196. <OLButton
  197. type="submit"
  198. variant="danger-ghost"
  199. onClick={handleConfirm}
  200. >
  201. {t('unlink')}
  202. </OLButton>
  203. </form>
  204. </OLModalFooter>
  205. </OLModal>
  206. )
  207. }