mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Server: Allow user to upgrade account
This commit is contained in:
parent
75a421edb1
commit
e83ab93644
@ -1539,6 +1539,9 @@ packages/lib/time.js.map
|
||||
packages/lib/utils/credentialFiles.d.ts
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/credentialFiles.js.map
|
||||
packages/lib/utils/joplinCloud.d.ts
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/joplinCloud.js.map
|
||||
packages/lib/uuid.d.ts
|
||||
packages/lib/uuid.js
|
||||
packages/lib/uuid.js.map
|
||||
@ -1698,9 +1701,6 @@ packages/tools/update-readme-sponsors.js.map
|
||||
packages/tools/website/build.d.ts
|
||||
packages/tools/website/build.js
|
||||
packages/tools/website/build.js.map
|
||||
packages/tools/website/utils/plans.d.ts
|
||||
packages/tools/website/utils/plans.js
|
||||
packages/tools/website/utils/plans.js.map
|
||||
packages/tools/website/utils/pressCarousel.d.ts
|
||||
packages/tools/website/utils/pressCarousel.js
|
||||
packages/tools/website/utils/pressCarousel.js.map
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1524,6 +1524,9 @@ packages/lib/time.js.map
|
||||
packages/lib/utils/credentialFiles.d.ts
|
||||
packages/lib/utils/credentialFiles.js
|
||||
packages/lib/utils/credentialFiles.js.map
|
||||
packages/lib/utils/joplinCloud.d.ts
|
||||
packages/lib/utils/joplinCloud.js
|
||||
packages/lib/utils/joplinCloud.js.map
|
||||
packages/lib/uuid.d.ts
|
||||
packages/lib/uuid.js
|
||||
packages/lib/uuid.js.map
|
||||
@ -1683,9 +1686,6 @@ packages/tools/update-readme-sponsors.js.map
|
||||
packages/tools/website/build.d.ts
|
||||
packages/tools/website/build.js
|
||||
packages/tools/website/build.js.map
|
||||
packages/tools/website/utils/plans.d.ts
|
||||
packages/tools/website/utils/plans.js
|
||||
packages/tools/website/utils/plans.js.map
|
||||
packages/tools/website/utils/pressCarousel.d.ts
|
||||
packages/tools/website/utils/pressCarousel.js
|
||||
packages/tools/website/utils/pressCarousel.js.map
|
||||
|
@ -1,6 +1,47 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export interface Plan {
|
||||
name: string;
|
||||
title: string;
|
||||
price: string;
|
||||
stripePriceId: string;
|
||||
featured: boolean;
|
||||
iconName: string;
|
||||
featuresOn: string[];
|
||||
featuresOff: string[];
|
||||
cfaLabel: string;
|
||||
cfaUrl: string;
|
||||
}
|
||||
|
||||
import { Plan, StripePublicConfig } from './types';
|
||||
export interface StripePublicConfig {
|
||||
publishableKey: string;
|
||||
basicPriceId: string;
|
||||
proPriceId: string;
|
||||
webhookBaseUrl: string;
|
||||
}
|
||||
|
||||
export interface PlanFeature {
|
||||
label: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export function getFeatureList(plan: Plan): PlanFeature[] {
|
||||
const output: PlanFeature[] = [];
|
||||
|
||||
for (const f of plan.featuresOn) {
|
||||
output.push({
|
||||
label: f,
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
|
||||
for (const f of plan.featuresOff) {
|
||||
output.push({
|
||||
label: f,
|
||||
enabled: false,
|
||||
});
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const businessAccountEmailBody = `Hello,
|
||||
|
0
packages/server/public/css/index/home.css
Normal file
0
packages/server/public/css/index/home.css
Normal file
3
packages/server/public/css/index/upgrade.css
Normal file
3
packages/server/public/css/index/upgrade.css
Normal file
@ -0,0 +1,3 @@
|
||||
.basic-plan {
|
||||
opacity: 0.8;
|
||||
}
|
@ -8,6 +8,7 @@ export enum NotificationKey {
|
||||
EmailConfirmed = 'emailConfirmed',
|
||||
ChangeAdminPassword = 'change_admin_password',
|
||||
UsingSqliteInProd = 'using_sqlite_in_prod',
|
||||
UpgradedToPro = 'upgraded_to_pro',
|
||||
}
|
||||
|
||||
interface NotificationType {
|
||||
@ -47,6 +48,10 @@ export default class NotificationModel extends BaseModel<Notification> {
|
||||
level: NotificationLevel.Important,
|
||||
message: 'The server is currently using SQLite3 as a database. It is not recommended in production as it is slow and can cause locking issues. Please see the README for information on how to change it.',
|
||||
},
|
||||
[NotificationKey.UpgradedToPro]: {
|
||||
level: NotificationLevel.Normal,
|
||||
message: 'Thank you! Your account has been successfully upgraded to Pro.',
|
||||
},
|
||||
};
|
||||
|
||||
const type = notificationTypes[key];
|
||||
|
@ -5,7 +5,7 @@ import { AppContext } from '../../utils/types';
|
||||
import { contextSessionId } from '../../utils/requestUtils';
|
||||
import { ErrorMethodNotAllowed } from '../../utils/errors';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
import { accountTypeToString } from '../../models/UserModel';
|
||||
import { AccountType, accountTypeToString } from '../../models/UserModel';
|
||||
import { formatMaxItemSize, formatMaxTotalSize, formatTotalSize, formatTotalSizePercent, yesOrNo } from '../../utils/strings';
|
||||
import { getCanShareFolder, totalSizeClass } from '../../models/utils/user';
|
||||
|
||||
@ -16,6 +16,7 @@ router.get('home', async (_path: SubPath, ctx: AppContext) => {
|
||||
|
||||
if (ctx.method === 'GET') {
|
||||
const user = ctx.joplin.owner;
|
||||
const subscription = await ctx.joplin.models.subscription().byUserId(user.id);
|
||||
|
||||
const view = defaultView('home', 'Home');
|
||||
view.content = {
|
||||
@ -57,8 +58,11 @@ router.get('home', async (_path: SubPath, ctx: AppContext) => {
|
||||
show: true,
|
||||
},
|
||||
],
|
||||
showUpgradeProButton: subscription && user.account_type === AccountType.Basic,
|
||||
};
|
||||
|
||||
view.cssFiles = ['index/home'];
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
75
packages/server/src/routes/index/upgrade.ts
Normal file
75
packages/server/src/routes/index/upgrade.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { SubPath, redirect } from '../../utils/routeUtils';
|
||||
import Router from '../../utils/Router';
|
||||
import { RouteType } from '../../utils/types';
|
||||
import { AppContext } from '../../utils/types';
|
||||
import { getFeatureList, getPlans } from '@joplin/lib/utils/joplinCloud';
|
||||
import config from '../../config';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
import { stripeConfig, updateSubscriptionType } from '../../utils/stripe';
|
||||
import { bodyFields } from '../../utils/requestUtils';
|
||||
import { NotificationKey } from '../../models/NotificationModel';
|
||||
import { AccountType } from '../../models/UserModel';
|
||||
import { ErrorBadRequest } from '../../utils/errors';
|
||||
|
||||
interface FormFields {
|
||||
upgrade_button: string;
|
||||
}
|
||||
|
||||
const router: Router = new Router(RouteType.Web);
|
||||
|
||||
function upgradeUrl() {
|
||||
return `${config().baseUrl}/upgrade`;
|
||||
}
|
||||
|
||||
router.get('upgrade', async (_path: SubPath, _ctx: AppContext) => {
|
||||
interface PlanRow {
|
||||
basicLabel: string;
|
||||
proLabel: string;
|
||||
}
|
||||
|
||||
const plans = getPlans(stripeConfig());
|
||||
const basicFeatureList = getFeatureList(plans.basic);
|
||||
const proFeatureList = getFeatureList(plans.pro);
|
||||
|
||||
const planRows: PlanRow[] = [];
|
||||
|
||||
for (let i = 0; i < basicFeatureList.length; i++) {
|
||||
const basic = basicFeatureList[i];
|
||||
const pro = proFeatureList[i];
|
||||
|
||||
if (basic.label === pro.label && basic.enabled === pro.enabled) continue;
|
||||
|
||||
planRows.push({
|
||||
basicLabel: basic.enabled ? basic.label : '-',
|
||||
proLabel: pro.label,
|
||||
});
|
||||
}
|
||||
|
||||
const view = defaultView('upgrade', 'Upgrade');
|
||||
view.content = {
|
||||
planRows,
|
||||
basicPrice: plans.basic.price,
|
||||
proPrice: plans.pro.price,
|
||||
postUrl: upgradeUrl(),
|
||||
};
|
||||
view.cssFiles = ['index/upgrade'];
|
||||
return view;
|
||||
});
|
||||
|
||||
router.post('upgrade', async (_path: SubPath, ctx: AppContext) => {
|
||||
const fields = await bodyFields<FormFields>(ctx.req);
|
||||
|
||||
const joplin = ctx.joplin;
|
||||
const models = joplin.models;
|
||||
|
||||
if (fields.upgrade_button) {
|
||||
await updateSubscriptionType(models, joplin.owner.id, AccountType.Pro);
|
||||
await models.user().save({ id: joplin.owner.id, account_type: AccountType.Pro });
|
||||
await models.notification().add(joplin.owner.id, NotificationKey.UpgradedToPro);
|
||||
return redirect(ctx, `${config().baseUrl}/home`);
|
||||
}
|
||||
|
||||
throw new ErrorBadRequest('Invalid button');
|
||||
});
|
||||
|
||||
export default router;
|
@ -24,6 +24,7 @@ import indexUsers from './index/users';
|
||||
import indexStripe from './index/stripe';
|
||||
import indexTerms from './index/terms';
|
||||
import indexPrivacy from './index/privacy';
|
||||
import indexUpgrade from './index/upgrade';
|
||||
|
||||
import defaultRoute from './default';
|
||||
|
||||
@ -52,6 +53,7 @@ const routes: Routers = {
|
||||
'stripe': indexStripe,
|
||||
'terms': indexTerms,
|
||||
'privacy': indexPrivacy,
|
||||
'upgrade': indexUpgrade,
|
||||
|
||||
'': defaultRoute,
|
||||
};
|
||||
|
@ -1,19 +1,25 @@
|
||||
<h1 class="title">Welcome to {{global.appName}}</h1>
|
||||
<p class="subtitle">Logged in as <strong>{{global.userDisplayName}}</strong></p>
|
||||
<h1 class="title">Welcome to {{global.appName}}</h1>
|
||||
<p class="subtitle">Logged in as <strong>{{global.userDisplayName}}</strong></p>
|
||||
|
||||
<table class="table is-hoverable">
|
||||
<tbody>
|
||||
{{#userProps}}
|
||||
{{#show}}
|
||||
<tr>
|
||||
<td class="{{#classes}}{{.}}{{/classes}}">
|
||||
<strong>{{label}}</strong>
|
||||
</td>
|
||||
<td class="{{#classes}}{{.}}{{/classes}}">
|
||||
{{value}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/show}}
|
||||
{{/userProps}}
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table is-hoverable user-props-table">
|
||||
<tbody>
|
||||
{{#userProps}}
|
||||
{{#show}}
|
||||
<tr>
|
||||
<td class="{{#classes}}{{.}}{{/classes}} prop-name">
|
||||
<strong>{{label}}</strong>
|
||||
</td>
|
||||
<td class="{{#classes}}{{.}}{{/classes}} prop-value">
|
||||
{{value}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/show}}
|
||||
{{/userProps}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{#showUpgradeProButton}}
|
||||
<p class="block">
|
||||
<a href="{{baseUrl}}/upgrade" class="upgrade-button">Upgrade to a Pro account</a> to benefit from collaborate on notebooks, to increase the max note size, or the max total size.
|
||||
</p>
|
||||
{{/showUpgradeProButton}}
|
38
packages/server/src/views/index/upgrade.mustache
Normal file
38
packages/server/src/views/index/upgrade.mustache
Normal file
@ -0,0 +1,38 @@
|
||||
<h1 class="title">Upgrade your account</h1>
|
||||
<p class="subtitle">Upgrading to a Pro account to get the following benefits.</p>
|
||||
|
||||
<form id="upgrade_form" action="{{{postUrl}}}" method="POST">
|
||||
<table class="table is-hoverable user-props-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="basic-plan">Basic - {{basicPrice}}</th>
|
||||
<th>Pro - {{proPrice}}</th>
|
||||
</tr>
|
||||
|
||||
{{#planRows}}
|
||||
<tr>
|
||||
<td class="basic-plan">
|
||||
{{basicLabel}}
|
||||
</td>
|
||||
<td>
|
||||
{{proLabel}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/planRows}}
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><input type="submit" id="upgrade_button" name="upgrade_button" class="button is-success" value="Upgrade to Pro" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(() => {
|
||||
$('#upgrade_form').submit((event) => {
|
||||
const ok = confirm('Your account is going to be upgraded to Pro. Do you wish to continue?');
|
||||
if (!ok) event.preventDefault();
|
||||
});
|
||||
});
|
||||
</script>
|
@ -1,9 +1,9 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import { insertContentIntoFile, rootDir } from '../tool-utils';
|
||||
import { getPlans } from './utils/plans';
|
||||
import { pressCarouselItems } from './utils/pressCarousel';
|
||||
import { getMarkdownIt, loadMustachePartials, markdownToPageHtml, renderMustache } from './utils/render';
|
||||
import { Env, PlanPageParams, Sponsors, StripePublicConfig, TemplateParams } from './utils/types';
|
||||
import { Env, PlanPageParams, Sponsors, TemplateParams } from './utils/types';
|
||||
import { getPlans, StripePublicConfig } from '@joplin/lib/utils/joplinCloud';
|
||||
const dirname = require('path').dirname;
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Plan, StripePublicConfig } from '@joplin/lib/utils/joplinCloud';
|
||||
|
||||
export enum Env {
|
||||
Dev = 'dev',
|
||||
Prod = 'prod',
|
||||
@ -64,28 +66,8 @@ export interface TemplateParams {
|
||||
buildTime?: number;
|
||||
}
|
||||
|
||||
export interface Plan {
|
||||
name: string;
|
||||
title: string;
|
||||
price: string;
|
||||
stripePriceId: string;
|
||||
featured: boolean;
|
||||
iconName: string;
|
||||
featuresOn: string[];
|
||||
featuresOff: string[];
|
||||
cfaLabel: string;
|
||||
cfaUrl: string;
|
||||
}
|
||||
|
||||
export interface PlanPageParams extends TemplateParams {
|
||||
plans: Record<string, Plan>;
|
||||
faqHtml: string;
|
||||
stripeConfig: StripePublicConfig;
|
||||
}
|
||||
|
||||
export interface StripePublicConfig {
|
||||
publishableKey: string;
|
||||
basicPriceId: string;
|
||||
proPriceId: string;
|
||||
webhookBaseUrl: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user