mirror of
https://github.com/logto-io/logto.git
synced 2025-03-24 22:41:28 -05:00
chore(console): add display pricing change notice and update plan comparison table (#6456)
* chore: add pricing change notice * chore: update plan comparison table
This commit is contained in:
parent
f17526c612
commit
36f1055314
21 changed files with 122 additions and 19 deletions
|
@ -12,6 +12,7 @@ export const proPlanAuditLogsRetentionDays = 14;
|
|||
|
||||
// TODO: currently we do not provide a good way to retrieve add-on items unit price in console, we hence manually defined the unit price here, will implement the API soon.
|
||||
/* === Add-on unit price (in USD) === */
|
||||
export const proPlanBasePrice = 16;
|
||||
export const resourceAddOnUnitPrice = 4;
|
||||
export const machineToMachineAddOnUnitPrice = 8;
|
||||
export const tenantMembersAddOnUnitPrice = 8;
|
||||
|
|
|
@ -26,6 +26,7 @@ const userPreferencesGuard = z.object({
|
|||
organizationUpsellNoticeAcknowledged: z.boolean().optional(),
|
||||
tenantMembersUpsellNoticeAcknowledged: z.boolean().optional(),
|
||||
enterpriseSsoUpsellNoticeAcknowledged: z.boolean().optional(),
|
||||
addOnChangesInCurrentCycleNoticeAcknowledged: z.boolean().optional(),
|
||||
/* === Add on feature related fields === */
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import { ReservedPlanId } from '@logto/schemas';
|
||||
import { useContext } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { proPlanBasePrice } from '@/consts/subscriptions';
|
||||
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
|
||||
import InlineNotification from '@/ds-components/InlineNotification';
|
||||
import TextLink from '@/ds-components/TextLink';
|
||||
import useUserPreferences from '@/hooks/use-user-preferences';
|
||||
|
||||
type Props = {
|
||||
readonly className?: string;
|
||||
};
|
||||
|
||||
function AddOnUsageChangesNotification({ className }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const {
|
||||
currentSubscription: { planId },
|
||||
} = useContext(SubscriptionDataContext);
|
||||
const {
|
||||
data: { addOnChangesInCurrentCycleNoticeAcknowledged },
|
||||
update,
|
||||
} = useUserPreferences();
|
||||
|
||||
if (planId !== ReservedPlanId.Pro || addOnChangesInCurrentCycleNoticeAcknowledged) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<InlineNotification
|
||||
action="general.got_it"
|
||||
className={className}
|
||||
onClick={() => {
|
||||
void update({ addOnChangesInCurrentCycleNoticeAcknowledged: true });
|
||||
}}
|
||||
>
|
||||
<Trans
|
||||
components={{
|
||||
a: <TextLink to="https://blog.logto.io/pricing-add-ons/" />,
|
||||
}}
|
||||
>
|
||||
{t('subscription.usage.pricing.add_on_changes_in_current_cycle_notice', {
|
||||
price: proPlanBasePrice,
|
||||
})}
|
||||
</Trans>
|
||||
</InlineNotification>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddOnUsageChangesNotification;
|
|
@ -15,6 +15,7 @@ import FormField from '@/ds-components/FormField';
|
|||
import { type SubscriptionPlan } from '@/types/subscriptions';
|
||||
import { hasSurpassedQuotaLimit, hasSurpassedSubscriptionQuotaLimit } from '@/utils/quota';
|
||||
|
||||
import AddOnUsageChangesNotification from './AddOnUsageChangesNotification';
|
||||
import MauLimitExceedNotification from './MauLimitExceededNotification';
|
||||
import PaymentOverdueNotification from './PaymentOverdueNotification';
|
||||
import styles from './index.module.scss';
|
||||
|
@ -94,6 +95,7 @@ function CurrentPlan({ subscription, subscriptionPlan, periodicUsage: rawPeriodi
|
|||
<FormField title="subscription.next_bill">
|
||||
<BillInfo cost={upcomingCost} isManagePaymentVisible={Boolean(upcomingCost)} />
|
||||
</FormField>
|
||||
<AddOnUsageChangesNotification className={styles.notification} />
|
||||
<MauLimitExceedNotification
|
||||
currentPlan={subscriptionPlan}
|
||||
periodicUsage={rawPeriodicUsage}
|
||||
|
|
|
@ -126,11 +126,20 @@ function PlanComparisonTable() {
|
|||
const tenantMembersLimit = t('included', { value: 3 });
|
||||
const tenantMembersPrice = t('per_member', { value: 8 });
|
||||
|
||||
// Compliance and support
|
||||
// Support
|
||||
const community = t('support.community');
|
||||
const emailTicketSupport = t('support.email_ticket_support');
|
||||
const soc2Report = t('support.soc2_report');
|
||||
const hipaaOrBaaReport = t('support.hipaa_or_baa_report');
|
||||
const discordPrivateChannel = t('support.discord_private_channel');
|
||||
const premiumSupport = t('support.premium_support');
|
||||
const developerOnboarding = t('support.developer_onboarding');
|
||||
const solutionEngineerSupport = t('support.solution_engineer_support');
|
||||
const sla = t('support.sla');
|
||||
const dedicatedComputingResources = t('support.dedicated_computing_resources');
|
||||
|
||||
// Compliance
|
||||
const soc2Compliant = t('compliance.soc2_compliant');
|
||||
const soc2Report = t('compliance.soc2_report');
|
||||
const hipaaOrBaaReport = t('compliance.hipaa_or_baa_report');
|
||||
|
||||
return [
|
||||
{
|
||||
|
@ -234,8 +243,20 @@ function PlanComparisonTable() {
|
|||
rows: [
|
||||
{ name: community, data: ['✓', '✓', '✓'] },
|
||||
{ name: emailTicketSupport, data: ['-', '✓ (48h)', contact] },
|
||||
{ name: discordPrivateChannel, data: ['-', '✓', '✓'] },
|
||||
{ name: premiumSupport, data: ['-', '-', '✓'] },
|
||||
{ name: developerOnboarding, data: ['-', '-', '✓'] },
|
||||
{ name: solutionEngineerSupport, data: ['-', '-', '✓'] },
|
||||
{ name: sla, data: ['-', '-', '✓'] },
|
||||
{ name: dedicatedComputingResources, data: ['-', '-', '✓'] },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'compliance.title',
|
||||
rows: [
|
||||
{ name: soc2Compliant, data: ['✓', '✓', '✓'] },
|
||||
{ name: soc2Report, data: ['-', '✓', '✓'] },
|
||||
{ name: hipaaOrBaaReport, data: ['-', '-', comingSoon] },
|
||||
{ name: hipaaOrBaaReport, data: ['-', '-', '✓'] },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'Bedarfsgesteuerte Bereitstellung',
|
||||
},
|
||||
support: {
|
||||
title: 'Compliance und Support',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'Gemeinschaft',
|
||||
customer_ticket: 'Support-Ticket',
|
||||
premium: 'Premium',
|
||||
|
|
|
@ -59,11 +59,21 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'Just-in-time provisioning',
|
||||
},
|
||||
support: {
|
||||
title: 'Compliance and support',
|
||||
title: 'Support',
|
||||
community: 'Community',
|
||||
customer_ticket: 'Ticket support',
|
||||
premium: 'Premium',
|
||||
email_ticket_support: 'Email ticket support',
|
||||
discord_private_channel: 'Discord private channel',
|
||||
premium_support: 'Premium support',
|
||||
developer_onboarding: 'Developer onboarding',
|
||||
solution_engineer_support: 'Solution engineer',
|
||||
sla: 'SLA',
|
||||
dedicated_computing_resources: 'Dedicated computing resources',
|
||||
},
|
||||
compliance: {
|
||||
title: 'Compliance',
|
||||
soc2_compliant: 'SOC2 compliant',
|
||||
soc2_report: 'SOC2 report',
|
||||
hipaa_or_baa_report: 'HIPAA/BAA report',
|
||||
},
|
||||
|
|
|
@ -54,6 +54,10 @@ const usage = {
|
|||
tooltip:
|
||||
'Add-on feature priced at ${{price, number}} per hook. The first 10 hooks are included.',
|
||||
},
|
||||
pricing: {
|
||||
add_on_changes_in_current_cycle_notice:
|
||||
'If you make any changes during the current billing cycle, your next bill may be slightly higher for the first month after the change. It will be ${{price, number}} base price plus add-on costs for unbilled usage from the current cycle and the full charge for the next cycle. <a>Learn more</a>',
|
||||
},
|
||||
};
|
||||
|
||||
export default Object.freeze(usage);
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'Provisión justo a tiempo',
|
||||
},
|
||||
support: {
|
||||
title: 'Cumplimiento y soporte',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'Comunidad',
|
||||
customer_ticket: 'Ticket de soporte',
|
||||
premium: 'Premium',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'Fourniture juste-à-temps',
|
||||
},
|
||||
support: {
|
||||
title: 'Conformité et support',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'Communauté',
|
||||
customer_ticket: 'Ticket de support',
|
||||
premium: 'Premium',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'Provisioning just-in-time',
|
||||
},
|
||||
support: {
|
||||
title: 'Conformità e supporto',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'Community',
|
||||
customer_ticket: 'Ticket di assistenza',
|
||||
premium: 'Premium',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'ジャストインタイムプロビジョニング',
|
||||
},
|
||||
support: {
|
||||
title: 'コンプライアンスとサポート',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'コミュニティ',
|
||||
customer_ticket: 'カスタマーチケット',
|
||||
premium: 'プレミアム',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: '적시 프로비저닝',
|
||||
},
|
||||
support: {
|
||||
title: '컴플라이언스 및 지원',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: '커뮤니티',
|
||||
customer_ticket: '지원 티켓',
|
||||
premium: '프리미엄',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'Provisioning w trybie just-in-time',
|
||||
},
|
||||
support: {
|
||||
title: 'Zgodność i wsparcie',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'Społeczność',
|
||||
customer_ticket: 'Zgłoszenie wsparcia',
|
||||
premium: 'Premium',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'Provisionamento just-in-time',
|
||||
},
|
||||
support: {
|
||||
title: 'Conformidade e suporte',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'Comunidade',
|
||||
customer_ticket: 'Ticket de suporte',
|
||||
premium: 'Premium',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'Provisionamento just-in-time',
|
||||
},
|
||||
support: {
|
||||
title: 'Conformidade e suporte',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'Comunidade',
|
||||
customer_ticket: 'Bilhete de suporte',
|
||||
premium: 'Premium',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'Пакетная настройка по запросу',
|
||||
},
|
||||
support: {
|
||||
title: 'Соблюдение и поддержка',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'Сообщество',
|
||||
customer_ticket: 'Техническая поддержка',
|
||||
premium: 'Премиум',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: 'İstisnai olana kadar temin',
|
||||
},
|
||||
support: {
|
||||
title: 'Uyumluluk ve destek',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: 'Topluluk',
|
||||
customer_ticket: 'Müşteri destek bileti',
|
||||
premium: 'Premium',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: '即时配置',
|
||||
},
|
||||
support: {
|
||||
title: '合规与支持',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: '社区',
|
||||
customer_ticket: '客户支持票据',
|
||||
premium: '高级版',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: '即時規定',
|
||||
},
|
||||
support: {
|
||||
title: '合規和支援',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: '社群',
|
||||
customer_ticket: '客戶支援票據',
|
||||
premium: '高級版',
|
||||
|
|
|
@ -59,7 +59,8 @@ const quota_table = {
|
|||
just_in_time_provisioning: '即時供應管理',
|
||||
},
|
||||
support: {
|
||||
title: '合規性與支援',
|
||||
/** UNTRANSLATED */
|
||||
title: 'Support',
|
||||
community: '社群',
|
||||
customer_ticket: '客戶支援票證',
|
||||
premium: '進階版',
|
||||
|
|
Loading…
Add table
Reference in a new issue