0
Fork 0
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:
Darcy Ye 2024-08-16 15:10:46 +08:00 committed by GitHub
parent f17526c612
commit 36f1055314
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 122 additions and 19 deletions

View file

@ -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;

View file

@ -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 === */
});

View file

@ -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;

View file

@ -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}

View file

@ -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: ['-', '-', '✓'] },
],
},
];

View file

@ -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',

View file

@ -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',
},

View file

@ -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);

View file

@ -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',

View file

@ -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',

View file

@ -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',

View file

@ -59,7 +59,8 @@ const quota_table = {
just_in_time_provisioning: 'ジャストインタイムプロビジョニング',
},
support: {
title: 'コンプライアンスとサポート',
/** UNTRANSLATED */
title: 'Support',
community: 'コミュニティ',
customer_ticket: 'カスタマーチケット',
premium: 'プレミアム',

View file

@ -59,7 +59,8 @@ const quota_table = {
just_in_time_provisioning: '적시 프로비저닝',
},
support: {
title: '컴플라이언스 및 지원',
/** UNTRANSLATED */
title: 'Support',
community: '커뮤니티',
customer_ticket: '지원 티켓',
premium: '프리미엄',

View file

@ -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',

View file

@ -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',

View file

@ -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',

View file

@ -59,7 +59,8 @@ const quota_table = {
just_in_time_provisioning: 'Пакетная настройка по запросу',
},
support: {
title: 'Соблюдение и поддержка',
/** UNTRANSLATED */
title: 'Support',
community: 'Сообщество',
customer_ticket: 'Техническая поддержка',
premium: 'Премиум',

View file

@ -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',

View file

@ -59,7 +59,8 @@ const quota_table = {
just_in_time_provisioning: '即时配置',
},
support: {
title: '合规与支持',
/** UNTRANSLATED */
title: 'Support',
community: '社区',
customer_ticket: '客户支持票据',
premium: '高级版',

View file

@ -59,7 +59,8 @@ const quota_table = {
just_in_time_provisioning: '即時規定',
},
support: {
title: '合規和支援',
/** UNTRANSLATED */
title: 'Support',
community: '社群',
customer_ticket: '客戶支援票據',
premium: '高級版',

View file

@ -59,7 +59,8 @@ const quota_table = {
just_in_time_provisioning: '即時供應管理',
},
support: {
title: '合規性與支援',
/** UNTRANSLATED */
title: 'Support',
community: '社群',
customer_ticket: '客戶支援票證',
premium: '進階版',