overleaf/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx

243 lines
5.9 KiB
TypeScript
Raw Normal View History

import { useCallback, useState, ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import BadgeWrapper from '@/features/ui/components/bootstrap-5/wrappers/badge-wrapper'
import getMeta from '../../../../utils/meta'
import { sendMB } from '../../../../infrastructure/event-tracking'
import ButtonWrapper from '@/features/ui/components/bootstrap-5/wrappers/button-wrapper'
import { bsVersion } from '@/features/utils/bootstrap-5'
import OLModal, {
OLModalBody,
OLModalFooter,
OLModalHeader,
OLModalTitle,
} from '@/features/ui/components/bootstrap-5/wrappers/ol-modal'
function trackUpgradeClick(integration: string) {
sendMB('settings-upgrade-click', { integration })
}
function trackLinkingClick(integration: string) {
sendMB('link-integration-click', { integration, location: 'Settings' })
}
type IntegrationLinkingWidgetProps = {
logo: ReactNode
title: string
description: string
helpPath: string
hasFeature?: boolean
statusIndicator?: ReactNode
linked?: boolean
linkPath: string
unlinkPath: string
unlinkConfirmationTitle: string
unlinkConfirmationText: string
disabled?: boolean
}
export function IntegrationLinkingWidget({
logo,
title,
description,
helpPath,
hasFeature,
statusIndicator,
linked,
linkPath,
unlinkPath,
unlinkConfirmationTitle,
unlinkConfirmationText,
disabled,
}: IntegrationLinkingWidgetProps) {
const { t } = useTranslation()
const [showModal, setShowModal] = useState(false)
const handleUnlinkClick = useCallback(() => {
setShowModal(true)
}, [])
const handleModalHide = useCallback(() => {
setShowModal(false)
}, [])
return (
<div className="settings-widget-container">
<div>{logo}</div>
<div className="description-container">
<div className="title-row">
<h4>{title}</h4>
{!hasFeature && (
<BadgeWrapper bg="info">{t('premium_feature')}</BadgeWrapper>
)}
</div>
<p className="small">
{description}{' '}
<a href={helpPath} target="_blank" rel="noreferrer">
{t('learn_more')}
</a>
</p>
{hasFeature && statusIndicator}
</div>
<div>
<ActionButton
integration={title}
hasFeature={hasFeature}
linked={linked}
handleUnlinkClick={handleUnlinkClick}
linkPath={linkPath}
disabled={disabled}
/>
</div>
<UnlinkConfirmationModal
integration={title}
show={showModal}
title={unlinkConfirmationTitle}
content={unlinkConfirmationText}
unlinkPath={unlinkPath}
handleHide={handleModalHide}
/>
</div>
)
}
type ActionButtonProps = {
integration: string
hasFeature?: boolean
linked?: boolean
handleUnlinkClick: () => void
linkPath: string
disabled?: boolean
}
function ActionButton({
hasFeature,
linked,
handleUnlinkClick,
linkPath,
disabled,
integration,
}: ActionButtonProps) {
const { t } = useTranslation()
if (!hasFeature) {
return (
<ButtonWrapper
variant="primary"
href="/user/subscription/plans"
onClick={() => trackUpgradeClick(integration)}
bs3Props={{ bsStyle: null, className: 'btn-primary' }}
>
<span className="text-capitalize">{t('upgrade')}</span>
</ButtonWrapper>
)
} else if (linked) {
return (
<ButtonWrapper
variant="danger-ghost"
onClick={handleUnlinkClick}
disabled={disabled}
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
>
{t('unlink')}
</ButtonWrapper>
)
} else {
return (
<>
{disabled ? (
<ButtonWrapper
disabled
variant="secondary"
className={bsVersion({
bs3: 'btn btn-secondary-info btn-secondary text-capitalize',
bs5: 'text-capitalize',
})}
>
{t('link')}
</ButtonWrapper>
) : (
<ButtonWrapper
variant="secondary"
href={linkPath}
className={bsVersion({
bs3: 'btn btn-secondary-info btn-secondary text-capitalize',
bs5: 'text-capitalize',
})}
bs3Props={{ bsStyle: null }}
onClick={() => trackLinkingClick(integration)}
>
{t('link')}
</ButtonWrapper>
)}
</>
)
}
}
type UnlinkConfirmModalProps = {
show: boolean
title: string
integration: string
content: string
unlinkPath: string
handleHide: () => void
}
function UnlinkConfirmationModal({
show,
title,
integration,
content,
unlinkPath,
handleHide,
}: UnlinkConfirmModalProps) {
const { t } = useTranslation()
const handleCancel = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault()
handleHide()
}
const handleConfirm = () => {
sendMB('unlink-integration-click', {
integration,
})
}
return (
<OLModal show={show} onHide={handleHide}>
<OLModalHeader closeButton>
<OLModalTitle>{title}</OLModalTitle>
</OLModalHeader>
<OLModalBody>
<p>{content}</p>
</OLModalBody>
<OLModalFooter>
<form action={unlinkPath} method="POST" className="form-inline">
<input type="hidden" name="_csrf" value={getMeta('ol-csrfToken')} />
<ButtonWrapper
variant="secondary"
onClick={handleCancel}
bs3Props={{
bsStyle: null,
className: 'btn-secondary-info btn-secondary',
}}
>
{t('cancel')}
</ButtonWrapper>
<ButtonWrapper
type="submit"
variant="danger-ghost"
bs3Props={{ bsStyle: null, className: 'btn-danger-ghost' }}
onClick={handleConfirm}
>
{t('unlink')}
</ButtonWrapper>
</form>
</OLModalFooter>
</OLModal>
)
}