diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/plan/settings_plan_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/plan/settings_plan_bloc.dart index 122c1e070b..49b6cb246e 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/plan/settings_plan_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/plan/settings_plan_bloc.dart @@ -27,7 +27,7 @@ class SettingsPlanBloc extends Bloc { on((event, emit) async { await event.when( - started: () async { + started: (withShowSuccessful) async { emit(const SettingsPlanState.loading()); final snapshots = await Future.wait([ @@ -88,8 +88,19 @@ class SettingsPlanBloc extends Bloc { workspaceUsage: usageResult, subscription: subscription, billingPortal: billingPortal, + showSuccessDialog: withShowSuccessful, ), ); + + if (withShowSuccessful) { + emit( + SettingsPlanState.ready( + workspaceUsage: usageResult, + subscription: subscription, + billingPortal: billingPortal, + ), + ); + } }, addSubscription: (plan) async { final result = await UserBackendService.createSubscription( @@ -104,6 +115,7 @@ class SettingsPlanBloc extends Bloc { }, cancelSubscription: () async { await UserBackendService.cancelSubscription(workspaceId); + add(const SettingsPlanEvent.started()); }, paymentSuccessful: () { final readyState = state.mapOrNull(ready: (state) => state); @@ -111,8 +123,7 @@ class SettingsPlanBloc extends Bloc { return; } - emit(readyState.copyWith(showSuccessDialog: true)); - emit(readyState.copyWith(showSuccessDialog: false)); + add(const SettingsPlanEvent.started(withShowSuccessful: true)); }, ); }); @@ -135,7 +146,9 @@ class SettingsPlanBloc extends Bloc { @freezed class SettingsPlanEvent with _$SettingsPlanEvent { - const factory SettingsPlanEvent.started() = _Started; + const factory SettingsPlanEvent.started({ + @Default(false) bool withShowSuccessful, + }) = _Started; const factory SettingsPlanEvent.addSubscription(SubscriptionPlanPB plan) = _AddSubscription; const factory SettingsPlanEvent.cancelSubscription() = _CancelSubscription; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_billing_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_billing_view.dart index 0352f6a35c..70d857446f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_billing_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_billing_view.dart @@ -8,7 +8,7 @@ import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_com import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart'; import 'package:appflowy/workspace/presentation/settings/shared/single_setting_action.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -63,8 +63,9 @@ class SettingsBillingView extends StatelessWidget { onPressed: () => _openPricingDialog( context, workspaceId, - state.subscription.subscriptionPlan, + state.subscription, ), + fontWeight: FontWeight.w500, label: state.subscription.label, buttonLabel: LocaleKeys .settings_billingPage_plan_planButtonLabel @@ -77,6 +78,7 @@ class SettingsBillingView extends StatelessWidget { label: LocaleKeys .settings_billingPage_plan_billingPeriod .tr(), + fontWeight: FontWeight.w500, buttonLabel: LocaleKeys .settings_billingPage_plan_periodButtonLabel .tr(), @@ -95,6 +97,7 @@ class SettingsBillingView extends StatelessWidget { label: LocaleKeys .settings_billingPage_paymentDetails_methodLabel .tr(), + fontWeight: FontWeight.w500, buttonLabel: LocaleKeys .settings_billingPage_paymentDetails_methodButtonLabel .tr(), @@ -113,7 +116,7 @@ class SettingsBillingView extends StatelessWidget { void _openPricingDialog( BuildContext context, String workspaceId, - SubscriptionPlanPB plan, + WorkspaceSubscriptionPB subscription, ) => showDialog( context: context, @@ -122,7 +125,7 @@ class SettingsBillingView extends StatelessWidget { ..add(const SettingsPlanEvent.started()), child: SettingsPlanComparisonDialog( workspaceId: workspaceId, - currentPlan: plan, + subscription: subscription, ), ), ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart index 849177eb05..546e242f51 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart @@ -3,6 +3,9 @@ import 'package:flutter/material.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/settings/plan/settings_plan_bloc.dart'; +import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart'; +import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -14,11 +17,11 @@ class SettingsPlanComparisonDialog extends StatefulWidget { const SettingsPlanComparisonDialog({ super.key, required this.workspaceId, - required this.currentPlan, + required this.subscription, }); final String workspaceId; - final SubscriptionPlanPB currentPlan; + final WorkspaceSubscriptionPB subscription; @override State createState() => @@ -30,6 +33,8 @@ class _SettingsPlanComparisonDialogState final horizontalController = ScrollController(); final verticalController = ScrollController(); + late WorkspaceSubscriptionPB currentSubscription = widget.subscription; + @override void dispose() { horizontalController.dispose(); @@ -39,147 +44,208 @@ class _SettingsPlanComparisonDialogState @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return FlowyDialog( - constraints: const BoxConstraints(maxWidth: 784, minWidth: 674), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only(top: 24, left: 24, right: 24), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - FlowyText.semibold( - LocaleKeys.settings_comparePlanDialog_title.tr(), - fontSize: 24, - ), - const Spacer(), - GestureDetector( - onTap: Navigator.of(context).pop, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: FlowySvg( - FlowySvgs.m_close_m, - size: const Size.square(20), - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - ], + return BlocListener( + listener: (context, state) { + final readyState = state.mapOrNull(ready: (state) => state); + + if (readyState == null) { + return; + } + + if (readyState.showSuccessDialog) { + SettingsAlertDialog( + icon: Center( + child: SizedBox( + height: 90, + width: 90, + child: FlowySvg( + FlowySvgs.check_circle_s, + color: AFThemeExtension.of(context).success, ), ), - Flexible( - child: SingleChildScrollView( - controller: horizontalController, - scrollDirection: Axis.horizontal, - child: SingleChildScrollView( - controller: verticalController, - padding: - const EdgeInsets.only(left: 24, right: 24, bottom: 24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 250, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const VSpace(22), - SizedBox( - height: 100, - child: FlowyText.semibold( - LocaleKeys - .settings_comparePlanDialog_planFeatures - .tr(), - fontSize: 24, - maxLines: 2, - color: const Color(0xFF5C3699), - ), - ), - const SizedBox(height: 64), - const SizedBox(height: 56), - ..._planLabels.map( - (e) => _ComparisonCell( - label: e.label, - tooltip: e.tooltip, - ), - ), - ], - ), - ), - _PlanTable( - title: LocaleKeys - .settings_comparePlanDialog_freePlan_title - .tr(), - description: LocaleKeys - .settings_comparePlanDialog_freePlan_description - .tr(), - price: LocaleKeys - .settings_comparePlanDialog_freePlan_price - .tr(), - priceInfo: LocaleKeys - .settings_comparePlanDialog_freePlan_priceInfo - .tr(), - cells: _freeLabels, - isCurrent: - widget.currentPlan == SubscriptionPlanPB.None, - canDowngrade: - widget.currentPlan != SubscriptionPlanPB.None, - onSelected: () async { - if (widget.currentPlan == - SubscriptionPlanPB.None) { - return; - } + ), + title: + LocaleKeys.settings_comparePlanDialog_paymentSuccess_title.tr( + args: [readyState.subscription.label], + ), + subtitle: LocaleKeys + .settings_comparePlanDialog_paymentSuccess_description + .tr( + args: [readyState.subscription.label], + ), + hideCancelButton: true, + confirm: Navigator.of(context).pop, + confirmLabel: LocaleKeys.button_close.tr(), + ).show(context); + } - context.read().add( - const SettingsPlanEvent - .cancelSubscription(), - ); - }, - ), - _PlanTable( - title: LocaleKeys - .settings_comparePlanDialog_proPlan_title - .tr(), - description: LocaleKeys - .settings_comparePlanDialog_proPlan_description - .tr(), - price: LocaleKeys - .settings_comparePlanDialog_proPlan_price - .tr(), - priceInfo: LocaleKeys - .settings_comparePlanDialog_proPlan_priceInfo - .tr(), - cells: _proLabels, - isCurrent: - widget.currentPlan == SubscriptionPlanPB.Pro, - canUpgrade: - widget.currentPlan == SubscriptionPlanPB.None, - onSelected: () => - context.read().add( - const SettingsPlanEvent.addSubscription( - SubscriptionPlanPB.Pro, - ), - ), - ), - ], - ), - ], + setState(() { + currentSubscription = readyState.subscription; + }); + }, + child: FlowyDialog( + constraints: const BoxConstraints(maxWidth: 784, minWidth: 674), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 24, left: 24, right: 24), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowyText.semibold( + LocaleKeys.settings_comparePlanDialog_title.tr(), + fontSize: 24, + ), + const Spacer(), + GestureDetector( + onTap: Navigator.of(context).pop, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: FlowySvg( + FlowySvgs.m_close_m, + size: const Size.square(20), + color: Theme.of(context).colorScheme.outline, + ), ), ), + ], + ), + ), + Flexible( + child: SingleChildScrollView( + controller: horizontalController, + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + controller: verticalController, + padding: + const EdgeInsets.only(left: 24, right: 24, bottom: 24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 250, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const VSpace(22), + SizedBox( + height: 100, + child: FlowyText.semibold( + LocaleKeys + .settings_comparePlanDialog_planFeatures + .tr(), + fontSize: 24, + maxLines: 2, + color: const Color(0xFF5C3699), + ), + ), + const SizedBox(height: 64), + const SizedBox(height: 56), + ..._planLabels.map( + (e) => _ComparisonCell( + label: e.label, + tooltip: e.tooltip, + ), + ), + ], + ), + ), + _PlanTable( + title: LocaleKeys + .settings_comparePlanDialog_freePlan_title + .tr(), + description: LocaleKeys + .settings_comparePlanDialog_freePlan_description + .tr(), + price: LocaleKeys + .settings_comparePlanDialog_freePlan_price + .tr(), + priceInfo: LocaleKeys + .settings_comparePlanDialog_freePlan_priceInfo + .tr(), + cells: _freeLabels, + isCurrent: currentSubscription.subscriptionPlan == + SubscriptionPlanPB.None, + canDowngrade: + currentSubscription.subscriptionPlan != + SubscriptionPlanPB.None, + onSelected: () async { + if (currentSubscription.subscriptionPlan == + SubscriptionPlanPB.None || + currentSubscription.hasCanceled) { + return; + } + + context.read().add( + const SettingsPlanEvent + .cancelSubscription(), + ); + + await SettingsAlertDialog( + icon: Center( + child: SizedBox( + height: 90, + width: 90, + child: FlowySvg( + FlowySvgs.check_circle_s, + color: + AFThemeExtension.of(context).success, + ), + ), + ), + title: LocaleKeys + .settings_comparePlanDialog_downgradeSuccess_title + .tr(args: [currentSubscription.label]), + subtitle: LocaleKeys + .settings_comparePlanDialog_downgradeSuccess_description + .tr(), + hideCancelButton: true, + confirm: Navigator.of(context).pop, + confirmLabel: LocaleKeys.button_close.tr(), + ).show(context); + }, + ), + _PlanTable( + title: LocaleKeys + .settings_comparePlanDialog_proPlan_title + .tr(), + description: LocaleKeys + .settings_comparePlanDialog_proPlan_description + .tr(), + price: LocaleKeys + .settings_comparePlanDialog_proPlan_price + .tr(), + priceInfo: LocaleKeys + .settings_comparePlanDialog_proPlan_priceInfo + .tr(), + cells: _proLabels, + isCurrent: currentSubscription.subscriptionPlan == + SubscriptionPlanPB.Pro, + canUpgrade: currentSubscription.subscriptionPlan == + SubscriptionPlanPB.None, + onSelected: () => + context.read().add( + const SettingsPlanEvent.addSubscription( + SubscriptionPlanPB.Pro, + ), + ), + ), + ], + ), + ], + ), ), ), - ], - ), - ); - }, + ), + ], + ), + ), ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart index e0c7746a28..d07d6d3ed3 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart @@ -61,7 +61,7 @@ class SettingsPlanView extends StatelessWidget { children: [ _PlanUsageSummary( usage: state.workspaceUsage, - currentPlan: state.subscription.subscriptionPlan, + subscription: state.subscription, ), _CurrentPlanBox(subscription: state.subscription), ], @@ -117,7 +117,7 @@ class _CurrentPlanBox extends StatelessWidget { onPressed: () => _openPricingDialog( context, context.read().workspaceId, - subscription.subscriptionPlan, + subscription, ), ), if (subscription.hasCanceled) ...[ @@ -225,7 +225,7 @@ class _CurrentPlanBox extends StatelessWidget { void _openPricingDialog( BuildContext context, String workspaceId, - SubscriptionPlanPB plan, + WorkspaceSubscriptionPB subscription, ) => showDialog( context: context, @@ -233,7 +233,7 @@ class _CurrentPlanBox extends StatelessWidget { value: context.read(), child: SettingsPlanComparisonDialog( workspaceId: workspaceId, - currentPlan: plan, + subscription: subscription, ), ), ); @@ -274,10 +274,10 @@ class _ProConItem extends StatelessWidget { } class _PlanUsageSummary extends StatelessWidget { - const _PlanUsageSummary({required this.usage, required this.currentPlan}); + const _PlanUsageSummary({required this.usage, required this.subscription}); final WorkspaceUsagePB usage; - final SubscriptionPlanPB currentPlan; + final WorkspaceSubscriptionPB subscription; @override Widget build(BuildContext context) { @@ -329,18 +329,18 @@ class _PlanUsageSummary extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ _ToggleMore( - value: currentPlan == SubscriptionPlanPB.Pro, + value: subscription.subscriptionPlan == SubscriptionPlanPB.Pro, label: LocaleKeys.settings_planPage_planUsage_memberProToggle.tr(), - currentPlan: currentPlan, + subscription: subscription, badgeLabel: LocaleKeys.settings_planPage_planUsage_proBadge.tr(), ), const VSpace(8), _ToggleMore( - value: currentPlan == SubscriptionPlanPB.Pro, + value: subscription.subscriptionPlan == SubscriptionPlanPB.Pro, label: LocaleKeys.settings_planPage_planUsage_guestCollabToggle.tr(), - currentPlan: currentPlan, + subscription: subscription, badgeLabel: LocaleKeys.settings_planPage_planUsage_proBadge.tr(), ), ], @@ -381,13 +381,13 @@ class _ToggleMore extends StatefulWidget { const _ToggleMore({ required this.value, required this.label, - required this.currentPlan, + required this.subscription, this.badgeLabel, }); final bool value; final String label; - final SubscriptionPlanPB currentPlan; + final WorkspaceSubscriptionPB subscription; final String? badgeLabel; @override @@ -422,7 +422,7 @@ class _ToggleMoreState extends State<_ToggleMore> { value: context.read(), child: SettingsPlanComparisonDialog( workspaceId: context.read().workspaceId, - currentPlan: widget.currentPlan, + subscription: widget.subscription, ), ), ).then((_) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart index aa67ea3158..6c906e272b 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; @@ -40,8 +43,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_alert_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_alert_dialog.dart index 17d60359cd..4efc31b06d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_alert_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_alert_dialog.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; @@ -10,6 +11,7 @@ import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; class SettingsAlertDialog extends StatefulWidget { const SettingsAlertDialog({ super.key, + this.icon, required this.title, this.subtitle, this.children, @@ -21,6 +23,7 @@ class SettingsAlertDialog extends StatefulWidget { this.implyLeading = false, }); + final Widget? icon; final String title; final String? subtitle; final List? children; @@ -86,6 +89,10 @@ class _SettingsAlertDialogState extends State { ), ], ), + if (widget.icon != null) ...[ + widget.icon!, + const VSpace(16), + ], Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -168,6 +175,7 @@ class _Actions extends StatelessWidget { fontColor: AFThemeExtension.of(context).textColor, fillColor: Colors.transparent, hoverColor: Colors.transparent, + radius: Corners.s12Border, onPressed: () { cancel?.call(); Navigator.of(context).pop(); @@ -187,6 +195,7 @@ class _Actions extends StatelessWidget { horizontal: 24, vertical: 12, ), + radius: Corners.s12Border, fontColor: isDangerous ? Colors.white : null, fontHoverColor: Colors.white, fillColor: isDangerous diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart index 7d51ea370c..8fc8e33280 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -65,6 +66,7 @@ class SingleSettingAction extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 7), fillColor: isDangerous ? null : Theme.of(context).colorScheme.primary, + radius: Corners.s12Border, hoverColor: isDangerous ? null : const Color(0xFF005483), fontColor: isDangerous ? Theme.of(context).colorScheme.error : null, fontHoverColor: Colors.white, diff --git a/frontend/resources/flowy_icons/16x/check_circle.svg b/frontend/resources/flowy_icons/16x/check_circle.svg new file mode 100644 index 0000000000..6703344199 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/check_circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index e6e8f7b108..bddb71571b 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -330,7 +330,8 @@ "signInGoogle": "Sign in with Google", "signInGithub": "Sign in with Github", "signInDiscord": "Sign in with Discord", - "more": "More" + "more": "More", + "close": "Close" }, "label": { "welcome": "Welcome!", @@ -618,6 +619,14 @@ "itemSix": "yes", "itemSeven": "yes", "itemEight": "10,000 monthly" + }, + "paymentSuccess": { + "title": "You are now on the {} plan!", + "description": "Your payment has been successfully processed and your plan is upgraded to AppFlowy {}. You can view your plan details on the Plan page" + }, + "downgradeSuccess": { + "title": "You have canceled the {} plan!", + "description": "Your plan has been canceled, until the end of the billing cycle you will retain your previous plan benefits." } }, "common": {