0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat(console): upsell m2m feature on guide card and library filter (#4337)

feat(console): upsell m2m feature on guide card and guide library filters
This commit is contained in:
Charles Zhao 2023-08-16 22:40:30 -05:00 committed by GitHub
parent 0f98330455
commit 7bbf3ae991
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 21 deletions

View file

@ -1,10 +1,16 @@
import classNames from 'classnames';
import DynamicT from '@/ds-components/DynamicT';
import * as styles from './index.module.scss';
function ProTag() {
type Props = {
className?: string;
};
function ProTag({ className }: Props) {
return (
<div className={styles.tag}>
<div className={classNames(styles.tag, className)}>
<DynamicT forKey="upsell.pro_tag" />
</div>
);

View file

@ -34,9 +34,16 @@
gap: _.unit(1);
}
.flexRow {
display: flex;
align-items: center;
justify-content: space-between;
}
.name {
font: var(--font-label-2);
color: var(--color-text);
margin-right: _.unit(1);
}
.description {

View file

@ -1,8 +1,15 @@
import { ApplicationType } from '@logto/schemas';
import classNames from 'classnames';
import { Suspense } from 'react';
import { Suspense, useContext } from 'react';
import { type Guide, type GuideMetadata } from '@/assets/docs/guides/types';
import ProTag from '@/components/ProTag';
import { isCloud } from '@/consts/env';
import { subscriptionPage } from '@/consts/pages';
import { TenantsContext } from '@/contexts/TenantsProvider';
import Button from '@/ds-components/Button';
import useSubscriptionPlan from '@/hooks/use-subscription-plan';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import * as styles from './index.module.scss';
@ -22,6 +29,13 @@ function LogoSkeleton() {
}
function GuideCard({ data, onClick, hasBorder }: Props) {
const { navigate } = useTenantPathname();
const { currentTenantId } = useContext(TenantsContext);
const { data: currentPlan } = useSubscriptionPlan(currentTenantId);
const isM2mDisabled = isCloud && currentPlan?.quota.machineToMachineLimit === 0;
const isSubscriptionRequired =
isM2mDisabled && data.metadata.target === ApplicationType.MachineToMachine;
const {
id,
Logo,
@ -35,15 +49,22 @@ function GuideCard({ data, onClick, hasBorder }: Props) {
<Logo className={styles.logo} />
</Suspense>
<div className={styles.infoWrapper}>
<div className={styles.name}>{name}</div>
<div className={styles.flexRow}>
<div className={styles.name}>{name}</div>
{isSubscriptionRequired && <ProTag />}
</div>
<div className={styles.description}>{description}</div>
</div>
</div>
<Button
title="applications.guide.start_building"
title={isSubscriptionRequired ? 'upsell.upgrade_plan' : 'applications.guide.start_building'}
size="small"
onClick={() => {
onClick({ target, id });
if (isSubscriptionRequired) {
navigate(subscriptionPage);
} else {
onClick({ target, id });
}
}}
/>
</div>

View file

@ -24,9 +24,20 @@
}
}
.checkboxGroupContainer {
position: relative;
}
.checkboxGroup {
gap: _.unit(4);
}
.proTag {
position: absolute;
right: 0;
bottom: 2px;
z-index: 1;
}
}
.groups {

View file

@ -1,13 +1,17 @@
import { type Application } from '@logto/schemas';
import classNames from 'classnames';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useContext, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { type Guide } from '@/assets/docs/guides/types';
import SearchIcon from '@/assets/icons/search.svg';
import ProTag from '@/components/ProTag';
import { isCloud } from '@/consts/env';
import { TenantsContext } from '@/contexts/TenantsProvider';
import { CheckboxGroup } from '@/ds-components/Checkbox';
import OverlayScrollbar from '@/ds-components/OverlayScrollbar';
import TextInput from '@/ds-components/TextInput';
import useSubscriptionPlan from '@/hooks/use-subscription-plan';
import useTenantPathname from '@/hooks/use-tenant-pathname';
import { allAppGuideCategories, type AppGuideCategory } from '@/types/applications';
@ -56,6 +60,10 @@ function GuideLibrary({ className, hasFilter, hasCardBorder }: Props) {
const [getFilteredMetadata, getStructuredMetadata] = useAppGuideMetadata();
const [showCreateForm, setShowCreateForm] = useState<boolean>(false);
const { currentTenantId } = useContext(TenantsContext);
const { data: currentPlan } = useSubscriptionPlan(currentTenantId);
const isM2mDisabledForCurrentPlan = isCloud && currentPlan?.quota.machineToMachineLimit === 0;
const structuredMetadata = useMemo(
() => getStructuredMetadata({ categories: filterCategories }),
[getStructuredMetadata, filterCategories]
@ -97,20 +105,23 @@ function GuideLibrary({ className, hasFilter, hasCardBorder }: Props) {
setKeyword(event.currentTarget.value);
}}
/>
<CheckboxGroup
className={styles.checkboxGroup}
options={allAppGuideCategories.map((category) => ({
title: `applications.guide.categories.${category}`,
value: category,
}))}
value={filterCategories}
onChange={(value) => {
const sortedValue = allAppGuideCategories.filter((category) =>
value.includes(category)
);
setFilterCategories(sortedValue);
}}
/>
<div className={styles.checkboxGroupContainer}>
<CheckboxGroup
className={styles.checkboxGroup}
options={allAppGuideCategories.map((category) => ({
title: `applications.guide.categories.${category}`,
value: category,
}))}
value={filterCategories}
onChange={(value) => {
const sortedValue = allAppGuideCategories.filter((category) =>
value.includes(category)
);
setFilterCategories(sortedValue);
}}
/>
{isM2mDisabledForCurrentPlan && <ProTag className={styles.proTag} />}
</div>
</div>
)}
{keyword && (