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:
parent
0f98330455
commit
7bbf3ae991
5 changed files with 77 additions and 21 deletions
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -24,9 +24,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
.checkboxGroupContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.checkboxGroup {
|
||||
gap: _.unit(4);
|
||||
}
|
||||
|
||||
.proTag {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 2px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.groups {
|
||||
|
|
|
@ -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 && (
|
||||
|
|
Loading…
Reference in a new issue