mirror of
https://github.com/laurent22/joplin.git
synced 2026-06-18 20:16:34 +02:00
Chore: Doc: Add (currently disabled) website/checkout logic for a self-hosted plan (#15675)
This commit is contained in:
@@ -36,7 +36,18 @@
|
||||
{{#featureLabelsOff}}
|
||||
<p class="unchecked-text"><i class="fas fa-times feature feature-off"></i>{{label}}</p>
|
||||
{{/featureLabelsOff}}
|
||||
|
||||
|
||||
{{#pricingTable}}
|
||||
<strong translate>Pricing</strong>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{{#rows}}
|
||||
<tr><td>{{condition}}</td><td>{{priceYearly}}</td></tr>
|
||||
{{/rows}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{/pricingTable}}
|
||||
|
||||
<p class="text-center subscribe-wrapper">
|
||||
<a id="subscribeButton-{{name}}" href="{{cfaUrl}}" class="button-link btn-white subscribeButton cfa-button">{{cfaLabel}}</a>
|
||||
|
||||
@@ -58,29 +69,27 @@
|
||||
const buttonId = 'subscribeButton-' + planName;
|
||||
const buttonElement = document.getElementById(buttonId);
|
||||
|
||||
if (stripePricesIds.monthly) {
|
||||
function handleResult() {
|
||||
console.info('Redirected to checkout');
|
||||
function handleResult() {
|
||||
console.info('Redirected to checkout');
|
||||
}
|
||||
|
||||
buttonElement.addEventListener("click", function(evt) {
|
||||
const priceId = stripePricesIds[subscriptionPeriod];
|
||||
|
||||
if (!priceId) {
|
||||
console.error('Invalid period: ' + subscriptionPeriod);
|
||||
return;
|
||||
}
|
||||
|
||||
buttonElement.addEventListener("click", function(evt) {
|
||||
evt.preventDefault();
|
||||
evt.preventDefault();
|
||||
|
||||
const priceId = stripePricesIds[subscriptionPeriod];
|
||||
|
||||
if (!priceId) {
|
||||
console.error('Invalid period: ' + subscriptionPeriod);
|
||||
return;
|
||||
}
|
||||
|
||||
createCheckoutSession(priceId).then(function(data) {
|
||||
stripe.redirectToCheckout({
|
||||
sessionId: data.sessionId
|
||||
})
|
||||
.then(handleResult);
|
||||
});
|
||||
createCheckoutSession(priceId).then(function(data) {
|
||||
stripe.redirectToCheckout({
|
||||
sessionId: data.sessionId
|
||||
})
|
||||
.then(handleResult);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (const button of document.querySelectorAll('button.action-toggleIncreaseStorage')) {
|
||||
button.onclick = () => {
|
||||
|
||||
@@ -250,6 +250,8 @@
|
||||
|
||||
$('.toggle-button-self').click((event) => {
|
||||
event.preventDefault();
|
||||
// Self-hosting is currently yearly-only
|
||||
applyPeriod('yearly');
|
||||
setHostingType('self');
|
||||
});
|
||||
|
||||
|
||||
@@ -38,11 +38,17 @@ enum PlanHostingType {
|
||||
Self = 'self',
|
||||
}
|
||||
|
||||
export interface PlanTieredPricingTableRow {
|
||||
condition: string;
|
||||
priceYearly: string;
|
||||
}
|
||||
|
||||
export interface Plan {
|
||||
name: string;
|
||||
title: string;
|
||||
priceMonthly?: StripePublicConfigPrice;
|
||||
priceYearly?: StripePublicConfigPrice;
|
||||
pricingTable?: { rows: PlanTieredPricingTableRow[] };
|
||||
featured: boolean;
|
||||
iconName: string;
|
||||
featuresOn: FeatureId[];
|
||||
@@ -67,16 +73,39 @@ export enum PriceCurrency {
|
||||
USD = 'USD',
|
||||
}
|
||||
|
||||
export interface StripePublicConfigPrice {
|
||||
export interface StripePublicConfigTieredAmount {
|
||||
amount: string;
|
||||
formattedAmount: string;
|
||||
users: [number, number|'infinity'];
|
||||
userRange: { min: number; max: number };
|
||||
}
|
||||
|
||||
export interface StripePublicConfigBasePrice {
|
||||
accountType: number; // AccountType
|
||||
id: string;
|
||||
period: PricePeriod;
|
||||
currency: PriceCurrency;
|
||||
}
|
||||
|
||||
export interface StripePublicConfigFixedPrice extends StripePublicConfigBasePrice {
|
||||
amount: string;
|
||||
formattedAmount: string;
|
||||
formattedMonthlyAmount: string;
|
||||
currency: PriceCurrency;
|
||||
|
||||
amounts: undefined;
|
||||
}
|
||||
|
||||
export interface StripePublicConfigTieredPrice extends StripePublicConfigBasePrice {
|
||||
amounts: StripePublicConfigTieredAmount[];
|
||||
|
||||
quantityMinimum: number;
|
||||
amount: undefined;
|
||||
formattedAmount: undefined;
|
||||
formattedMonthlyAmount: undefined;
|
||||
}
|
||||
|
||||
export type StripePublicConfigPrice = StripePublicConfigFixedPrice | StripePublicConfigTieredPrice;
|
||||
|
||||
export interface StripePublicConfig {
|
||||
publishableKey: string;
|
||||
prices: StripePublicConfigPrice[];
|
||||
@@ -92,17 +121,28 @@ function formatPrice(amount: string | number, currency: PriceCurrency): string {
|
||||
throw new Error(`Unsupported currency: ${currency}`);
|
||||
}
|
||||
|
||||
interface FindPriceQuery {
|
||||
accountType?: number;
|
||||
period?: PricePeriod;
|
||||
priceId?: string;
|
||||
}
|
||||
export const isTieredPrice = (p: StripePublicConfigPrice): p is StripePublicConfigTieredPrice => {
|
||||
return 'amounts' in p;
|
||||
};
|
||||
|
||||
export function loadStripeConfig(env: string, filePath: string): StripePublicConfig {
|
||||
const config: StripePublicConfig = JSON.parse(fs.readFileSync(filePath, 'utf8'))[env];
|
||||
if (!config) throw new Error(`Invalid env: ${env}`);
|
||||
|
||||
const decoratePrices = (p: StripePublicConfigPrice) => {
|
||||
const decoratePrices = (p: StripePublicConfigPrice): StripePublicConfigPrice => {
|
||||
if (isTieredPrice(p)) {
|
||||
return {
|
||||
...p,
|
||||
amounts: p.amounts.map(amount => ({
|
||||
...amount,
|
||||
formattedAmount: formatPrice(amount.amount, p.currency),
|
||||
userRange: {
|
||||
min: amount.users[0],
|
||||
max: amount.users[1] === 'infinity' ? Number.POSITIVE_INFINITY : amount.users[1],
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
return {
|
||||
...p,
|
||||
formattedAmount: formatPrice(p.amount, p.currency),
|
||||
@@ -116,6 +156,12 @@ export function loadStripeConfig(env: string, filePath: string): StripePublicCon
|
||||
return config;
|
||||
}
|
||||
|
||||
interface FindPriceQuery {
|
||||
accountType?: number;
|
||||
period?: PricePeriod;
|
||||
priceId?: string;
|
||||
}
|
||||
|
||||
export function findPrice(config: StripePublicConfig, query: FindPriceQuery): StripePublicConfigPrice {
|
||||
let output: StripePublicConfigPrice = null;
|
||||
|
||||
@@ -455,7 +501,32 @@ export const createFeatureTableMd = () => {
|
||||
return markdownUtils.createMarkdownTable(headers, rows);
|
||||
};
|
||||
|
||||
const getTieredPricingTable = (price: StripePublicConfigPrice) => {
|
||||
if (!isTieredPrice(price)) throw new Error(`Not a tiered price: ${price.id}`);
|
||||
|
||||
const rows: PlanTieredPricingTableRow[] = [];
|
||||
for (const amount of price.amounts) {
|
||||
const formatUserCount = (count: number) => {
|
||||
if (count === Number.POSITIVE_INFINITY) {
|
||||
return '∞';
|
||||
}
|
||||
return String(count);
|
||||
};
|
||||
rows.push({
|
||||
condition: `${
|
||||
formatUserCount(amount.userRange.min)
|
||||
}—${
|
||||
formatUserCount(amount.userRange.max)
|
||||
} users`,
|
||||
priceYearly: `${amount.formattedAmount} / user / year`,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
|
||||
export function getPlans(stripeConfig: StripePublicConfig): Record<PlanName, Plan> {
|
||||
// TODO: Set to true to enable self-hosting self-service.
|
||||
const selfServiceSelfHostingEnabled = false;
|
||||
return {
|
||||
basic: {
|
||||
name: 'basic',
|
||||
@@ -560,8 +631,23 @@ export function getPlans(stripeConfig: StripePublicConfig): Record<PlanName, Pla
|
||||
featuresOff: [],
|
||||
featureLabelsOn: getFeatureLabelsByPlan(PlanName.JoplinServerBusiness, true),
|
||||
featureLabelsOff: [],
|
||||
cfaLabel: _('Get a quote'),
|
||||
cfaUrl: 'https://tally.so/r/D4BlOE',
|
||||
...(selfServiceSelfHostingEnabled ? {
|
||||
pricingTable: {
|
||||
rows: getTieredPricingTable(findPrice(stripeConfig, {
|
||||
accountType: 5,
|
||||
period: PricePeriod.Yearly,
|
||||
})),
|
||||
},
|
||||
cfaLabel: _('Try it now'),
|
||||
cfaUrl: '',
|
||||
priceYearly: findPrice(stripeConfig, {
|
||||
accountType: 5,
|
||||
period: PricePeriod.Yearly,
|
||||
}),
|
||||
} : {
|
||||
cfaLabel: _('Get a quote'),
|
||||
cfaUrl: 'https://tally.so/r/D4BlOE',
|
||||
}),
|
||||
footnote: '',
|
||||
learnMoreUrl: 'https://joplinapp.org/help/apps/joplin_server_business',
|
||||
hostingType: PlanHostingType.Self,
|
||||
|
||||
@@ -58,6 +58,27 @@
|
||||
"period": "yearly",
|
||||
"amount": "95.88",
|
||||
"currency": "EUR"
|
||||
},
|
||||
{
|
||||
"accountType": 5,
|
||||
"id": "price_1ThCwVL9ZkKzC9sXU9AVyDB1",
|
||||
"period": "yearly",
|
||||
"quantityMinimum": 2,
|
||||
"amounts": [
|
||||
{
|
||||
"amount": "40.00",
|
||||
"users": [2, 10]
|
||||
},
|
||||
{
|
||||
"amount": "30.00",
|
||||
"users": [11, 50]
|
||||
},
|
||||
{
|
||||
"amount": "20.00",
|
||||
"users": [51, "infinity"]
|
||||
}
|
||||
],
|
||||
"currency": "EUR"
|
||||
}
|
||||
],
|
||||
"archivedPrices": []
|
||||
@@ -121,6 +142,27 @@
|
||||
"period": "yearly",
|
||||
"amount": "95.88",
|
||||
"currency": "EUR"
|
||||
},
|
||||
{
|
||||
"accountType": 5,
|
||||
"id": "price_1TgZ0zLx4fybOTqJl0xF4kHr",
|
||||
"period": "yearly",
|
||||
"quantityMinimum": 2,
|
||||
"amounts": [
|
||||
{
|
||||
"amount": "40.00",
|
||||
"users": [2, 10]
|
||||
},
|
||||
{
|
||||
"amount": "30.00",
|
||||
"users": [11, 50]
|
||||
},
|
||||
{
|
||||
"amount": "20.00",
|
||||
"users": [51, "infinity"]
|
||||
}
|
||||
],
|
||||
"currency": "EUR"
|
||||
}
|
||||
],
|
||||
"archivedPrices": [
|
||||
|
||||
Reference in New Issue
Block a user