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:
parent
cbefbd3f57
commit
3c51ecc29d
8 changed files with 107 additions and 6 deletions
|
@ -3,6 +3,8 @@
|
|||
.title {
|
||||
@include _.section-head-1;
|
||||
color: var(--color-neutral-variant-60);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
|
|
|
@ -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}>
|
||||
|
|
11
packages/console/src/components/ProTag/index.module.scss
Normal file
11
packages/console/src/components/ProTag/index.module.scss
Normal 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);
|
||||
}
|
13
packages/console/src/components/ProTag/index.tsx
Normal file
13
packages/console/src/components/ProTag/index.tsx
Normal 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;
|
21
packages/console/src/hooks/use-current-subscription-plan.ts
Normal file
21
packages/console/src/hooks/use-current-subscription-plan.ts
Normal 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;
|
|
@ -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>
|
||||
|
|
|
@ -5,3 +5,7 @@
|
|||
margin-top: _.unit(4);
|
||||
}
|
||||
}
|
||||
|
||||
.upsellNotification {
|
||||
margin-top: _.unit(4);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue