0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

refactor(console): refactor tag component (#6453)

This commit is contained in:
Darcy Ye 2024-08-16 14:11:47 +08:00 committed by GitHub
parent 9674d5c8f0
commit 26b976a828
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 56 additions and 71 deletions

View file

@ -128,6 +128,7 @@ function CreateForm({
planId === ReservedPlanId.Pro && planId === ReservedPlanId.Pro &&
ReservedPlanId.Pro ReservedPlanId.Pro
)} )}
hasAddOnTag={isDevFeaturesEnabled && watch('type') === ApplicationType.MachineToMachine}
size={defaultCreateType ? 'medium' : 'large'} size={defaultCreateType ? 'medium' : 'large'}
footer={ footer={
<Footer <Footer

View file

@ -1,19 +0,0 @@
import classNames from 'classnames';
/**
* AddOnTag static component
*
* Used to indicate that a feature is add-on feature and will be charged according to usage.
*/
import styles from './index.module.scss';
type Props = {
readonly className?: string;
};
function AddOnTag({ className }: Props) {
return <div className={classNames(styles.tag, styles.beta, className)}>Add-on</div>;
}
export default AddOnTag;

View file

@ -2,11 +2,10 @@ import { ReservedPlanId } from '@logto/schemas';
import classNames from 'classnames'; import classNames from 'classnames';
import { useContext } from 'react'; import { useContext } from 'react';
import { isDevFeaturesEnabled } from '@/consts/env'; import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
import { TenantsContext } from '@/contexts/TenantsProvider'; import { TenantsContext } from '@/contexts/TenantsProvider';
import AddOnTag from './AddOnTag';
import styles from './index.module.scss'; import styles from './index.module.scss';
export { default as BetaTag } from './BetaTag'; export { default as BetaTag } from './BetaTag';
@ -53,9 +52,6 @@ export type Props = {
function FeatureTag(props: Props) { function FeatureTag(props: Props) {
const { className } = props; const { className } = props;
const { isDevTenant } = useContext(TenantsContext); const { isDevTenant } = useContext(TenantsContext);
const {
currentSubscription: { planId },
} = useContext(SubscriptionDataContext);
const { isVisible, plan } = props; const { isVisible, plan } = props;
@ -65,12 +61,36 @@ function FeatureTag(props: Props) {
return null; return null;
} }
// Show the add-on tag for Pro plan when dev features are enabled.
if (isDevFeaturesEnabled && planId === ReservedPlanId.Pro) {
return <AddOnTag className={className} />;
}
return <div className={classNames(styles.tag, className)}>{plan}</div>; return <div className={classNames(styles.tag, className)}>{plan}</div>;
} }
export default FeatureTag; export default FeatureTag;
type CombinedAddOnAndFeatureTagProps = {
readonly hasAddOnTag?: boolean;
readonly className?: string;
/** The minimum plan required to use the feature. */
readonly paywall?: Props['plan'];
};
/**
* When `hasAddOnTag` is `true`, the tag will be `AddOnTag` if the plan is `ReservedPlanId.Pro`
* and dev features are enabled. Otherwise, it will be `FeatureTag` with the `paywall` prop.
*/
export function CombinedAddOnAndFeatureTag(props: CombinedAddOnAndFeatureTagProps) {
const { hasAddOnTag, className, paywall } = props;
const {
currentSubscription: { planId },
} = useContext(SubscriptionDataContext);
// Show the "Add-on" tag for Pro plan when dev features enabled.
if (hasAddOnTag && isDevFeaturesEnabled && isCloud && planId === ReservedPlanId.Pro) {
return <div className={classNames(styles.tag, styles.beta, className)}>Add-on</div>;
}
if (paywall && isCloud) {
return <FeatureTag isVisible plan={paywall} />;
}
return null;
}

View file

@ -4,8 +4,7 @@ import classNames from 'classnames';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import FeatureTag from '@/components/FeatureTag'; import { CombinedAddOnAndFeatureTag } from '@/components/FeatureTag';
import { isCloud } from '@/consts/env';
import type { Props as TextLinkProps } from '@/ds-components/TextLink'; import type { Props as TextLinkProps } from '@/ds-components/TextLink';
import type DangerousRaw from '../DangerousRaw'; import type DangerousRaw from '../DangerousRaw';
@ -27,6 +26,7 @@ export type Props = {
* If not provided, no paywall tag will be shown. * If not provided, no paywall tag will be shown.
*/ */
readonly paywall?: Exclude<ReservedPlanId, ReservedPlanId.Free | ReservedPlanId.Development>; readonly paywall?: Exclude<ReservedPlanId, ReservedPlanId.Free | ReservedPlanId.Development>;
readonly hasAddOnTag?: boolean;
}; };
/** /**
@ -40,6 +40,7 @@ function CardTitle({
learnMoreLink, learnMoreLink,
className, className,
paywall, paywall,
hasAddOnTag,
}: Props) { }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
@ -47,7 +48,7 @@ function CardTitle({
<div className={classNames(styles.container, styles[size], className)}> <div className={classNames(styles.container, styles[size], className)}>
<div className={classNames(styles.title, !isWordWrapEnabled && styles.titleEllipsis)}> <div className={classNames(styles.title, !isWordWrapEnabled && styles.titleEllipsis)}>
{typeof title === 'string' ? <DynamicT forKey={title} /> : title} {typeof title === 'string' ? <DynamicT forKey={title} /> : title}
{paywall && isCloud && <FeatureTag isVisible plan={paywall} />} <CombinedAddOnAndFeatureTag hasAddOnTag={hasAddOnTag} paywall={paywall} />
</div> </div>
{Boolean(subtitle ?? learnMoreLink) && ( {Boolean(subtitle ?? learnMoreLink) && (
<div className={styles.subtitle}> <div className={styles.subtitle}>

View file

@ -17,7 +17,10 @@ export type Props = {
readonly className?: string; readonly className?: string;
readonly size?: 'medium' | 'large' | 'xlarge'; readonly size?: 'medium' | 'large' | 'xlarge';
readonly headerIcon?: ReactElement; readonly headerIcon?: ReactElement;
} & Pick<CardTitleProps, 'learnMoreLink' | 'title' | 'subtitle' | 'isWordWrapEnabled' | 'paywall'>; } & Pick<
CardTitleProps,
'learnMoreLink' | 'title' | 'subtitle' | 'isWordWrapEnabled' | 'paywall' | 'hasAddOnTag'
>;
function ModalLayout({ function ModalLayout({
children, children,

View file

@ -68,8 +68,9 @@ function CreateForm({ onClose }: Props) {
title="api_resources.create" title="api_resources.create"
subtitle="api_resources.subtitle" subtitle="api_resources.subtitle"
paywall={conditional( paywall={conditional(
isDevFeaturesEnabled && planId === ReservedPlanId.Pro && ReservedPlanId.Pro isDevFeaturesEnabled && planId !== ReservedPlanId.Pro && ReservedPlanId.Pro
)} )}
hasAddOnTag={isDevFeaturesEnabled}
footer={<Footer isCreationLoading={isSubmitting} onClickCreate={onSubmit} />} footer={<Footer isCreationLoading={isSubmitting} onClickCreate={onSubmit} />}
onClose={onClose} onClose={onClose}
> >

View file

@ -155,7 +155,7 @@ function SsoCreationModal({ isOpen, onClose: rawOnClose }: Props) {
<ModalLayout <ModalLayout
title="enterprise_sso.create_modal.title" title="enterprise_sso.create_modal.title"
paywall={conditional( paywall={conditional(
isDevFeaturesEnabled && planId === ReservedPlanId.Pro && ReservedPlanId.Pro isDevFeaturesEnabled && planId !== ReservedPlanId.Pro && ReservedPlanId.Pro
)} )}
footer={ footer={
conditional( conditional(

View file

@ -36,11 +36,7 @@ function EnterpriseSso() {
const { navigate } = useTenantPathname(); const { navigate } = useTenantPathname();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { isDevTenant } = useContext(TenantsContext); const { isDevTenant } = useContext(TenantsContext);
const { const { currentPlan, currentSubscriptionQuota } = useContext(SubscriptionDataContext);
currentPlan,
currentSubscription: { planId },
currentSubscriptionQuota,
} = useContext(SubscriptionDataContext);
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({ const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
page: 1, page: 1,
@ -67,11 +63,10 @@ function EnterpriseSso() {
return ( return (
<ListPage <ListPage
title={{ title={{
paywall: isDevFeaturesEnabled paywall: conditional((!isSsoEnabled || isDevTenant) && ReservedPlanId.Pro),
? conditional(planId === ReservedPlanId.Pro && ReservedPlanId.Pro)
: conditional((!isSsoEnabled || isDevTenant) && ReservedPlanId.Pro),
title: 'enterprise_sso.title', title: 'enterprise_sso.title',
subtitle: 'enterprise_sso.subtitle', subtitle: 'enterprise_sso.subtitle',
hasAddOnTag: isDevFeaturesEnabled,
}} }}
pageMeta={{ titleKey: 'enterprise_sso.page_title' }} pageMeta={{ titleKey: 'enterprise_sso.page_title' }}
createButton={conditional( createButton={conditional(

View file

@ -18,7 +18,6 @@ function PageWrapper({ children }: Props) {
const { isDevTenant } = useContext(TenantsContext); const { isDevTenant } = useContext(TenantsContext);
const { const {
currentPlan, currentPlan,
currentSubscription: { planId },
currentSubscriptionQuota: { mfaEnabled }, currentSubscriptionQuota: { mfaEnabled },
} = useContext(SubscriptionDataContext); } = useContext(SubscriptionDataContext);
const isMfaEnabled = const isMfaEnabled =
@ -28,11 +27,8 @@ function PageWrapper({ children }: Props) {
<div className={styles.container}> <div className={styles.container}>
<PageMeta titleKey="mfa.title" /> <PageMeta titleKey="mfa.title" />
<CardTitle <CardTitle
paywall={ paywall={cond((!isMfaEnabled || isDevTenant) && ReservedPlanId.Pro)}
isDevFeaturesEnabled hasAddOnTag={isDevFeaturesEnabled}
? cond(planId === ReservedPlanId.Pro && ReservedPlanId.Pro)
: cond((!isMfaEnabled || isDevTenant) && ReservedPlanId.Pro)
}
title="mfa.title" title="mfa.title"
subtitle="mfa.description" subtitle="mfa.description"
className={styles.cardTitle} className={styles.cardTitle}

View file

@ -32,11 +32,7 @@ const basePathname = '/organization-template';
function OrganizationTemplate() { function OrganizationTemplate() {
const { getDocumentationUrl } = useDocumentationUrl(); const { getDocumentationUrl } = useDocumentationUrl();
const [isGuideDrawerOpen, setIsGuideDrawerOpen] = useState(false); const [isGuideDrawerOpen, setIsGuideDrawerOpen] = useState(false);
const { const { currentPlan, currentSubscriptionQuota } = useContext(SubscriptionDataContext);
currentPlan,
currentSubscription: { planId },
currentSubscriptionQuota,
} = useContext(SubscriptionDataContext);
const { isDevTenant } = useContext(TenantsContext); const { isDevTenant } = useContext(TenantsContext);
const isOrganizationsDisabled = const isOrganizationsDisabled =
isCloud && isCloud &&
@ -60,11 +56,7 @@ function OrganizationTemplate() {
href: getDocumentationUrl(organizationTemplateLink), href: getDocumentationUrl(organizationTemplateLink),
targetBlank: 'noopener', targetBlank: 'noopener',
}} }}
paywall={ paywall={cond((isOrganizationsDisabled || isDevTenant) && ReservedPlanId.Pro)}
isDevFeaturesEnabled
? cond(planId === ReservedPlanId.Pro && ReservedPlanId.Pro)
: cond((isOrganizationsDisabled || isDevTenant) && ReservedPlanId.Pro)
}
/> />
<Button <Button
title="application_details.check_guide" title="application_details.check_guide"

View file

@ -82,8 +82,9 @@ function CreateOrganizationModal({ isOpen, onClose }: Props) {
<ModalLayout <ModalLayout
title="organizations.create_organization" title="organizations.create_organization"
paywall={conditional( paywall={conditional(
isDevFeaturesEnabled && planId === ReservedPlanId.Pro && ReservedPlanId.Pro isDevFeaturesEnabled && planId !== ReservedPlanId.Pro && ReservedPlanId.Pro
)} )}
hasAddOnTag={isDevFeaturesEnabled}
footer={ footer={
cond( cond(
isDevFeaturesEnabled && isDevFeaturesEnabled &&

View file

@ -25,11 +25,7 @@ const organizationsPathname = '/organizations';
function Organizations() { function Organizations() {
const { getDocumentationUrl } = useDocumentationUrl(); const { getDocumentationUrl } = useDocumentationUrl();
const { const { currentPlan, currentSubscriptionQuota } = useContext(SubscriptionDataContext);
currentPlan,
currentSubscription: { planId },
currentSubscriptionQuota,
} = useContext(SubscriptionDataContext);
const { isDevTenant } = useContext(TenantsContext); const { isDevTenant } = useContext(TenantsContext);
const { navigate } = useTenantPathname(); const { navigate } = useTenantPathname();
@ -64,11 +60,8 @@ function Organizations() {
<PageMeta titleKey="organizations.page_title" /> <PageMeta titleKey="organizations.page_title" />
<div className={pageLayout.headline}> <div className={pageLayout.headline}>
<CardTitle <CardTitle
paywall={ paywall={cond((isOrganizationsDisabled || isDevTenant) && ReservedPlanId.Pro)}
isDevFeaturesEnabled hasAddOnTag={isDevFeaturesEnabled}
? cond(planId === ReservedPlanId.Pro && ReservedPlanId.Pro)
: cond((isOrganizationsDisabled || isDevTenant) && ReservedPlanId.Pro)
}
title="organizations.title" title="organizations.title"
subtitle="organizations.subtitle" subtitle="organizations.subtitle"
learnMoreLink={{ learnMoreLink={{

View file

@ -130,8 +130,9 @@ function InviteMemberModal({ isOpen, onClose }: Props) {
size="large" size="large"
title="tenant_members.invite_modal.title" title="tenant_members.invite_modal.title"
paywall={conditional( paywall={conditional(
isDevFeaturesEnabled && planId === ReservedPlanId.Pro && ReservedPlanId.Pro isDevFeaturesEnabled && planId !== ReservedPlanId.Pro && ReservedPlanId.Pro
)} )}
hasAddOnTag={isDevFeaturesEnabled}
subtitle="tenant_members.invite_modal.subtitle" subtitle="tenant_members.invite_modal.subtitle"
footer={ footer={
conditional( conditional(