0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-07 23:01:25 -05:00

feat(console): add quota guard to protected app creation form (#5353)

This commit is contained in:
Charles Zhao 2024-01-31 14:40:55 +08:00 committed by GitHub
parent df92091106
commit 1d20a3a1ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 25 deletions

View file

@ -4,16 +4,22 @@ import { isValidSubdomain } from '@logto/shared/universal';
import { condString, conditional } from '@silverhand/essentials';
import classNames from 'classnames';
import { HTTPError } from 'ky';
import { useContext } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';
import useSWRImmutable from 'swr/immutable';
import ContactUsPhraseLink from '@/components/ContactUsPhraseLink';
import PlanName from '@/components/PlanName';
import QuotaGuardFooter from '@/components/QuotaGuardFooter';
import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import Button, { type Props as ButtonProps } from '@/ds-components/Button';
import FormField from '@/ds-components/FormField';
import TextInput from '@/ds-components/TextInput';
import useApi from '@/hooks/use-api';
import useApplicationsUsage from '@/hooks/use-applications-usage';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import * as styles from './index.module.scss';
@ -41,6 +47,9 @@ function ProtectedAppForm({
const { data } = useSWRImmutable<ProtectedAppsDomainConfig>(
isDevFeaturesEnabled && isCloud && 'api/systems/application'
);
const { currentPlan } = useContext(SubscriptionDataContext);
const { hasAppsReachedLimit } = useApplicationsUsage();
const { name: planName, quota } = currentPlan;
const defaultDomain = data?.protectedApps.defaultDomain ?? '';
const { navigate } = useTenantPathname();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
@ -166,17 +175,30 @@ function ProtectedAppForm({
</div>
</FormField>
</div>
<Button
className={classNames(
styles.submitButton,
buttonAlignment === 'left' && styles.leftAligned
)}
size={buttonSize}
type="primary"
title={buttonText}
isLoading={isSubmitting}
onClick={onSubmit}
/>
{hasAppsReachedLimit ? (
<QuotaGuardFooter>
<Trans
components={{
a: <ContactUsPhraseLink />,
planName: <PlanName name={planName} />,
}}
>
{t('upsell.paywall.applications', { count: quota.applicationsLimit ?? 0 })}
</Trans>
</QuotaGuardFooter>
) : (
<Button
className={classNames(
styles.submitButton,
buttonAlignment === 'left' && styles.leftAligned
)}
size={buttonSize}
type="primary"
title={buttonText}
isLoading={isSubmitting}
onClick={onSubmit}
/>
)}
</form>
);
}

View file

@ -17,7 +17,7 @@
.protectedApps {
display: flex;
flex-direction: column;
gap: _.unit(8);
gap: _.unit(3);
background: var(--color-success-container);
padding: _.unit(5) _.unit(9);
@ -31,6 +31,7 @@
.list {
display: flex;
flex-wrap: wrap;
gap: _.unit(8);
}
.app {

View file

@ -6,6 +6,7 @@ import useSWR from 'swr';
import OpenExternalLink from '@/components/OpenExternalLink';
import CopyToClipboard from '@/ds-components/CopyToClipboard';
import useApplicationsUsage from '@/hooks/use-applications-usage';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import ProtectedAppCard from '@/pages/Applications/components/ProtectedAppCard';
import ProtectedAppForm from '@/pages/Applications/components/ProtectedAppForm';
@ -16,7 +17,9 @@ function ProtectedAppCreationForm() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { getTo } = useTenantPathname();
const { data, mutate } = useSWR<Application[]>('api/applications?types=Protected');
const { hasAppsReachedLimit } = useApplicationsUsage();
const hasProtectedApps = !!data?.length;
const showCreationForm = !hasProtectedApps && !hasAppsReachedLimit;
const mutateApps = useCallback(
(app: Application) => {
@ -27,18 +30,20 @@ function ProtectedAppCreationForm() {
return (
<div className={styles.container}>
<ProtectedAppCard hasCreateButton={hasProtectedApps} onCreateSuccess={mutateApps} />
<div className={styles.separator} />
{!hasProtectedApps && (
<div className={styles.form}>
<ProtectedAppForm
hasDetailedInstructions
buttonAlignment="left"
buttonText="protected_app.form.create_protected_app"
buttonSize="medium"
onCreateSuccess={mutateApps}
/>
</div>
<ProtectedAppCard hasCreateButton={!showCreationForm} onCreateSuccess={mutateApps} />
{showCreationForm && (
<>
<div className={styles.separator} />
<div className={styles.form}>
<ProtectedAppForm
hasDetailedInstructions
buttonAlignment="left"
buttonText="protected_app.form.create_protected_app"
buttonSize="medium"
onCreateSuccess={mutateApps}
/>
</div>
</>
)}
{hasProtectedApps && (
<div className={styles.protectedApps}>