integration-widget.tsx 4.7 KB

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