chore(server): remove useless free subscription enum (#6634)

This commit is contained in:
forehalo
2024-04-19 13:34:58 +00:00
parent 657a5250ad
commit 098e717688
10 changed files with 53 additions and 83 deletions

View File

@@ -56,7 +56,10 @@ export class UserSubscriptionType implements Partial<UserSubscription> {
@Field({ name: 'id' }) @Field({ name: 'id' })
stripeSubscriptionId!: string; stripeSubscriptionId!: string;
@Field(() => SubscriptionPlan) @Field(() => SubscriptionPlan, {
description:
"The 'Free' plan just exists to be a placeholder and for the type convenience of frontend.\nThere won't actually be a subscription with plan 'Free'",
})
plan!: SubscriptionPlan; plan!: SubscriptionPlan;
@Field(() => SubscriptionRecurring) @Field(() => SubscriptionRecurring)

View File

@@ -443,6 +443,11 @@ type UserSubscription {
end: DateTime! end: DateTime!
id: String! id: String!
nextBillAt: DateTime nextBillAt: DateTime
"""
The 'Free' plan just exists to be a placeholder and for the type convenience of frontend.
There won't actually be a subscription with plan 'Free'
"""
plan: SubscriptionPlan! plan: SubscriptionPlan!
recurring: SubscriptionRecurring! recurring: SubscriptionRecurring!
start: DateTime! start: DateTime!

View File

@@ -21,7 +21,7 @@ export const UserPlanButton = () => {
serverConfigService.serverConfig.features$.map(r => r?.payment) serverConfigService.serverConfig.features$.map(r => r?.payment)
); );
const plan = useLiveData( const plan = useLiveData(
subscriptionService.subscription.primary$.map(subscription => subscriptionService.subscription.pro$.map(subscription =>
subscription !== null ? subscription?.plan : null subscription !== null ? subscription?.plan : null
) )
); );

View File

@@ -1,5 +1,4 @@
import { Button, ErrorMessage, Skeleton, Tooltip } from '@affine/component'; import { Button, ErrorMessage, Skeleton, Tooltip } from '@affine/component';
import { SubscriptionPlan } from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
@@ -45,15 +44,14 @@ export const StorageProgress = ({ onUpgrade }: StorageProgressProgress) => {
subscription.revalidate(); subscription.revalidate();
}, [subscription]); }, [subscription]);
const primarySubscription = useLiveData(subscription.primary$); const proSubscription = useLiveData(subscription.pro$);
const isFreeUser = const isFreeUser = !proSubscription;
!primarySubscription || primarySubscription?.plan === SubscriptionPlan.Free;
const quotaName = useLiveData( const quotaName = useLiveData(
quota.quota$.map(q => (q !== null ? q?.humanReadable.name : null)) quota.quota$.map(q => (q !== null ? q?.humanReadable.name : null))
); );
const loading = const loading =
primarySubscription === null || percent === null || quotaName === null; proSubscription === null || percent === null || quotaName === null;
const loadError = useLiveData(quota.error$); const loadError = useLiveData(quota.error$);
const buttonType = useMemo(() => { const buttonType = useMemo(() => {

View File

@@ -96,47 +96,39 @@ const SubscriptionSettings = () => {
subscriptionService.prices.revalidate(); subscriptionService.prices.revalidate();
}, [subscriptionService]); }, [subscriptionService]);
const primarySubscription = useLiveData( const proSubscription = useLiveData(subscriptionService.subscription.pro$);
subscriptionService.subscription.primary$
);
const proPrice = useLiveData(subscriptionService.prices.proPrice$); const proPrice = useLiveData(subscriptionService.prices.proPrice$);
const currentPlan =
primarySubscription === null
? null
: primarySubscription?.plan ?? SubscriptionPlan.Free;
const [openCancelModal, setOpenCancelModal] = useState(false); const [openCancelModal, setOpenCancelModal] = useState(false);
const recurring =
primarySubscription?.recurring ?? SubscriptionRecurring.Monthly;
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom); const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
const currentPlan = proSubscription?.plan ?? SubscriptionPlan.Free;
const currentRecurring =
proSubscription?.recurring ?? SubscriptionRecurring.Monthly;
const gotoPlansSetting = useCallback(() => { const gotoPlansSetting = useCallback(() => {
mixpanel.track('Button', { mixpanel.track('Button', {
resolve: 'ChangePlan', resolve: 'ChangePlan',
currentPlan: currentPlan, currentPlan: proSubscription?.plan,
}); });
setOpenSettingModalAtom({ setOpenSettingModalAtom({
open: true, open: true,
activeTab: 'plans', activeTab: 'plans',
}); });
}, [currentPlan, setOpenSettingModalAtom]); }, [proSubscription, setOpenSettingModalAtom]);
const amount = currentPlan const amount = proSubscription
? currentPlan === SubscriptionPlan.Free ? proPrice
? '0' ? proSubscription.recurring === SubscriptionRecurring.Monthly
: proPrice
? recurring === SubscriptionRecurring.Monthly
? String((proPrice.amount ?? 0) / 100) ? String((proPrice.amount ?? 0) / 100)
: String((proPrice.yearlyAmount ?? 0) / 100) : String((proPrice.yearlyAmount ?? 0) / 100)
: '?' : '?'
: '?'; : '0';
return ( return (
<div className={styles.subscription}> <div className={styles.subscription}>
{currentPlan !== null ? ( {/* loaded */}
{proSubscription !== null ? (
<div className={styles.planCard}> <div className={styles.planCard}>
<div className={styles.currentPlan}> <div className={styles.currentPlan}>
<SettingRow <SettingRow
@@ -144,7 +136,7 @@ const SubscriptionSettings = () => {
name={t['com.affine.payment.billing-setting.current-plan']()} name={t['com.affine.payment.billing-setting.current-plan']()}
desc={ desc={
<Trans <Trans
i18nKey={getMessageKey(currentPlan, recurring)} i18nKey={getMessageKey(currentPlan, currentRecurring)}
values={{ values={{
planName: currentPlan, planName: currentPlan,
}} }}
@@ -168,7 +160,7 @@ const SubscriptionSettings = () => {
${amount} ${amount}
<span className={styles.billingFrequency}> <span className={styles.billingFrequency}>
/ /
{recurring === SubscriptionRecurring.Monthly {currentRecurring === SubscriptionRecurring.Monthly
? t['com.affine.payment.billing-setting.month']() ? t['com.affine.payment.billing-setting.month']()
: t['com.affine.payment.billing-setting.year']()} : t['com.affine.payment.billing-setting.year']()}
</span> </span>
@@ -179,8 +171,8 @@ const SubscriptionSettings = () => {
)} )}
<AIPlanCard /> <AIPlanCard />
{primarySubscription !== null ? ( {proSubscription !== null ? (
primarySubscription?.status === SubscriptionStatus.Active && ( proSubscription?.status === SubscriptionStatus.Active && (
<> <>
<SettingRow <SettingRow
className={styles.paymentMethod} className={styles.paymentMethod}
@@ -191,26 +183,26 @@ const SubscriptionSettings = () => {
> >
<PaymentMethodUpdater /> <PaymentMethodUpdater />
</SettingRow> </SettingRow>
{primarySubscription.nextBillAt && ( {proSubscription.nextBillAt && (
<SettingRow <SettingRow
name={t['com.affine.payment.billing-setting.renew-date']()} name={t['com.affine.payment.billing-setting.renew-date']()}
desc={t[ desc={t[
'com.affine.payment.billing-setting.renew-date.description' 'com.affine.payment.billing-setting.renew-date.description'
]({ ]({
renewDate: new Date( renewDate: new Date(
primarySubscription.nextBillAt proSubscription.nextBillAt
).toLocaleDateString(), ).toLocaleDateString(),
})} })}
/> />
)} )}
{primarySubscription.canceledAt ? ( {proSubscription.canceledAt ? (
<SettingRow <SettingRow
name={t['com.affine.payment.billing-setting.expiration-date']()} name={t['com.affine.payment.billing-setting.expiration-date']()}
desc={t[ desc={t[
'com.affine.payment.billing-setting.expiration-date.description' 'com.affine.payment.billing-setting.expiration-date.description'
]({ ]({
expirationDate: new Date( expirationDate: new Date(
primarySubscription.end proSubscription.end
).toLocaleDateString(), ).toLocaleDateString(),
})} })}
> >
@@ -322,9 +314,9 @@ const PlanAction = ({
type="primary" type="primary"
onClick={gotoPlansSetting} onClick={gotoPlansSetting}
> >
{plan === SubscriptionPlan.Free {plan === SubscriptionPlan.Pro
? t['com.affine.payment.billing-setting.upgrade']() ? t['com.affine.payment.billing-setting.change-plan']()
: t['com.affine.payment.billing-setting.change-plan']()} : t['com.affine.payment.billing-setting.upgrade']()}
</Button> </Button>
); );
}; };

View File

@@ -36,9 +36,7 @@ const Settings = () => {
const scrollWrapper = useRef<HTMLDivElement>(null); const scrollWrapper = useRef<HTMLDivElement>(null);
const subscriptionService = useService(SubscriptionService); const subscriptionService = useService(SubscriptionService);
const primarySubscription = useLiveData( const proSubscription = useLiveData(subscriptionService.subscription.pro$);
subscriptionService.subscription.primary$
);
const prices = useLiveData(subscriptionService.prices.prices$); const prices = useLiveData(subscriptionService.prices.prices$);
useEffect(() => { useEffect(() => {
@@ -62,13 +60,13 @@ const Settings = () => {
}); });
const [recurring, setRecurring] = useState<SubscriptionRecurring>( const [recurring, setRecurring] = useState<SubscriptionRecurring>(
primarySubscription?.recurring ?? SubscriptionRecurring.Yearly proSubscription?.recurring ?? SubscriptionRecurring.Yearly
); );
const currentPlan = primarySubscription?.plan ?? SubscriptionPlan.Free; const currentPlan = proSubscription?.plan ?? SubscriptionPlan.Free;
const isCanceled = !!primarySubscription?.canceledAt; const isCanceled = !!proSubscription?.canceledAt;
const currentRecurring = const currentRecurring =
primarySubscription?.recurring ?? SubscriptionRecurring.Monthly; proSubscription?.recurring ?? SubscriptionRecurring.Monthly;
const yearlyDiscount = ( const yearlyDiscount = (
planDetail.get(SubscriptionPlan.Pro) as FixedPrice | undefined planDetail.get(SubscriptionPlan.Pro) as FixedPrice | undefined

View File

@@ -32,15 +32,13 @@ export const PlanCard = (props: PlanCardProps) => {
const loggedIn = const loggedIn =
useLiveData(useService(AuthService).session.status$) === 'authenticated'; useLiveData(useService(AuthService).session.status$) === 'authenticated';
const subscriptionService = useService(SubscriptionService); const subscriptionService = useService(SubscriptionService);
const primarySubscription = useLiveData( const proSubscription = useLiveData(subscriptionService.subscription.pro$);
subscriptionService.subscription.primary$ const currentPlan = proSubscription?.plan ?? SubscriptionPlan.Free;
);
const currentPlan = primarySubscription?.plan ?? SubscriptionPlan.Free;
const isCurrent = const isCurrent =
loggedIn && loggedIn &&
detail.plan === currentPlan && detail.plan === currentPlan &&
recurring === primarySubscription?.recurring; recurring === proSubscription?.recurring;
const isPro = detail.plan === SubscriptionPlan.Pro; const isPro = detail.plan === SubscriptionPlan.Pro;
return ( return (
@@ -93,7 +91,7 @@ const ActionButton = ({ detail, recurring }: PlanCardProps) => {
useLiveData(useService(AuthService).session.status$) === 'authenticated'; useLiveData(useService(AuthService).session.status$) === 'authenticated';
const subscriptionService = useService(SubscriptionService); const subscriptionService = useService(SubscriptionService);
const primarySubscription = useLiveData( const primarySubscription = useLiveData(
subscriptionService.subscription.primary$ subscriptionService.subscription.pro$
); );
const currentPlan = primarySubscription?.plan ?? SubscriptionPlan.Free; const currentPlan = primarySubscription?.plan ?? SubscriptionPlan.Free;
const currentRecurring = primarySubscription?.recurring; const currentRecurring = primarySubscription?.recurring;

View File

@@ -24,7 +24,7 @@ import { useRevokeMemberPermission } from '@affine/core/hooks/affine/use-revoke-
import { WorkspacePermissionService } from '@affine/core/modules/permissions'; import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { WorkspaceFlavour } from '@affine/env/workspace'; import { WorkspaceFlavour } from '@affine/env/workspace';
import { Permission, SubscriptionPlan } from '@affine/graphql'; import { Permission } from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { MoreVerticalIcon } from '@blocksuite/icons'; import { MoreVerticalIcon } from '@blocksuite/icons';
import { import {
@@ -98,7 +98,7 @@ export const CloudWorkspaceMembersPanel = () => {
const workspaceQuota = useLiveData(workspaceQuotaService.quota.quota$); const workspaceQuota = useLiveData(workspaceQuotaService.quota.quota$);
const subscriptionService = useService(SubscriptionService); const subscriptionService = useService(SubscriptionService);
const plan = useLiveData( const plan = useLiveData(
subscriptionService.subscription.primary$.map(s => s?.plan) subscriptionService.subscription.pro$.map(s => s?.plan)
); );
const isLimited = workspaceQuota const isLimited = workspaceQuota
? checkMemberCountLimit(memberCount, workspaceQuota.memberLimit) ? checkMemberCountLimit(memberCount, workspaceQuota.memberLimit)
@@ -206,7 +206,7 @@ export const CloudWorkspaceMembersPanel = () => {
<Button onClick={openModal}>{t['Invite Members']()}</Button> <Button onClick={openModal}>{t['Invite Members']()}</Button>
{isLimited ? ( {isLimited ? (
<MemberLimitModal <MemberLimitModal
isFreePlan={plan === SubscriptionPlan.Free} isFreePlan={!!plan}
open={open} open={open}
plan={workspaceQuota?.humanReadable.name ?? ''} plan={workspaceQuota?.humanReadable.name ?? ''}
quota={workspaceQuota?.humanReadable.memberLimit ?? ''} quota={workspaceQuota?.humanReadable.memberLimit ?? ''}

View File

@@ -21,8 +21,7 @@ const UserInfo = () => {
useEffect(() => { useEffect(() => {
subscription.revalidate(); subscription.revalidate();
}, [subscription]); }, [subscription]);
const primary = useLiveData(subscription.primary$); const plan = useLiveData(subscription.pro$)?.plan;
const plan = primary?.plan;
if (!user) { if (!user) {
// TODO: loading UI // TODO: loading UI

View File

@@ -28,34 +28,11 @@ export class Subscription extends Entity {
isRevalidating$ = new LiveData(false); isRevalidating$ = new LiveData(false);
error$ = new LiveData<any | null>(null); error$ = new LiveData<any | null>(null);
/**
* Primary subscription is the subscription that is not AI.
*/
primary$ = this.subscription$.map(subscriptions =>
subscriptions
? subscriptions.find(sub => sub.plan !== SubscriptionPlan.AI)
: null
);
isFree$ = this.subscription$.map(subscriptions =>
subscriptions
? subscriptions.some(sub => sub.plan === SubscriptionPlan.Free)
: null
);
isPro$ = this.subscription$.map(subscriptions =>
subscriptions
? subscriptions.some(sub => sub.plan === SubscriptionPlan.Pro)
: null
);
pro$ = this.subscription$.map(subscriptions => pro$ = this.subscription$.map(subscriptions =>
subscriptions subscriptions
? subscriptions.find(sub => sub.plan === SubscriptionPlan.Pro) ? subscriptions.find(sub => sub.plan === SubscriptionPlan.Pro)
: null : null
); );
isSelfHosted$ = this.subscription$.map(subscriptions =>
subscriptions
? subscriptions.some(sub => sub.plan === SubscriptionPlan.SelfHosted)
: null
);
ai$ = this.subscription$.map(subscriptions => ai$ = this.subscription$.map(subscriptions =>
subscriptions subscriptions
? subscriptions.find(sub => sub.plan === SubscriptionPlan.AI) ? subscriptions.find(sub => sub.plan === SubscriptionPlan.AI)