0
Fork 0
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:
Xiao Yijun 2023-07-20 11:29:01 +08:00 committed by GitHub
parent 7e47881e21
commit ac2f492dbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 136 additions and 11 deletions

View file

@ -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;

View file

@ -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"