mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Customizable stripe plans.
Summary: - Reading plans from Stripe, and allowing Stripe to define custom plans. - Storing product features (aka limits) in Stripe, that override those in db. - Adding hierarchical data in Stripe. All features are defined at Product level but can be overwritten on Price levels. - New options for Support user to -- Override product for team site (if he is added as a billing manager) -- Override subscription and customer id for a team site -- Attach an "offer", an custom plan configured in stripe that a team site can use -- Enabling wire transfer for subscription by allowing subscription to be created without a payment method (which is customizable) Test Plan: Updated and new. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D4201
This commit is contained in:
@@ -139,7 +139,7 @@ export class AccountWidget extends Disposable {
|
||||
|
||||
// Show 'Organization Settings' when on a home page of a valid org.
|
||||
(!this._docPageModel && currentOrg && this._appModel.isTeamSite ?
|
||||
menuItem(() => manageTeamUsers(currentOrg, user, this._appModel.api),
|
||||
menuItem(() => manageTeamUsers({org: currentOrg, user, api: this._appModel.api}),
|
||||
roles.canEditAccess(currentOrg.access) ? t("Manage Team") : t("Access Details"),
|
||||
testId('dm-org-access')) :
|
||||
// Don't show on doc pages, or for personal orgs.
|
||||
|
||||
@@ -117,7 +117,7 @@ export class AppHeader extends Disposable {
|
||||
|
||||
// Show 'Organization Settings' when on a home page of a valid org.
|
||||
(!this._docPageModel && this._currentOrg && !this._currentOrg.owner ?
|
||||
menuItem(() => manageTeamUsersApp(this._appModel),
|
||||
menuItem(() => manageTeamUsersApp({app: this._appModel}),
|
||||
'Manage Team', testId('orgmenu-manage-team'),
|
||||
dom.cls('disabled', !roles.canEditAccess(this._currentOrg.access))) :
|
||||
// Don't show on doc pages, or for personal orgs.
|
||||
|
||||
@@ -10,6 +10,7 @@ import {IModalControl, modal} from 'app/client/ui2018/modals';
|
||||
import {TEAM_PLAN} from 'app/common/Features';
|
||||
import {checkSubdomainValidity} from 'app/common/orgNameUtils';
|
||||
import {UserAPIImpl} from 'app/common/UserAPI';
|
||||
import {PlanSelection} from 'app/common/BillingAPI';
|
||||
import {
|
||||
Disposable, dom, DomArg, DomContents, DomElementArg, IDisposableOwner, input, makeTestId,
|
||||
Observable, styled
|
||||
@@ -19,9 +20,9 @@ import { makeT } from '../lib/localization';
|
||||
const t = makeT('CreateTeamModal');
|
||||
const testId = makeTestId('test-create-team-');
|
||||
|
||||
export function buildNewSiteModal(context: Disposable, options: {
|
||||
planName: string,
|
||||
selectedPlan?: string,
|
||||
export async function buildNewSiteModal(context: Disposable, options: {
|
||||
appModel: AppModel,
|
||||
plan?: PlanSelection,
|
||||
onCreate?: () => void
|
||||
}) {
|
||||
const { onCreate } = options;
|
||||
@@ -78,7 +79,11 @@ class NewSiteModalContent extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export function buildUpgradeModal(owner: Disposable, planName: string): void {
|
||||
export function buildUpgradeModal(owner: Disposable, options: {
|
||||
appModel: AppModel,
|
||||
pickPlan?: PlanSelection,
|
||||
reason?: 'upgrade' | 'renew',
|
||||
}): Promise<void> {
|
||||
throw new UserError(t(`Billing is not supported in grist-core`));
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) {
|
||||
css.docMenu(
|
||||
attachAddNewTip(home),
|
||||
|
||||
dom.maybe(!home.app.currentFeatures.workspaces, () => [
|
||||
dom.maybe(!home.app.currentFeatures?.workspaces, () => [
|
||||
css.docListHeader(t("This service is not available right now")),
|
||||
dom('span', t("(The organization needs a paid plan)")),
|
||||
]),
|
||||
|
||||
@@ -168,7 +168,7 @@ function buildButtons(homeModel: HomeModel, options: {
|
||||
!options.invite ? null :
|
||||
cssBtn(cssBtnIcon('Help'), t("Invite Team Members"), testId('intro-invite'),
|
||||
cssButton.cls('-primary'),
|
||||
dom.on('click', () => manageTeamUsersApp(homeModel.app)),
|
||||
dom.on('click', () => manageTeamUsersApp({app: homeModel.app})),
|
||||
),
|
||||
!options.templates ? null :
|
||||
cssBtn(cssBtnIcon('FieldTable'), t("Browse Templates"), testId('intro-templates'),
|
||||
|
||||
@@ -196,7 +196,7 @@ export async function importFromPluginAndOpen(home: HomeModel, source: ImportSou
|
||||
function addMenu(home: HomeModel, creating: Observable<boolean>): DomElementArg[] {
|
||||
const org = home.app.currentOrg;
|
||||
const orgAccess: roles.Role|null = org ? org.access : null;
|
||||
const needUpgrade = home.app.currentFeatures.maxWorkspacesPerOrg === 1;
|
||||
const needUpgrade = home.app.currentFeatures?.maxWorkspacesPerOrg === 1;
|
||||
|
||||
return [
|
||||
menuItem(() => createDocAndOpen(home), menuIcon('Page'), t("Create Empty Document"),
|
||||
@@ -261,7 +261,7 @@ function workspaceMenu(home: HomeModel, ws: Workspace, renaming: Observable<Work
|
||||
});
|
||||
}
|
||||
|
||||
const needUpgrade = home.app.currentFeatures.maxWorkspacesPerOrg === 1;
|
||||
const needUpgrade = home.app.currentFeatures?.maxWorkspacesPerOrg === 1;
|
||||
|
||||
return [
|
||||
upgradableMenuItem(needUpgrade, () => renaming.set(ws), t("Rename"),
|
||||
|
||||
@@ -140,8 +140,8 @@ class SaveCopyModal extends Disposable {
|
||||
}
|
||||
// We won't have info about any other org except the one we are at.
|
||||
if (org.id === this._app.currentOrg?.id) {
|
||||
const workspaces = this._app.currentOrg.billingAccount?.product.features.workspaces ?? true;
|
||||
const numberAllowed = this._app.currentOrg.billingAccount?.product.features.maxWorkspacesPerOrg ?? 2;
|
||||
const workspaces = this._app.currentFeatures?.workspaces ?? true;
|
||||
const numberAllowed = this._app.currentFeatures?.maxWorkspacesPerOrg ?? 2;
|
||||
return workspaces && numberAllowed > 1;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -2,20 +2,33 @@ import {loadUserManager} from 'app/client/lib/imports';
|
||||
import {AppModel} from 'app/client/models/AppModel';
|
||||
import {FullUser, Organization, UserAPI} from 'app/common/UserAPI';
|
||||
|
||||
export interface ManageTeamUsersOptions {
|
||||
org: Organization;
|
||||
user: FullUser | null;
|
||||
api: UserAPI;
|
||||
onSave?: (personal: boolean) => Promise<unknown>;
|
||||
}
|
||||
|
||||
// Opens the user-manager for the given org.
|
||||
export async function manageTeamUsers(org: Organization, user: FullUser|null, api: UserAPI) {
|
||||
export async function manageTeamUsers({org, user, api, onSave}: ManageTeamUsersOptions) {
|
||||
(await loadUserManager()).showUserManagerModal(api, {
|
||||
permissionData: api.getOrgAccess(org.id),
|
||||
activeUser: user,
|
||||
resourceType: 'organization',
|
||||
resourceId: org.id,
|
||||
resource: org,
|
||||
onSave
|
||||
});
|
||||
}
|
||||
|
||||
export interface ManagePersonalUsersAppOptions {
|
||||
app: AppModel;
|
||||
onSave?: (personal: boolean) => Promise<unknown>;
|
||||
}
|
||||
|
||||
// Opens the user-manager for the current org in the given AppModel.
|
||||
export async function manageTeamUsersApp(app: AppModel) {
|
||||
export async function manageTeamUsersApp({app, onSave}: ManagePersonalUsersAppOptions) {
|
||||
if (app.currentOrg) {
|
||||
return manageTeamUsers(app.currentOrg, app.currentValidUser, app.api);
|
||||
return manageTeamUsers({org: app.currentOrg, user: app.currentValidUser, api: app.api, onSave});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import {Computed, dom, DomElementArg, makeTestId, MultiHolder, Observable, style
|
||||
|
||||
const t = makeT('TopBar');
|
||||
|
||||
export function createTopBarHome(appModel: AppModel) {
|
||||
export function createTopBarHome(appModel: AppModel, onSave?: (personal: boolean) => Promise<unknown>){
|
||||
const isAnonymous = !appModel.currentValidUser;
|
||||
|
||||
return [
|
||||
@@ -32,7 +32,7 @@ export function createTopBarHome(appModel: AppModel) {
|
||||
[
|
||||
basicButton(
|
||||
t("Manage Team"),
|
||||
dom.on('click', () => manageTeamUsersApp(appModel)),
|
||||
dom.on('click', () => manageTeamUsersApp({app: appModel, onSave})),
|
||||
testId('topbar-manage-team')
|
||||
),
|
||||
cssSpacer()
|
||||
|
||||
Reference in New Issue
Block a user