From e0f7ac426c4461fc86caca54fb0e369010611ed4 Mon Sep 17 00:00:00 2001 From: JimmFly Date: Thu, 26 Oct 2023 16:15:12 +0800 Subject: [PATCH] feat(core): add translation key for payment (#4723) --- .../setting-components/storage-progess.tsx | 24 +- .../affine/auth/user-plan-button.tsx | 5 +- .../setting-modal/account-setting/index.tsx | 1 + .../general-setting/billing/index.tsx | 250 ++++++++++++------ .../general-setting/billing/style.css.ts | 14 + .../setting-modal/general-setting/index.tsx | 6 +- packages/frontend/i18n/src/resources/en.json | 33 ++- 7 files changed, 236 insertions(+), 97 deletions(-) diff --git a/packages/frontend/component/src/components/setting-components/storage-progess.tsx b/packages/frontend/component/src/components/setting-components/storage-progess.tsx index 33dd170182..4532a88ed0 100644 --- a/packages/frontend/component/src/components/setting-components/storage-progess.tsx +++ b/packages/frontend/component/src/components/setting-components/storage-progess.tsx @@ -1,3 +1,4 @@ +import { SubscriptionPlan } from '@affine/graphql'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { Button } from '@toeverything/components/button'; import { Tooltip } from '@toeverything/components/tooltip'; @@ -11,7 +12,12 @@ export interface StorageProgressProgress { max: number; value: number; onUpgrade: () => void; - plan: string; + plan: SubscriptionPlan; +} + +enum ButtonType { + Primary = 'primary', + Default = 'default', } export const StorageProgress = ({ @@ -30,10 +36,10 @@ export const StorageProgress = ({ const max = useMemo(() => bytes.format(upperLimit), [upperLimit]); const buttonType = useMemo(() => { - if (plan === 'Free') { - return 'primary'; + if (plan === SubscriptionPlan.Free) { + return ButtonType.Primary; } - return 'default'; + return ButtonType.Default; }, [plan]); return ( @@ -43,7 +49,7 @@ export const StorageProgress = ({ {t['com.affine.storage.used.hint']()} {used}/{max} - {` (${plan} Plan)`} + {` (${plan} ${t['com.affine.storage.plan']()})`} @@ -59,9 +65,7 @@ export const StorageProgress = ({ diff --git a/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx b/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx index be9e4ca8ae..580db2ea11 100644 --- a/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx +++ b/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx @@ -1,4 +1,5 @@ import { SubscriptionPlan } from '@affine/graphql'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; import Tooltip from '@toeverything/components/tooltip'; import { useSetAtom } from 'jotai'; import { useCallback } from 'react'; @@ -24,8 +25,10 @@ export const UserPlanButton = () => { [setSettingModalAtom] ); + const t = useAFFiNEI18N(); + return ( - +
{plan}
diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx index 8a5541b1a0..34c50bbf58 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx @@ -157,6 +157,7 @@ const StoragePanel = () => { const [subscription] = useUserSubscription(); const plan = subscription?.plan ?? SubscriptionPlan.Free; + const maxLimit = useMemo(() => { return bytes.parse(plan === SubscriptionPlan.Free ? '10GB' : '100GB'); }, [plan]); diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx index 146f982470..a1eab2ab5d 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx @@ -15,11 +15,13 @@ import { SubscriptionRecurring, SubscriptionStatus, } from '@affine/graphql'; +import { Trans } from '@affine/i18n'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useMutation, useQuery } from '@affine/workspace/affine/gql'; import { ArrowRightSmallIcon } from '@blocksuite/icons'; import { Button, IconButton } from '@toeverything/components/button'; import { useSetAtom } from 'jotai'; -import { Suspense, useCallback } from 'react'; +import { Suspense, useCallback, useMemo } from 'react'; import { openSettingModalAtom } from '../../../../../atoms'; import { useCurrentLoginStatus } from '../../../../../hooks/affine/use-current-login-status'; @@ -29,8 +31,25 @@ import { } from '../../../../../hooks/use-subscription'; import * as styles from './style.css'; +enum DescriptionI18NKey { + Basic = 'com.affine.payment.billing-setting.current-plan.description', + Monthly = 'com.affine.payment.billing-setting.current-plan.description.monthly', + Yearly = 'com.affine.payment.billing-setting.current-plan.description.yearly', +} + +const getMessageKey = ( + plan: SubscriptionPlan, + recurring: SubscriptionRecurring +): DescriptionI18NKey => { + if (plan !== SubscriptionPlan.Pro) { + return DescriptionI18NKey.Basic; + } + return DescriptionI18NKey[recurring]; +}; + export const BillingSettings = () => { const status = useCurrentLoginStatus(); + const t = useAFFiNEI18N(); if (status !== 'authenticated') { return null; @@ -39,18 +58,22 @@ export const BillingSettings = () => { return ( <> {/* TODO: loading fallback */} - + {/* TODO: loading fallback */} - + @@ -74,75 +97,11 @@ const SubscriptionSettings = () => { : price ? recurring === SubscriptionRecurring.Monthly ? String(price.amount / 100) - : (price.yearlyAmount / 100 / 12).toFixed(2) + : String(price.yearlyAmount / 100) : '?'; - return ( -
-
-
- - You are current on the{' '} - - {/* TODO: Action */} - {plan} plan - - . -

- } - /> - -
-

${amount}/month

-
- {subscription?.status === SubscriptionStatus.Active && ( - <> - - - - {subscription.nextBillAt && ( - - )} - {subscription.canceledAt ? ( - - - - ) : ( - - - - )} - - )} -
- ); -}; + const t = useAFFiNEI18N(); -const PlanAction = ({ plan }: { plan: string }) => { const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom); const gotoPlansSetting = useCallback(() => { @@ -153,13 +112,120 @@ const PlanAction = ({ plan }: { plan: string }) => { }); }, [setOpenSettingModalAtom]); + const currentPlanDesc = useMemo(() => { + const messageKey = getMessageKey(plan, recurring); + return ( + + ), + }} + /> + ); + }, [plan, recurring, gotoPlansSetting]); + + return ( +
+
+
+ + +
+

+ ${amount} + + / + {recurring === SubscriptionRecurring.Monthly + ? t['com.affine.payment.billing-setting.month']() + : t['com.affine.payment.billing-setting.year']()} + +

+
+ {subscription?.status === SubscriptionStatus.Active && ( + <> + + + + {subscription.nextBillAt && ( + + )} + {subscription.canceledAt ? ( + + + + ) : ( + + + + )} + + )} +
+ ); +}; + +const PlanAction = ({ + plan, + gotoPlansSetting, +}: { + plan: string; + gotoPlansSetting: () => void; +}) => { + const t = useAFFiNEI18N(); + return ( ); }; @@ -169,6 +235,7 @@ const PaymentMethodUpdater = () => { const { isMutating, trigger } = useMutation({ mutation: createCustomerPortalMutation, }); + const t = useAFFiNEI18N(); const update = useCallback(() => { trigger(null, { @@ -179,8 +246,13 @@ const PaymentMethodUpdater = () => { }, [trigger]); return ( - ); }; @@ -190,6 +262,7 @@ const ResumeSubscription = ({ }: { onSubscriptionUpdate: SubscriptionMutator; }) => { + const t = useAFFiNEI18N(); const { isMutating, trigger } = useMutation({ mutation: resumeSubscriptionMutation, }); @@ -203,8 +276,13 @@ const ResumeSubscription = ({ }, [trigger, onSubscriptionUpdate]); return ( - ); }; @@ -237,6 +315,7 @@ const CancelSubscription = ({ }; const BillingHistory = () => { + const t = useAFFiNEI18N(); const { data: invoicesQueryResult } = useQuery({ query: invoicesQuery, variables: { @@ -250,7 +329,9 @@ const BillingHistory = () => { return (
{invoices.length === 0 ? ( -

There are no invoices to display.

+

+ {t['com.affine.payment.billing-setting.no-invoice']()} +

) : ( // TODO: pagination invoices.map(invoice => ( @@ -266,21 +347,28 @@ const InvoiceLine = ({ }: { invoice: NonNullable['invoices'][0]; }) => { + const t = useAFFiNEI18N(); + const open = useCallback(() => { if (invoice.link) { window.open(invoice.link, '_blank', 'noopener noreferrer'); } }, [invoice.link]); + return ( $, cny => ¥ - desc={`${invoice.status === InvoiceStatus.Paid ? 'Paid' : ''} $${ - invoice.amount / 100 - }`} + desc={`${ + invoice.status === InvoiceStatus.Paid + ? t['com.affine.payment.billing-setting.paid']() + : '' + } $${invoice.amount / 100}`} > - + ); }; diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/style.css.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/style.css.ts index 74f87f2c5e..55243b36dd 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/style.css.ts +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/style.css.ts @@ -25,6 +25,10 @@ export const planPrice = style({ fontWeight: 600, }); +export const billingFrequency = style({ + fontSize: 'var(--affine-font-base)', +}); + export const paymentMethod = style({ marginTop: '24px', }); @@ -37,3 +41,13 @@ export const noInvoice = style({ color: 'var(--affine-text-secondary-color)', fontSize: 'var(--affine-font-xs)', }); + +export const currentPlanName = style({ + fontSize: 'var(--affine-font-xs)', + fontWeight: 500, + color: 'var(--affine-text-emphasis-color)', + cursor: 'pointer', +}); +export const button = style({ + padding: '4px 12px', +}); diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx index 97012598b5..241170a1d0 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/index.tsx @@ -51,8 +51,7 @@ export const useGeneralSettingList = (): GeneralSettingList => { }, { key: 'plans', - // TODO: i18n - title: 'AFFiNE Cloud Plans', + title: t['com.affine.payment.title'](), // TODO: icon icon: KeyboardIcon, testId: 'plans-panel-trigger', @@ -75,8 +74,7 @@ export const useGeneralSettingList = (): GeneralSettingList => { if (status === 'authenticated') { settings.splice(3, 0, { key: 'billing', - // TODO: i18n - title: 'Billing', + title: t['com.affine.payment.billing-setting.title'](), // TODO: icon icon: KeyboardIcon, testId: 'billing-panel-trigger', diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 2cc521d8e2..4ae0bfd809 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -333,7 +333,9 @@ "com.affine.storage.extend.hint": "The usage has reached its maximum capacity, AFFiNE Cloud is currently in early access phase and is not supported for upgrading, please be patient and wait for our pricing plan. ", "com.affine.storage.extend.link": "To get more information click here.", "com.affine.storage.title": "AFFiNE Cloud Storage", - "com.affine.storage.upgrade": "Upgrade to Pro", + "com.affine.storage.upgrade": "Upgrade", + "com.affine.storage.change-plan": "Change", + "com.affine.storage.plan": "Plan", "com.affine.storage.used.hint": "Space used", "com.affine.themeSettings.dark": "Dark", "com.affine.themeSettings.light": "Light", @@ -639,5 +641,32 @@ "com.affine.auth.sign-out.confirm-modal.title": "Sign out?", "com.affine.auth.sign-out.confirm-modal.description": "After signing out, the Cloud Workspaces associated with this account will be removed from the current device, and signing in again will add them back.", "com.affine.auth.sign-out.confirm-modal.cancel": "Cancel", - "com.affine.auth.sign-out.confirm-modal.confirm": "Sign Out" + "com.affine.auth.sign-out.confirm-modal.confirm": "Sign Out", + "com.affine.storage.maximum-tips": "You have reached the maximum capacity limit for your current account", + "com.affine.payment.tag-tooltips": "See all plans", + "com.affine.payment.title": "Pricing Plans", + "com.affine.payment.billing-setting.title": "Billing", + "com.affine.payment.billing-setting.subtitle": "Manage your billing information and invoices.", + "com.affine.payment.billing-setting.information": "Information", + "com.affine.payment.billing-setting.history": "Billing history", + "com.affine.payment.billing-setting.current-plan": "Current Plan", + "com.affine.payment.billing-setting.current-plan.description": "You are current on the <1>{{planName}} plan.", + "com.affine.payment.billing-setting.current-plan.description.monthly": "You are current on the monthly <1>{{planName}} plan.", + "com.affine.payment.billing-setting.current-plan.description.yearly": "You are current on the yearly <1>{{planName}} plan.", + "com.affine.payment.billing-setting.month": "month", + "com.affine.payment.billing-setting.year": "year", + "com.affine.payment.billing-setting.payment-method": "Payment Method", + "com.affine.payment.billing-setting.payment-method.description": "Provided by Stripe.", + "com.affine.payment.billing-setting.renew-date": "Renew Date", + "com.affine.payment.billing-setting.renew-date.description": "Next billing date: {{renewDate}}", + "com.affine.payment.billing-setting.expiration-date": "Expiration Date", + "com.affine.payment.billing-setting.expiration-date.description": "Your subscription is valid until {{expirationDate}}", + "com.affine.payment.billing-setting.cancel-subscription": "Cancel Subscription", + "com.affine.payment.billing-setting.cancel-subscription.description": "Subscription cancelled, your pro account will expire on {{cancelDate}}", + "com.affine.payment.billing-setting.upgrade": "Upgrade", + "com.affine.payment.billing-setting.change-plan": "Change Plan", + "com.affine.payment.billing-setting.resume-subscription": "Resume", + "com.affine.payment.billing-setting.no-invoice": "There are no invoices to display.", + "com.affine.payment.billing-setting.paid": "Paid", + "com.affine.payment.billing-setting.view-invoice": "View Invoice" }