From 40c9b8b7e8c7b54ee436e0cc443cd784cd057d7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?= Date: Wed, 3 Aug 2022 12:48:01 +0200 Subject: [PATCH] (core) New URL that opens Create site popup. Summary: Adding new url parameter for team site creation Test Plan: Updated tests. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3554 --- app/client/models/AppModel.ts | 10 +++++++++- app/client/ui/BillingPage.ts | 6 +++--- app/client/ui/ProductUpgradesStub.ts | 1 + app/common/gristUrls.ts | 6 ++++++ app/server/lib/FlexServer.ts | 20 ++++++++++++++++---- 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/app/client/models/AppModel.ts b/app/client/models/AppModel.ts index dfe58f69..c7e1c6f9 100644 --- a/app/client/models/AppModel.ts +++ b/app/client/models/AppModel.ts @@ -226,6 +226,13 @@ export class AppModelImpl extends Disposable implements AppModel { ) { super(); this._recordSignUpIfIsNewUser(); + + const state = urlState().state.get(); + if (state.createTeam) { + // Remove params from the URL. + urlState().pushUrl({createTeam: false, params: {}}, {avoidReload: true, replace: true}).catch(() => {}); + this.showNewSiteModal(state.params?.planType); + } } public get planName() { @@ -244,10 +251,11 @@ export class AppModelImpl extends Disposable implements AppModel { } } - public showNewSiteModal() { + public showNewSiteModal(selectedPlan?: string) { if (this.planName) { buildNewSiteModal(this, { planName: this.planName, + selectedPlan, onCreate: () => this.topAppModel.fetchUsersAndOrgs().catch(reportError) }); } diff --git a/app/client/ui/BillingPage.ts b/app/client/ui/BillingPage.ts index 4212106a..314814de 100644 --- a/app/client/ui/BillingPage.ts +++ b/app/client/ui/BillingPage.ts @@ -10,7 +10,7 @@ import {BillingPlanManagers} from 'app/client/ui/BillingPlanManagers'; import {createForbiddenPage} from 'app/client/ui/errorPages'; import {leftPanelBasic} from 'app/client/ui/LeftPanelCommon'; import {pagePanels} from 'app/client/ui/PagePanels'; -import {showTeamUpgradeConfirmation} from 'app/client/ui/ProductUpgrades'; +import {NEW_DEAL, showTeamUpgradeConfirmation} from 'app/client/ui/ProductUpgrades'; import {createTopBarHome} from 'app/client/ui/TopBar'; import {cssBreadcrumbs, cssBreadcrumbsLink, separator} from 'app/client/ui2018/breadcrumbs'; import {bigBasicButton, bigBasicButtonLink, bigPrimaryButton} from 'app/client/ui2018/buttons'; @@ -273,8 +273,8 @@ export class BillingPage extends Disposable { !canManage ? null : makeActionLink('Manage billing', 'Settings', this._model.getCustomerPortalUrl(), testId('portal-link')), !wasTeam ? null : - makeActionButton('Downgrade plan', 'Settings', - () => this._confirmDowngradeToTeamFree(), testId('downgrade-free-link')), + dom.maybe(NEW_DEAL(), () => makeActionButton('Downgrade plan', 'Settings', + () => this._confirmDowngradeToTeamFree(), testId('downgrade-free-link'))), !canRenew ? null : makeActionLink('Renew subscription', 'Settings', this._model.renewPlan(), testId('renew-link')), !canUpgrade ? null : diff --git a/app/client/ui/ProductUpgradesStub.ts b/app/client/ui/ProductUpgradesStub.ts index 07b05944..75f8b43a 100644 --- a/app/client/ui/ProductUpgradesStub.ts +++ b/app/client/ui/ProductUpgradesStub.ts @@ -4,6 +4,7 @@ import {Disposable, DomContents, IDisposableOwner, Observable, observable} from export function buildNewSiteModal(context: Disposable, options: { planName: string, + selectedPlan?: string, onCreate?: () => void }) { window.location.href = commonUrls.plans; diff --git a/app/common/gristUrls.ts b/app/common/gristUrls.ts index 04dbe837..da779c2c 100644 --- a/app/common/gristUrls.ts +++ b/app/common/gristUrls.ts @@ -87,8 +87,10 @@ export interface IGristUrlState { welcomeTour?: boolean; docTour?: boolean; manageUsers?: boolean; + createTeam?: boolean; params?: { billingPlan?: string; + planType?: string; billingTask?: BillingTask; embed?: boolean; state?: string; @@ -258,6 +260,8 @@ export function encodeUrl(gristConfig: Partial, url.hash = 'repeat-doc-tour'; } else if (state.manageUsers) { url.hash = 'manage-users'; + } else if (state.createTeam) { + url.hash = 'create-team'; } else { url.hash = ''; } @@ -313,6 +317,7 @@ export function decodeUrl(gristConfig: Partial, location: Locat if (map.has('account')) { state.account = AccountPage.parse(map.get('account')) || 'account'; } if (map.has('billing')) { state.billing = BillingSubPage.parse(map.get('billing')) || 'billing'; } if (map.has('welcome')) { state.welcome = WelcomePage.parse(map.get('welcome')); } + if (sp.has('planType')) { state.params!.planType = sp.get('planType')!; } if (sp.has('billingPlan')) { state.params!.billingPlan = sp.get('billingPlan')!; } if (sp.has('billingTask')) { state.params!.billingTask = BillingTask.parse(sp.get('billingTask')); @@ -374,6 +379,7 @@ export function decodeUrl(gristConfig: Partial, location: Locat state.welcomeTour = hashMap.get('#') === 'repeat-welcome-tour'; state.docTour = hashMap.get('#') === 'repeat-doc-tour'; state.manageUsers = hashMap.get('#') === 'manage-users'; + state.createTeam = hashMap.get('#') === 'create-team'; } return state; } diff --git a/app/server/lib/FlexServer.ts b/app/server/lib/FlexServer.ts index e377b0ed..314f2cd4 100644 --- a/app/server/lib/FlexServer.ts +++ b/app/server/lib/FlexServer.ts @@ -4,6 +4,8 @@ import {encodeUrl, getSlugIfNeeded, GristLoadConfig, IGristUrlState, isOrgInPath parseSubdomain, sanitizePathTail} from 'app/common/gristUrls'; import {getOrgUrlInfo} from 'app/common/gristUrls'; import {UserProfile} from 'app/common/LoginSessionAPI'; +import {BillingTask} from 'app/common/BillingAPI'; +import {TEAM_FREE_PLAN, TEAM_PLAN} from 'app/common/Features'; import {tbind} from 'app/common/tbind'; import * as version from 'app/common/version'; import {ApiServer, getOrgFromRequest} from 'app/gen-server/ApiServer'; @@ -70,7 +72,6 @@ import {AddressInfo} from 'net'; import fetch from 'node-fetch'; import * as path from 'path'; import * as serveStatic from "serve-static"; -import {BillingTask} from 'app/common/BillingAPI'; // Health checks are a little noisy in the logs, so we don't show them all. // We show the first N health checks: @@ -1200,6 +1201,19 @@ export class FlexServer implements GristServer { const url = `${getPrefix(req)}/api/billing/signup?planType=${planType}&billingPlan=${billingPlan}`; return resp.redirect(url); })); + + // New landing page for the new NEW_DEAL. + this.app.get('/billing/create-team', ...middleware, expressWrap(async (req, resp, next) => { + const planType = optStringParam(req.query.planType) || ''; + // Currently we have hardcoded support only for those two plans. + const supportedPlans = [TEAM_PLAN, TEAM_FREE_PLAN]; + if (!supportedPlans.includes(planType)) { + return this._sendAppPage(req, resp, {path: 'error.html', status: 404, config: {errPage: 'not-found'}}); + } + // Redirect to home page with url params + const url = `${getPrefix(req)}?planType=${planType}#create-team`; + return resp.redirect(url); + })); } /** @@ -1238,9 +1252,7 @@ export class FlexServer implements GristServer { }); resp.status(200).send(); - }), - // Add a final error handler that reports errors as JSON. - jsonErrorHandler); + }), jsonErrorHandler); // Add a final error handler that reports errors as JSON. } public finalize() {