feat(core): add upgrade success page (#4738)
This commit is contained in:
@@ -194,8 +194,7 @@ export class SubscriptionResolver {
|
|||||||
const session = await this.service.createCheckoutSession({
|
const session = await this.service.createCheckoutSession({
|
||||||
user,
|
user,
|
||||||
recurring,
|
recurring,
|
||||||
// TODO: replace with frontend url
|
redirectUrl: `${this.config.baseUrl}/upgrade-success`,
|
||||||
redirectUrl: `${this.config.baseUrl}/api/stripe/success`,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!session.url) {
|
if (!session.url) {
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import type { RawBodyRequest } from '@nestjs/common';
|
import type { RawBodyRequest } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
|
||||||
Logger,
|
Logger,
|
||||||
NotAcceptableException,
|
NotAcceptableException,
|
||||||
Post,
|
Post,
|
||||||
Req,
|
Req,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import type { User } from '@prisma/client';
|
|
||||||
import type { Request } from 'express';
|
import type { Request } from 'express';
|
||||||
import Stripe from 'stripe';
|
import Stripe from 'stripe';
|
||||||
|
|
||||||
import { Config } from '../../config';
|
import { Config } from '../../config';
|
||||||
import { PrismaService } from '../../prisma';
|
|
||||||
import { Auth, CurrentUser } from '../auth';
|
|
||||||
|
|
||||||
@Controller('/api/stripe')
|
@Controller('/api/stripe')
|
||||||
export class StripeWebhook {
|
export class StripeWebhook {
|
||||||
@@ -24,23 +20,11 @@ export class StripeWebhook {
|
|||||||
constructor(
|
constructor(
|
||||||
config: Config,
|
config: Config,
|
||||||
private readonly stripe: Stripe,
|
private readonly stripe: Stripe,
|
||||||
private readonly event: EventEmitter2,
|
private readonly event: EventEmitter2
|
||||||
private readonly db: PrismaService
|
|
||||||
) {
|
) {
|
||||||
this.config = config.payment;
|
this.config = config.payment;
|
||||||
}
|
}
|
||||||
|
|
||||||
// just for test
|
|
||||||
@Auth()
|
|
||||||
@Get('/success')
|
|
||||||
async handleSuccess(@CurrentUser() user: User) {
|
|
||||||
return this.db.userSubscription.findUnique({
|
|
||||||
where: {
|
|
||||||
userId: user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/webhook')
|
@Post('/webhook')
|
||||||
async handleWebhook(@Req() req: RawBodyRequest<Request>) {
|
async handleWebhook(@Req() req: RawBodyRequest<Request>) {
|
||||||
// Check if webhook signing is configured.
|
// Check if webhook signing is configured.
|
||||||
|
|||||||
93
packages/frontend/core/src/pages/upgrade-success.css.ts
Normal file
93
packages/frontend/core/src/pages/upgrade-success.css.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const root = style({
|
||||||
|
height: '100vh',
|
||||||
|
width: '100vw',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const affineLogo = style({
|
||||||
|
color: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const topNav = style({
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: '16px 120px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const topNavLinks = style({
|
||||||
|
display: 'flex',
|
||||||
|
columnGap: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const topNavLink = style({
|
||||||
|
color: 'var(--affine-text-primary-color)',
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
fontWeight: 500,
|
||||||
|
textDecoration: 'none',
|
||||||
|
padding: '4px 18px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tryAgainLink = style({
|
||||||
|
color: 'var(--affine-link-color)',
|
||||||
|
fontWeight: 500,
|
||||||
|
textDecoration: 'none',
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const centerContent = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 40,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const prompt = style({
|
||||||
|
marginTop: 20,
|
||||||
|
marginBottom: 12,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const body = style({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: '100%',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '48px',
|
||||||
|
padding: '0 20px',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const leftContainer = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '548px',
|
||||||
|
gap: '28px',
|
||||||
|
});
|
||||||
|
export const leftContentTitle = style({
|
||||||
|
fontSize: 'var(--affine-font-title)',
|
||||||
|
fontWeight: 700,
|
||||||
|
minHeight: '44px',
|
||||||
|
});
|
||||||
|
export const leftContentText = style({
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: '1.6',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mail = style({
|
||||||
|
color: 'var(--affine-link-color)',
|
||||||
|
textDecoration: 'none',
|
||||||
|
':visited': {
|
||||||
|
color: 'var(--affine-link-color)',
|
||||||
|
},
|
||||||
|
});
|
||||||
105
packages/frontend/core/src/pages/upgrade-success.tsx
Normal file
105
packages/frontend/core/src/pages/upgrade-success.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { Empty } from '@affine/component';
|
||||||
|
import { Trans } from '@affine/i18n';
|
||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { Logo1Icon } from '@blocksuite/icons';
|
||||||
|
import { Button } from '@toeverything/components/button';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||||
|
import * as styles from './upgrade-success.css';
|
||||||
|
|
||||||
|
export const UpgradeSuccess = () => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
|
const openDownloadLink = useCallback(() => {
|
||||||
|
const url = `https://affine.pro/download`;
|
||||||
|
open(url, '_blank');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const { jumpToIndex } = useNavigateHelper();
|
||||||
|
const openAffine = useCallback(() => {
|
||||||
|
jumpToIndex();
|
||||||
|
}, [jumpToIndex]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<div className={styles.topNav}>
|
||||||
|
<a href="/" rel="noreferrer" className={styles.affineLogo}>
|
||||||
|
<Logo1Icon width={24} height={24} />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div className={styles.topNavLinks}>
|
||||||
|
<a
|
||||||
|
href="https://affine.pro"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.topNavLink}
|
||||||
|
>
|
||||||
|
{t['com.affine.other-page.nav.official-website']()}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://community.affine.pro/home"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.topNavLink}
|
||||||
|
>
|
||||||
|
{t['com.affine.other-page.nav.affine-community']()}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://affine.pro/blog"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.topNavLink}
|
||||||
|
>
|
||||||
|
{t['com.affine.other-page.nav.blog']()}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://affine.pro/about-us"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className={styles.topNavLink}
|
||||||
|
>
|
||||||
|
{t['com.affine.other-page.nav.contact-us']()}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button onClick={openDownloadLink}>
|
||||||
|
{t['com.affine.auth.open.affine.download-app']()}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.body}>
|
||||||
|
<div className={styles.leftContainer}>
|
||||||
|
<div className={styles.leftContentTitle}>
|
||||||
|
{t['com.affine.payment.upgrade-success-page.title']()}
|
||||||
|
</div>
|
||||||
|
<div className={styles.leftContentText}>
|
||||||
|
{t['com.affine.payment.upgrade-success-page.text']()}
|
||||||
|
<div>
|
||||||
|
<Trans
|
||||||
|
i18nKey={'com.affine.payment.upgrade-success-page.support'}
|
||||||
|
components={{
|
||||||
|
1: (
|
||||||
|
<a
|
||||||
|
href="mailto:support@toeverything.info"
|
||||||
|
className={styles.mail}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button type="primary" size="extraLarge" onClick={openAffine}>
|
||||||
|
{t['com.affine.other-page.nav.open-affine']()}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Empty />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Component = () => {
|
||||||
|
return <UpgradeSuccess />;
|
||||||
|
};
|
||||||
@@ -52,6 +52,10 @@ export const routes = [
|
|||||||
path: '/open-app/:action',
|
path: '/open-app/:action',
|
||||||
lazy: () => import('./pages/open-app'),
|
lazy: () => import('./pages/open-app'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/upgrade-success',
|
||||||
|
lazy: () => import('./pages/upgrade-success'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/desktop-signin',
|
path: '/desktop-signin',
|
||||||
lazy: () => import('./pages/desktop-signin'),
|
lazy: () => import('./pages/desktop-signin'),
|
||||||
|
|||||||
@@ -712,5 +712,14 @@
|
|||||||
"com.affine.payment.billing-setting.no-invoice": "There are no invoices to display.",
|
"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.paid": "Paid",
|
||||||
"com.affine.payment.billing-setting.view-invoice": "View Invoice",
|
"com.affine.payment.billing-setting.view-invoice": "View Invoice",
|
||||||
|
"com.affine.payment.upgrade-success-page.title": "Upgrade Successful!",
|
||||||
|
"com.affine.payment.upgrade-success-page.text": "Congratulations! Your AFFiNE account has been successfully upgraded to a Pro account.",
|
||||||
|
"com.affine.payment.upgrade-success-page.support": "If you have any questions, please contact our <1> customer support</1>.",
|
||||||
|
"com.affine.other-page.nav.official-website": "Official Website",
|
||||||
|
"com.affine.other-page.nav.affine-community": "AFFiNE Community",
|
||||||
|
"com.affine.other-page.nav.blog": "Blog",
|
||||||
|
"com.affine.other-page.nav.contact-us": "Contact us",
|
||||||
|
"com.affine.other-page.nav.download-app": "Download App",
|
||||||
|
"com.affine.other-page.nav.open-affine": "Open AFFiNE",
|
||||||
"com.affine.payment.member.description": "Manage members here. {{planName}} Users can invite up to {{memberLimit}}"
|
"com.affine.payment.member.description": "Manage members here. {{planName}} Users can invite up to {{memberLimit}}"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user