mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(console): apply quota limit for connector creation (#4190)
This commit is contained in:
parent
7e47881e21
commit
ac2f492dbe
2 changed files with 136 additions and 11 deletions
|
@ -0,0 +1,117 @@
|
|||
import {
|
||||
ConnectorType,
|
||||
type ConnectorResponse,
|
||||
type ConnectorFactoryResponse,
|
||||
} from '@logto/schemas';
|
||||
import { useMemo } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import ContactUsPhraseLink from '@/components/ContactUsPhraseLink';
|
||||
import PlanName from '@/components/PlanName';
|
||||
import QuotaGuardFooter from '@/components/QuotaGuardFooter';
|
||||
import { ReservedPlanId } from '@/consts/subscriptions';
|
||||
import Button from '@/ds-components/Button';
|
||||
import useCurrentSubscriptionPlan from '@/hooks/use-current-subscription-plan';
|
||||
import { type ConnectorGroup } from '@/types/connector';
|
||||
import { isOverQuota } from '@/utils/quota';
|
||||
|
||||
type Props = {
|
||||
isCreatingSocialConnector: boolean;
|
||||
existingConnectors: ConnectorResponse[];
|
||||
selectedConnectorGroup?: ConnectorGroup<ConnectorFactoryResponse>;
|
||||
isCreateButtonDisabled: boolean;
|
||||
onClickCreateButton: () => void;
|
||||
};
|
||||
|
||||
function Footer({
|
||||
isCreatingSocialConnector,
|
||||
existingConnectors,
|
||||
selectedConnectorGroup,
|
||||
isCreateButtonDisabled,
|
||||
onClickCreateButton,
|
||||
}: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.upsell.paywall' });
|
||||
const { data: currentPlan } = useCurrentSubscriptionPlan();
|
||||
|
||||
const standardConnectorCount = useMemo(
|
||||
() =>
|
||||
existingConnectors.filter(
|
||||
({ isStandard, isDemo, type }) => isStandard && !isDemo && type === ConnectorType.Social
|
||||
).length,
|
||||
[existingConnectors]
|
||||
);
|
||||
|
||||
const socialConnectorCount = useMemo(
|
||||
() =>
|
||||
existingConnectors.filter(
|
||||
({ isStandard, isDemo, type }) => !isStandard && !isDemo && type === ConnectorType.Social
|
||||
).length,
|
||||
[existingConnectors]
|
||||
);
|
||||
|
||||
const isStandardConnectorsOverQuota = isOverQuota({
|
||||
quotaKey: 'standardConnectorsLimit',
|
||||
plan: currentPlan,
|
||||
usage: standardConnectorCount,
|
||||
});
|
||||
|
||||
const isSocialConnectorsOverQuota = isOverQuota({
|
||||
quotaKey: 'socialConnectorsLimit',
|
||||
plan: currentPlan,
|
||||
usage: socialConnectorCount,
|
||||
});
|
||||
|
||||
if (isCreatingSocialConnector && currentPlan && selectedConnectorGroup) {
|
||||
const { id: planId, name: planName, quota } = currentPlan;
|
||||
|
||||
if (isStandardConnectorsOverQuota && selectedConnectorGroup.isStandard) {
|
||||
return (
|
||||
<QuotaGuardFooter>
|
||||
<Trans
|
||||
components={{
|
||||
a: <ContactUsPhraseLink />,
|
||||
planName: <PlanName name={planName} />,
|
||||
}}
|
||||
>
|
||||
{quota.standardConnectorsLimit === 0
|
||||
? t('standard_connectors_feature')
|
||||
: t(
|
||||
planId === ReservedPlanId.pro ? 'standard_connectors_pro' : 'standard_connectors',
|
||||
{
|
||||
count: quota.standardConnectorsLimit,
|
||||
}
|
||||
)}
|
||||
</Trans>
|
||||
</QuotaGuardFooter>
|
||||
);
|
||||
}
|
||||
|
||||
if (isSocialConnectorsOverQuota && !selectedConnectorGroup.isStandard) {
|
||||
return (
|
||||
<QuotaGuardFooter>
|
||||
<Trans
|
||||
components={{
|
||||
a: <ContactUsPhraseLink />,
|
||||
planName: <PlanName name={planName} />,
|
||||
}}
|
||||
>
|
||||
{t('social_connectors', {
|
||||
count: quota.socialConnectorsLimit ?? 0,
|
||||
})}
|
||||
</Trans>
|
||||
</QuotaGuardFooter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
title="general.next"
|
||||
type="primary"
|
||||
disabled={isCreateButtonDisabled}
|
||||
onClick={onClickCreateButton}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
|
@ -7,15 +7,17 @@ import { useMemo, useState } from 'react';
|
|||
import Modal from 'react-modal';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import Button from '@/ds-components/Button';
|
||||
import { isProduction } from '@/consts/env';
|
||||
import DynamicT from '@/ds-components/DynamicT';
|
||||
import ModalLayout from '@/ds-components/ModalLayout';
|
||||
import type { RequestError } from '@/hooks/use-api';
|
||||
import * as modalStyles from '@/scss/modal.module.scss';
|
||||
|
||||
import { getConnectorGroups } from '../../pages/Connectors/utils';
|
||||
import ProTag from '../ProTag';
|
||||
|
||||
import ConnectorRadioGroup from './ConnectorRadioGroup';
|
||||
import Footer from './Footer';
|
||||
import PlatformSelector from './PlatformSelector';
|
||||
import Skeleton from './Skeleton';
|
||||
import * as styles from './index.module.scss';
|
||||
|
@ -39,6 +41,7 @@ function CreateConnectorForm({ onClose, isOpen: isFormOpen, type }: Props) {
|
|||
const isLoading = !factories && !existingConnectors && !connectorsError && !factoriesError;
|
||||
const [activeGroupId, setActiveGroupId] = useState<string>();
|
||||
const [activeFactoryId, setActiveFactoryId] = useState<string>();
|
||||
const isCreatingSocialConnector = type === ConnectorType.Social;
|
||||
|
||||
const groups = useMemo(() => {
|
||||
if (!factories || !existingConnectors) {
|
||||
|
@ -90,8 +93,8 @@ function CreateConnectorForm({ onClose, isOpen: isFormOpen, type }: Props) {
|
|||
};
|
||||
|
||||
const defaultGroups = useMemo(
|
||||
() => (type === ConnectorType.Social ? groups.filter((group) => !group.isStandard) : groups),
|
||||
[groups, type]
|
||||
() => (isCreatingSocialConnector ? groups.filter((group) => !group.isStandard) : groups),
|
||||
[groups, isCreatingSocialConnector]
|
||||
);
|
||||
|
||||
const standardGroups = useMemo(() => groups.filter((group) => group.isStandard), [groups]);
|
||||
|
@ -113,14 +116,17 @@ function CreateConnectorForm({ onClose, isOpen: isFormOpen, type }: Props) {
|
|||
<ModalLayout
|
||||
title={cardTitle}
|
||||
footer={
|
||||
<Button
|
||||
title="general.next"
|
||||
type="primary"
|
||||
disabled={!activeFactoryId}
|
||||
onClick={() => {
|
||||
onClose?.(activeFactoryId);
|
||||
}}
|
||||
/>
|
||||
existingConnectors && (
|
||||
<Footer
|
||||
isCreatingSocialConnector={isCreatingSocialConnector}
|
||||
existingConnectors={existingConnectors}
|
||||
selectedConnectorGroup={activeGroup}
|
||||
isCreateButtonDisabled={!activeFactoryId}
|
||||
onClickCreateButton={() => {
|
||||
onClose?.(activeFactoryId);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
size={radioGroupSize}
|
||||
onClose={onClose}
|
||||
|
@ -138,6 +144,8 @@ function CreateConnectorForm({ onClose, isOpen: isFormOpen, type }: Props) {
|
|||
<>
|
||||
<div className={styles.standardLabel}>
|
||||
<DynamicT forKey="connectors.standard_connectors" />
|
||||
{/* Todo: @xiaoyijun remove this condition on subscription features ready. */}
|
||||
{!isProduction && <ProTag />}
|
||||
</div>
|
||||
<ConnectorRadioGroup
|
||||
name="group"
|
||||
|
|
Loading…
Reference in a new issue