0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

feat(console): apply quota limit for custom domain (#4181)

This commit is contained in:
Xiao Yijun 2023-07-19 13:56:00 +08:00 committed by GitHub
parent cbefbd3f57
commit 3c51ecc29d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 107 additions and 6 deletions

View file

@ -3,6 +3,8 @@
.title {
@include _.section-head-1;
color: var(--color-neutral-variant-60);
display: flex;
align-items: center;
}
.description {

View file

@ -10,12 +10,13 @@ import * as styles from './index.module.scss';
type Props = {
title: AdminConsoleKey;
tag?: ReactNode;
description?: AdminConsoleKey;
learnMoreLink?: string;
children: ReactNode;
};
function FormCard({ title, description, learnMoreLink, children }: Props) {
function FormCard({ title, tag, description, learnMoreLink, children }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
return (
@ -24,6 +25,7 @@ function FormCard({ title, description, learnMoreLink, children }: Props) {
<>
<div className={styles.title}>
<DynamicT forKey={title} />
{tag}
</div>
{description && (
<div className={styles.description}>

View file

@ -0,0 +1,11 @@
@use '@/scss/underscore' as _;
.tag {
@include _.section-head-2;
display: inline-block;
background-color: var(--color-primary);
border-radius: 10px;
padding: 0 _.unit(1);
color: var(--color-white);
margin: 0 _.unit(1);
}

View file

@ -0,0 +1,13 @@
import DynamicT from '@/ds-components/DynamicT';
import * as styles from './index.module.scss';
function ProTag() {
return (
<div className={styles.tag}>
<DynamicT forKey="upsell.pro_tag" />
</div>
);
}
export default ProTag;

View file

@ -0,0 +1,21 @@
import { useMemo } from 'react';
import useCurrentSubscription from './use-current-subscription';
import useSubscriptionPlans from './use-subscription-plans';
const useCurrentSubscriptionPlan = () => {
const { data: subscription, error: fetchSubscriptionError } = useCurrentSubscription();
const { data: subscriptionPlans, error: fetchSubscriptionPlansError } = useSubscriptionPlans();
const currentPlan = useMemo(
() => subscriptionPlans?.find(({ id: planId }) => planId === subscription?.planId),
[subscription?.planId, subscriptionPlans]
);
return {
data: currentPlan,
error: fetchSubscriptionError ?? fetchSubscriptionPlansError,
};
};
export default useCurrentSubscriptionPlan;

View file

@ -17,10 +17,11 @@ type FormData = {
};
type Props = {
isCustomDomainEnabled: boolean;
onCustomDomainAdded: (domain: Domain) => void;
};
function AddDomainForm({ onCustomDomainAdded }: Props) {
function AddDomainForm({ isCustomDomainEnabled, onCustomDomainAdded }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
register,
@ -47,6 +48,7 @@ function AddDomainForm({ onCustomDomainAdded }: Props) {
return (
<div className={styles.addDomain}>
<TextInput
readOnly={!isCustomDomainEnabled}
className={styles.textInput}
placeholder={t('domain.custom.custom_domain_placeholder')}
error={errors.domain?.message}
@ -64,7 +66,7 @@ function AddDomainForm({ onCustomDomainAdded }: Props) {
type="primary"
title="domain.custom.add_domain"
isLoading={isSubmitting}
disabled={domainInput.length === 0}
disabled={domainInput.length === 0 || !isCustomDomainEnabled}
onClick={onSubmit}
/>
</div>

View file

@ -5,3 +5,7 @@
margin-top: _.unit(4);
}
}
.upsellNotification {
margin-top: _.unit(4);
}

View file

@ -1,8 +1,16 @@
import { withAppInsights } from '@logto/app-insights/react';
import { Trans, useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import FormCard from '@/components/FormCard';
import PageMeta from '@/components/PageMeta';
import ProTag from '@/components/ProTag';
import { contactEmailLink } from '@/consts';
import { isProduction } from '@/consts/env';
import FormField from '@/ds-components/FormField';
import InlineNotification from '@/ds-components/InlineNotification';
import TextLink from '@/ds-components/TextLink';
import useCurrentSubscriptionPlan from '@/hooks/use-current-subscription-plan';
import useCustomDomain from '@/hooks/use-custom-domain';
import useDocumentationUrl from '@/hooks/use-documentation-url';
@ -14,18 +22,34 @@ import DefaultDomain from './DefaultDomain';
import * as styles from './index.module.scss';
function TenantDomainSettings() {
const { data: customDomain, isLoading, mutate } = useCustomDomain(true);
const { data: customDomain, isLoading: isLoadingCustomDomain, mutate } = useCustomDomain(true);
const { data: currentPlan, error: fetchCurrentPlanError } = useCurrentSubscriptionPlan();
const isLoadingCurrentPlan = !currentPlan && !fetchCurrentPlanError;
const { getDocumentationUrl } = useDocumentationUrl();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const navigate = useNavigate();
if (isLoading) {
if (isLoadingCustomDomain || isLoadingCurrentPlan) {
return <Skeleton />;
}
const customDomainEnabled =
/**
* Todo: @xiaoyijun remove this condition on subscription features ready.
*/
isProduction ||
Boolean(currentPlan?.quota.customDomainEnabled) ||
/**
* Note: this is for tenants which already have a custom domain before we have subscription features.
*/
Boolean(customDomain);
return (
<div className={styles.container}>
<PageMeta titleKey={['tenants.tabs.domains', 'tenants.title']} />
<FormCard
title="domain.custom.custom_domain"
tag={!isProduction && <ProTag />}
description="domain.custom.custom_domain_description"
learnMoreLink={getDocumentationUrl('docs/recipes/custom-domain')}
>
@ -33,7 +57,29 @@ function TenantDomainSettings() {
{customDomain ? (
<CustomDomain customDomain={customDomain} onDeleteCustomDomain={mutate} />
) : (
<AddDomainForm onCustomDomainAdded={mutate} />
<AddDomainForm
isCustomDomainEnabled={customDomainEnabled}
onCustomDomainAdded={mutate}
/>
)}
{!isProduction && !customDomain && !customDomainEnabled && (
<InlineNotification
hasIcon={false}
severity="info"
action="upsell.compare_plans"
className={styles.upsellNotification}
onClick={() => {
navigate('/tenant-settings/subscription');
}}
>
<Trans
components={{
a: <TextLink href={contactEmailLink} target="_blank" />,
}}
>
{t('upsell.paywall.custom_domain')}
</Trans>
</InlineNotification>
)}
</FormField>
</FormCard>