feat(core): onetime subscription ui (#8462)
This commit is contained in:
parent
69fb5c06f4
commit
29a31110cd
@ -94,6 +94,7 @@ const SubscriptionSettings = () => {
|
||||
const proSubscription = useLiveData(subscriptionService.subscription.pro$);
|
||||
const proPrice = useLiveData(subscriptionService.prices.proPrice$);
|
||||
const isBeliever = useLiveData(subscriptionService.subscription.isBeliever$);
|
||||
const isOnetime = useLiveData(subscriptionService.subscription.isOnetime$);
|
||||
|
||||
const [openCancelModal, setOpenCancelModal] = useState(false);
|
||||
const setOpenSettingModalAtom = useSetAtom(openSettingModalAtom);
|
||||
@ -206,7 +207,17 @@ const SubscriptionSettings = () => {
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{isBeliever ? null : proSubscription.end &&
|
||||
{isOnetime && proSubscription.end && (
|
||||
<SettingRow
|
||||
name={t['com.affine.payment.billing-setting.due-date']()}
|
||||
desc={t[
|
||||
'com.affine.payment.billing-setting.due-date.description'
|
||||
]({
|
||||
dueDate: new Date(proSubscription.end).toLocaleDateString(),
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{isBeliever || isOnetime ? null : proSubscription.end &&
|
||||
proSubscription.canceledAt ? (
|
||||
<SettingRow
|
||||
name={t['com.affine.payment.billing-setting.expiration-date']()}
|
||||
|
@ -4,7 +4,7 @@ import { SubscriptionRecurring } from '@affine/graphql';
|
||||
import { Trans, useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
|
||||
import { Upgrade } from '../plan-card';
|
||||
import { RedeemCode, Upgrade } from '../plan-card';
|
||||
import { BelieverCard } from './believer-card';
|
||||
import { BelieverBenefits } from './benefits';
|
||||
import * as styles from './style.css';
|
||||
@ -17,6 +17,7 @@ export const LifetimePlan = () => {
|
||||
subscriptionService.prices.readableLifetimePrice$
|
||||
);
|
||||
const isBeliever = useLiveData(subscriptionService.subscription.isBeliever$);
|
||||
const isOnetime = useLiveData(subscriptionService.subscription.isOnetime$);
|
||||
|
||||
if (!readableLifetimePrice) return null;
|
||||
|
||||
@ -36,6 +37,8 @@ export const LifetimePlan = () => {
|
||||
<Button className={styles.purchase} size="default" disabled>
|
||||
{t['com.affine.payment.lifetime.purchased']()}
|
||||
</Button>
|
||||
) : isOnetime ? (
|
||||
<RedeemCode className={styles.purchase} size="default" />
|
||||
) : (
|
||||
<Upgrade
|
||||
className={styles.purchase}
|
||||
|
@ -4,8 +4,15 @@ import { generateSubscriptionCallbackLink } from '@affine/core/components/hooks/
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { AuthService, SubscriptionService } from '@affine/core/modules/cloud';
|
||||
import { popupWindow } from '@affine/core/utils';
|
||||
import type { SubscriptionRecurring } from '@affine/graphql';
|
||||
import { SubscriptionPlan, SubscriptionStatus } from '@affine/graphql';
|
||||
import {
|
||||
type CreateCheckoutSessionInput,
|
||||
SubscriptionRecurring,
|
||||
} from '@affine/graphql';
|
||||
import {
|
||||
SubscriptionPlan,
|
||||
SubscriptionStatus,
|
||||
SubscriptionVariant,
|
||||
} from '@affine/graphql';
|
||||
import { Trans, useI18n } from '@affine/i18n';
|
||||
import { track } from '@affine/track';
|
||||
import { DoneIcon } from '@blocksuite/icons/rc';
|
||||
@ -96,6 +103,7 @@ const ActionButton = ({ detail, recurring }: PlanCardProps) => {
|
||||
);
|
||||
const currentPlan = primarySubscription?.plan ?? SubscriptionPlan.Free;
|
||||
const currentRecurring = primarySubscription?.recurring;
|
||||
const isOnetime = useLiveData(subscriptionService.subscription.isOnetime$);
|
||||
|
||||
// branches:
|
||||
// if contact => 'Contact Sales'
|
||||
@ -104,12 +112,12 @@ const ActionButton = ({ detail, recurring }: PlanCardProps) => {
|
||||
// else => 'Buy Pro'
|
||||
// else
|
||||
// if isBeliever => 'Included in Lifetime'
|
||||
// if onetime => 'Redeem Code'
|
||||
// if isCurrent
|
||||
// if canceled => 'Resume'
|
||||
// else => 'Current Plan'
|
||||
// if isCurrent => 'Current Plan'
|
||||
// else if free => 'Downgrade'
|
||||
// else if currentRecurring !== recurring => 'Change to {recurring} Billing'
|
||||
// if free => 'Downgrade'
|
||||
// if currentRecurring !== recurring => 'Change to {recurring} Billing'
|
||||
// else => 'Upgrade'
|
||||
|
||||
// contact
|
||||
@ -137,6 +145,11 @@ const ActionButton = ({ detail, recurring }: PlanCardProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
// onetime
|
||||
if (isOnetime) {
|
||||
return <RedeemCode recurring={recurring} />;
|
||||
}
|
||||
|
||||
const isCanceled = !!primarySubscription?.canceledAt;
|
||||
const isFree = detail.plan === SubscriptionPlan.Free;
|
||||
const isCurrent =
|
||||
@ -242,9 +255,11 @@ export const Upgrade = ({
|
||||
className,
|
||||
recurring,
|
||||
children,
|
||||
checkoutInput,
|
||||
...btnProps
|
||||
}: ButtonProps & {
|
||||
recurring: SubscriptionRecurring;
|
||||
checkoutInput?: Partial<CreateCheckoutSessionInput>;
|
||||
}) => {
|
||||
const [isMutating, setMutating] = useState(false);
|
||||
const [isOpenedExternalWindow, setOpenedExternalWindow] = useState(false);
|
||||
@ -289,6 +304,7 @@ export const Upgrade = ({
|
||||
SubscriptionPlan.Pro,
|
||||
recurring
|
||||
),
|
||||
...checkoutInput,
|
||||
});
|
||||
setMutating(false);
|
||||
setIdempotencyKey(nanoid());
|
||||
@ -299,6 +315,7 @@ export const Upgrade = ({
|
||||
authService.session.account$.value,
|
||||
subscriptionService,
|
||||
idempotencyKey,
|
||||
checkoutInput,
|
||||
]);
|
||||
|
||||
return (
|
||||
@ -435,3 +452,24 @@ const ResumeButton = () => {
|
||||
</ResumeAction>
|
||||
);
|
||||
};
|
||||
|
||||
const redeemCodeCheckoutInput = { variant: SubscriptionVariant.Onetime };
|
||||
export const RedeemCode = ({
|
||||
className,
|
||||
recurring = SubscriptionRecurring.Yearly,
|
||||
children,
|
||||
...btnProps
|
||||
}: ButtonProps & { recurring?: SubscriptionRecurring }) => {
|
||||
const t = useI18n();
|
||||
|
||||
return (
|
||||
<Upgrade
|
||||
recurring={recurring}
|
||||
className={className}
|
||||
checkoutInput={redeemCodeCheckoutInput}
|
||||
{...btnProps}
|
||||
>
|
||||
{children ?? t['com.affine.payment.redeem-code']()}
|
||||
</Upgrade>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { type SubscriptionQuery, SubscriptionRecurring } from '@affine/graphql';
|
||||
import {
|
||||
type SubscriptionQuery,
|
||||
SubscriptionRecurring,
|
||||
SubscriptionVariant,
|
||||
} from '@affine/graphql';
|
||||
import { SubscriptionPlan } from '@affine/graphql';
|
||||
import {
|
||||
backoffRetry,
|
||||
@ -41,6 +45,9 @@ export class Subscription extends Entity {
|
||||
isBeliever$ = this.pro$.map(
|
||||
sub => sub?.recurring === SubscriptionRecurring.Lifetime
|
||||
);
|
||||
isOnetime$ = this.pro$.map(
|
||||
sub => sub?.variant === SubscriptionVariant.Onetime
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
|
@ -1037,6 +1037,7 @@ query subscription {
|
||||
end
|
||||
nextBillAt
|
||||
canceledAt
|
||||
variant
|
||||
}
|
||||
}
|
||||
}`,
|
||||
|
@ -10,6 +10,7 @@ query subscription {
|
||||
end
|
||||
nextBillAt
|
||||
canceledAt
|
||||
variant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1141,6 +1141,7 @@ export interface UserSubscription {
|
||||
trialEnd: Maybe<Scalars['DateTime']['output']>;
|
||||
trialStart: Maybe<Scalars['DateTime']['output']>;
|
||||
updatedAt: Scalars['DateTime']['output'];
|
||||
variant: Maybe<SubscriptionVariant>;
|
||||
}
|
||||
|
||||
export interface UserType {
|
||||
@ -2210,6 +2211,7 @@ export type SubscriptionQuery = {
|
||||
end: string | null;
|
||||
nextBillAt: string | null;
|
||||
canceledAt: string | null;
|
||||
variant: SubscriptionVariant | null;
|
||||
}>;
|
||||
} | null;
|
||||
};
|
||||
|
@ -742,6 +742,8 @@
|
||||
"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.due-date": "Due date",
|
||||
"com.affine.payment.billing-setting.due-date.description": "Your subscription will end on {{dueDate}}",
|
||||
"com.affine.payment.billing-setting.resume-subscription": "Resume",
|
||||
"com.affine.payment.billing-setting.subtitle": "Manage your billing information and invoices.",
|
||||
"com.affine.payment.billing-setting.title": "Billing",
|
||||
@ -870,6 +872,7 @@
|
||||
"com.affine.payment.updated-notify-msg": "You have changed your plan to {{plan}} billing.",
|
||||
"com.affine.payment.updated-notify-title": "Subscription updated",
|
||||
"com.affine.payment.upgrade": "Upgrade",
|
||||
"com.affine.payment.redeem-code": "Redeem code",
|
||||
"com.affine.payment.upgrade-success-notify.content": "We'd like to hear more about your use case, so that we can make AFFiNE better.",
|
||||
"com.affine.payment.upgrade-success-notify.later": "Later",
|
||||
"com.affine.payment.upgrade-success-notify.ok-client": "Sure, open in browser",
|
||||
|
Loading…
x
Reference in New Issue
Block a user