feat: downgrade/upgrade dialogs

This commit is contained in:
Mathias Mogensen 2024-06-10 13:41:57 +02:00
parent 89f6056592
commit 08fd136f39
9 changed files with 266 additions and 160 deletions

View File

@ -27,7 +27,7 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
on<SettingsPlanEvent>((event, emit) async { on<SettingsPlanEvent>((event, emit) async {
await event.when( await event.when(
started: () async { started: (withShowSuccessful) async {
emit(const SettingsPlanState.loading()); emit(const SettingsPlanState.loading());
final snapshots = await Future.wait([ final snapshots = await Future.wait([
@ -88,8 +88,19 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
workspaceUsage: usageResult, workspaceUsage: usageResult,
subscription: subscription, subscription: subscription,
billingPortal: billingPortal, billingPortal: billingPortal,
showSuccessDialog: withShowSuccessful,
), ),
); );
if (withShowSuccessful) {
emit(
SettingsPlanState.ready(
workspaceUsage: usageResult,
subscription: subscription,
billingPortal: billingPortal,
),
);
}
}, },
addSubscription: (plan) async { addSubscription: (plan) async {
final result = await UserBackendService.createSubscription( final result = await UserBackendService.createSubscription(
@ -104,6 +115,7 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
}, },
cancelSubscription: () async { cancelSubscription: () async {
await UserBackendService.cancelSubscription(workspaceId); await UserBackendService.cancelSubscription(workspaceId);
add(const SettingsPlanEvent.started());
}, },
paymentSuccessful: () { paymentSuccessful: () {
final readyState = state.mapOrNull(ready: (state) => state); final readyState = state.mapOrNull(ready: (state) => state);
@ -111,8 +123,7 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
return; return;
} }
emit(readyState.copyWith(showSuccessDialog: true)); add(const SettingsPlanEvent.started(withShowSuccessful: true));
emit(readyState.copyWith(showSuccessDialog: false));
}, },
); );
}); });
@ -135,7 +146,9 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
@freezed @freezed
class SettingsPlanEvent with _$SettingsPlanEvent { class SettingsPlanEvent with _$SettingsPlanEvent {
const factory SettingsPlanEvent.started() = _Started; const factory SettingsPlanEvent.started({
@Default(false) bool withShowSuccessful,
}) = _Started;
const factory SettingsPlanEvent.addSubscription(SubscriptionPlanPB plan) = const factory SettingsPlanEvent.addSubscription(SubscriptionPlanPB plan) =
_AddSubscription; _AddSubscription;
const factory SettingsPlanEvent.cancelSubscription() = _CancelSubscription; const factory SettingsPlanEvent.cancelSubscription() = _CancelSubscription;

View File

@ -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_body.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.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/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:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -63,8 +63,9 @@ class SettingsBillingView extends StatelessWidget {
onPressed: () => _openPricingDialog( onPressed: () => _openPricingDialog(
context, context,
workspaceId, workspaceId,
state.subscription.subscriptionPlan, state.subscription,
), ),
fontWeight: FontWeight.w500,
label: state.subscription.label, label: state.subscription.label,
buttonLabel: LocaleKeys buttonLabel: LocaleKeys
.settings_billingPage_plan_planButtonLabel .settings_billingPage_plan_planButtonLabel
@ -77,6 +78,7 @@ class SettingsBillingView extends StatelessWidget {
label: LocaleKeys label: LocaleKeys
.settings_billingPage_plan_billingPeriod .settings_billingPage_plan_billingPeriod
.tr(), .tr(),
fontWeight: FontWeight.w500,
buttonLabel: LocaleKeys buttonLabel: LocaleKeys
.settings_billingPage_plan_periodButtonLabel .settings_billingPage_plan_periodButtonLabel
.tr(), .tr(),
@ -95,6 +97,7 @@ class SettingsBillingView extends StatelessWidget {
label: LocaleKeys label: LocaleKeys
.settings_billingPage_paymentDetails_methodLabel .settings_billingPage_paymentDetails_methodLabel
.tr(), .tr(),
fontWeight: FontWeight.w500,
buttonLabel: LocaleKeys buttonLabel: LocaleKeys
.settings_billingPage_paymentDetails_methodButtonLabel .settings_billingPage_paymentDetails_methodButtonLabel
.tr(), .tr(),
@ -113,7 +116,7 @@ class SettingsBillingView extends StatelessWidget {
void _openPricingDialog( void _openPricingDialog(
BuildContext context, BuildContext context,
String workspaceId, String workspaceId,
SubscriptionPlanPB plan, WorkspaceSubscriptionPB subscription,
) => ) =>
showDialog( showDialog(
context: context, context: context,
@ -122,7 +125,7 @@ class SettingsBillingView extends StatelessWidget {
..add(const SettingsPlanEvent.started()), ..add(const SettingsPlanEvent.started()),
child: SettingsPlanComparisonDialog( child: SettingsPlanComparisonDialog(
workspaceId: workspaceId, workspaceId: workspaceId,
currentPlan: plan, subscription: subscription,
), ),
), ),
); );

View File

@ -3,6 +3,9 @@ import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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/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:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
@ -14,11 +17,11 @@ class SettingsPlanComparisonDialog extends StatefulWidget {
const SettingsPlanComparisonDialog({ const SettingsPlanComparisonDialog({
super.key, super.key,
required this.workspaceId, required this.workspaceId,
required this.currentPlan, required this.subscription,
}); });
final String workspaceId; final String workspaceId;
final SubscriptionPlanPB currentPlan; final WorkspaceSubscriptionPB subscription;
@override @override
State<SettingsPlanComparisonDialog> createState() => State<SettingsPlanComparisonDialog> createState() =>
@ -30,6 +33,8 @@ class _SettingsPlanComparisonDialogState
final horizontalController = ScrollController(); final horizontalController = ScrollController();
final verticalController = ScrollController(); final verticalController = ScrollController();
late WorkspaceSubscriptionPB currentSubscription = widget.subscription;
@override @override
void dispose() { void dispose() {
horizontalController.dispose(); horizontalController.dispose();
@ -39,9 +44,46 @@ class _SettingsPlanComparisonDialogState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SettingsPlanBloc, SettingsPlanState>( return BlocListener<SettingsPlanBloc, SettingsPlanState>(
builder: (context, state) { listener: (context, state) {
return FlowyDialog( 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,
),
),
),
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);
}
setState(() {
currentSubscription = readyState.subscription;
});
},
child: FlowyDialog(
constraints: const BoxConstraints(maxWidth: 784, minWidth: 674), constraints: const BoxConstraints(maxWidth: 784, minWidth: 674),
child: Column( child: Column(
children: [ children: [
@ -128,13 +170,15 @@ class _SettingsPlanComparisonDialogState
.settings_comparePlanDialog_freePlan_priceInfo .settings_comparePlanDialog_freePlan_priceInfo
.tr(), .tr(),
cells: _freeLabels, cells: _freeLabels,
isCurrent: isCurrent: currentSubscription.subscriptionPlan ==
widget.currentPlan == SubscriptionPlanPB.None, SubscriptionPlanPB.None,
canDowngrade: canDowngrade:
widget.currentPlan != SubscriptionPlanPB.None, currentSubscription.subscriptionPlan !=
SubscriptionPlanPB.None,
onSelected: () async { onSelected: () async {
if (widget.currentPlan == if (currentSubscription.subscriptionPlan ==
SubscriptionPlanPB.None) { SubscriptionPlanPB.None ||
currentSubscription.hasCanceled) {
return; return;
} }
@ -142,6 +186,29 @@ class _SettingsPlanComparisonDialogState
const SettingsPlanEvent const SettingsPlanEvent
.cancelSubscription(), .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( _PlanTable(
@ -158,10 +225,10 @@ class _SettingsPlanComparisonDialogState
.settings_comparePlanDialog_proPlan_priceInfo .settings_comparePlanDialog_proPlan_priceInfo
.tr(), .tr(),
cells: _proLabels, cells: _proLabels,
isCurrent: isCurrent: currentSubscription.subscriptionPlan ==
widget.currentPlan == SubscriptionPlanPB.Pro, SubscriptionPlanPB.Pro,
canUpgrade: canUpgrade: currentSubscription.subscriptionPlan ==
widget.currentPlan == SubscriptionPlanPB.None, SubscriptionPlanPB.None,
onSelected: () => onSelected: () =>
context.read<SettingsPlanBloc>().add( context.read<SettingsPlanBloc>().add(
const SettingsPlanEvent.addSubscription( const SettingsPlanEvent.addSubscription(
@ -178,8 +245,7 @@ class _SettingsPlanComparisonDialogState
), ),
], ],
), ),
); ),
},
); );
} }
} }

View File

@ -61,7 +61,7 @@ class SettingsPlanView extends StatelessWidget {
children: [ children: [
_PlanUsageSummary( _PlanUsageSummary(
usage: state.workspaceUsage, usage: state.workspaceUsage,
currentPlan: state.subscription.subscriptionPlan, subscription: state.subscription,
), ),
_CurrentPlanBox(subscription: state.subscription), _CurrentPlanBox(subscription: state.subscription),
], ],
@ -117,7 +117,7 @@ class _CurrentPlanBox extends StatelessWidget {
onPressed: () => _openPricingDialog( onPressed: () => _openPricingDialog(
context, context,
context.read<SettingsPlanBloc>().workspaceId, context.read<SettingsPlanBloc>().workspaceId,
subscription.subscriptionPlan, subscription,
), ),
), ),
if (subscription.hasCanceled) ...[ if (subscription.hasCanceled) ...[
@ -225,7 +225,7 @@ class _CurrentPlanBox extends StatelessWidget {
void _openPricingDialog( void _openPricingDialog(
BuildContext context, BuildContext context,
String workspaceId, String workspaceId,
SubscriptionPlanPB plan, WorkspaceSubscriptionPB subscription,
) => ) =>
showDialog( showDialog(
context: context, context: context,
@ -233,7 +233,7 @@ class _CurrentPlanBox extends StatelessWidget {
value: context.read<SettingsPlanBloc>(), value: context.read<SettingsPlanBloc>(),
child: SettingsPlanComparisonDialog( child: SettingsPlanComparisonDialog(
workspaceId: workspaceId, workspaceId: workspaceId,
currentPlan: plan, subscription: subscription,
), ),
), ),
); );
@ -274,10 +274,10 @@ class _ProConItem extends StatelessWidget {
} }
class _PlanUsageSummary 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 WorkspaceUsagePB usage;
final SubscriptionPlanPB currentPlan; final WorkspaceSubscriptionPB subscription;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -329,18 +329,18 @@ class _PlanUsageSummary extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_ToggleMore( _ToggleMore(
value: currentPlan == SubscriptionPlanPB.Pro, value: subscription.subscriptionPlan == SubscriptionPlanPB.Pro,
label: label:
LocaleKeys.settings_planPage_planUsage_memberProToggle.tr(), LocaleKeys.settings_planPage_planUsage_memberProToggle.tr(),
currentPlan: currentPlan, subscription: subscription,
badgeLabel: LocaleKeys.settings_planPage_planUsage_proBadge.tr(), badgeLabel: LocaleKeys.settings_planPage_planUsage_proBadge.tr(),
), ),
const VSpace(8), const VSpace(8),
_ToggleMore( _ToggleMore(
value: currentPlan == SubscriptionPlanPB.Pro, value: subscription.subscriptionPlan == SubscriptionPlanPB.Pro,
label: label:
LocaleKeys.settings_planPage_planUsage_guestCollabToggle.tr(), LocaleKeys.settings_planPage_planUsage_guestCollabToggle.tr(),
currentPlan: currentPlan, subscription: subscription,
badgeLabel: LocaleKeys.settings_planPage_planUsage_proBadge.tr(), badgeLabel: LocaleKeys.settings_planPage_planUsage_proBadge.tr(),
), ),
], ],
@ -381,13 +381,13 @@ class _ToggleMore extends StatefulWidget {
const _ToggleMore({ const _ToggleMore({
required this.value, required this.value,
required this.label, required this.label,
required this.currentPlan, required this.subscription,
this.badgeLabel, this.badgeLabel,
}); });
final bool value; final bool value;
final String label; final String label;
final SubscriptionPlanPB currentPlan; final WorkspaceSubscriptionPB subscription;
final String? badgeLabel; final String? badgeLabel;
@override @override
@ -422,7 +422,7 @@ class _ToggleMoreState extends State<_ToggleMore> {
value: context.read<SettingsPlanBloc>(), value: context.read<SettingsPlanBloc>(),
child: SettingsPlanComparisonDialog( child: SettingsPlanComparisonDialog(
workspaceId: context.read<SettingsPlanBloc>().workspaceId, workspaceId: context.read<SettingsPlanBloc>().workspaceId,
currentPlan: widget.currentPlan, subscription: widget.subscription,
), ),
), ),
).then((_) { ).then((_) {

View File

@ -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/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.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/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.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:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.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/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.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 { class SettingsAlertDialog extends StatefulWidget {
const SettingsAlertDialog({ const SettingsAlertDialog({
super.key, super.key,
this.icon,
required this.title, required this.title,
this.subtitle, this.subtitle,
this.children, this.children,
@ -21,6 +23,7 @@ class SettingsAlertDialog extends StatefulWidget {
this.implyLeading = false, this.implyLeading = false,
}); });
final Widget? icon;
final String title; final String title;
final String? subtitle; final String? subtitle;
final List<Widget>? children; final List<Widget>? children;
@ -86,6 +89,10 @@ class _SettingsAlertDialogState extends State<SettingsAlertDialog> {
), ),
], ],
), ),
if (widget.icon != null) ...[
widget.icon!,
const VSpace(16),
],
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -168,6 +175,7 @@ class _Actions extends StatelessWidget {
fontColor: AFThemeExtension.of(context).textColor, fontColor: AFThemeExtension.of(context).textColor,
fillColor: Colors.transparent, fillColor: Colors.transparent,
hoverColor: Colors.transparent, hoverColor: Colors.transparent,
radius: Corners.s12Border,
onPressed: () { onPressed: () {
cancel?.call(); cancel?.call();
Navigator.of(context).pop(); Navigator.of(context).pop();
@ -187,6 +195,7 @@ class _Actions extends StatelessWidget {
horizontal: 24, horizontal: 24,
vertical: 12, vertical: 12,
), ),
radius: Corners.s12Border,
fontColor: isDangerous ? Colors.white : null, fontColor: isDangerous ? Colors.white : null,
fontHoverColor: Colors.white, fontHoverColor: Colors.white,
fillColor: isDangerous fillColor: isDangerous

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.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), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 7),
fillColor: fillColor:
isDangerous ? null : Theme.of(context).colorScheme.primary, isDangerous ? null : Theme.of(context).colorScheme.primary,
radius: Corners.s12Border,
hoverColor: isDangerous ? null : const Color(0xFF005483), hoverColor: isDangerous ? null : const Color(0xFF005483),
fontColor: isDangerous ? Theme.of(context).colorScheme.error : null, fontColor: isDangerous ? Theme.of(context).colorScheme.error : null,
fontHoverColor: Colors.white, fontHoverColor: Colors.white,

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.88 11.68L12.52 6.04L11.4 4.92L6.88 9.44L4.6 7.16L3.48 8.28L6.88 11.68ZM8 16C6.89333 16 5.85333 15.79 4.88 15.37C3.90667 14.95 3.06 14.38 2.34 13.66C1.62 12.94 1.05 12.0933 0.63 11.12C0.21 10.1467 0 9.10667 0 8C0 6.89333 0.21 5.85333 0.63 4.88C1.05 3.90667 1.62 3.06 2.34 2.34C3.06 1.62 3.90667 1.05 4.88 0.63C5.85333 0.21 6.89333 0 8 0C9.10667 0 10.1467 0.21 11.12 0.63C12.0933 1.05 12.94 1.62 13.66 2.34C14.38 3.06 14.95 3.90667 15.37 4.88C15.79 5.85333 16 6.89333 16 8C16 9.10667 15.79 10.1467 15.37 11.12C14.95 12.0933 14.38 12.94 13.66 13.66C12.94 14.38 12.0933 14.95 11.12 15.37C10.1467 15.79 9.10667 16 8 16Z" fill="#66CF80"/>
</svg>

After

Width:  |  Height:  |  Size: 748 B

View File

@ -330,7 +330,8 @@
"signInGoogle": "Sign in with Google", "signInGoogle": "Sign in with Google",
"signInGithub": "Sign in with Github", "signInGithub": "Sign in with Github",
"signInDiscord": "Sign in with Discord", "signInDiscord": "Sign in with Discord",
"more": "More" "more": "More",
"close": "Close"
}, },
"label": { "label": {
"welcome": "Welcome!", "welcome": "Welcome!",
@ -618,6 +619,14 @@
"itemSix": "yes", "itemSix": "yes",
"itemSeven": "yes", "itemSeven": "yes",
"itemEight": "10,000 monthly" "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": { "common": {