(core) Polishing upgrade plan UI

Summary:
- Update nudge boxes content and collapsing on personal and free team site
- New confirmation after upgrading from a free team site
- Refactoring ProductUpgrade code, splitting plans / modals and nudges

Test Plan: Manual and updated tests

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3481
This commit is contained in:
Jarosław Sadziński
2022-06-29 12:19:20 +02:00
parent dd2eadc86e
commit aefe451bab
16 changed files with 203 additions and 76 deletions

View File

@@ -14,11 +14,12 @@ 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';
import { loadingSpinner } from 'app/client/ui2018/loaders';
import { NEW_DEAL, showTeamUpgradeConfirmation } from 'app/client/ui/ProductUpgrades';
import { IconName } from 'app/client/ui2018/IconList';
import { BillingTask, IBillingCoupon } from 'app/common/BillingAPI';
import { capitalize } from 'app/common/gutil';
import { Organization } from 'app/common/UserAPI';
import { Disposable, dom, DomArg, IAttrObj, makeTestId, Observable } from 'grainjs';
import { IconName } from '../ui2018/IconList';
const testId = makeTestId('test-bp-');
const billingTasksNames = {
@@ -26,6 +27,7 @@ const billingTasksNames = {
signUpLite: 'Complete Sign Up', // task for payment page
updateDomain: 'Update Name', // task for summary page
cancelPlan: 'Cancel plan', // this is not a task, but a sub page
upgraded: 'Account',
};
/**
@@ -41,6 +43,8 @@ export class BillingPage extends Disposable {
constructor(private _appModel: AppModel) {
super();
// TODO: remove once NEW_DEAL is there. Execute for side effect
void NEW_DEAL();
this._appModel.refreshOrgUsage().catch(reportError);
}
@@ -77,7 +81,7 @@ export class BillingPage extends Disposable {
* Builds the contentMain dom for the current billing page.
*/
private _buildCurrentPageDom() {
return css.billingWrapper(
const page = css.billingWrapper(
dom.domComputed(this._model.currentSubpage, (subpage) => {
if (!subpage) {
return this._buildSummaryPage();
@@ -86,6 +90,11 @@ export class BillingPage extends Disposable {
}
})
);
if (this._model.currentTask.get() === 'upgraded') {
urlState().pushUrl({params: {}}, { replace: true }).catch(() => {});
showTeamUpgradeConfirmation(this);
}
return page;
}
private _buildSummaryPage() {

View File

@@ -10,12 +10,12 @@ import {getTimeFromNow, HomeModel, makeLocalViewSettings, ViewSettings} from 'ap
import {getWorkspaceInfo, workspaceName} from 'app/client/models/WorkspaceInfo';
import * as css from 'app/client/ui/DocMenuCss';
import {buildHomeIntro} from 'app/client/ui/HomeIntro';
import {createVideoTourTextButton} from 'app/client/ui/OpenVideoTour';
import {buildUpgradeNudge} from 'app/client/ui/ProductUpgrades';
import {buildUpgradeButton} from 'app/client/ui/ProductUpgrades';
import {buildPinnedDoc, createPinnedDocs} from 'app/client/ui/PinnedDocs';
import {shadowScroll} from 'app/client/ui/shadowScroll';
import {transition} from 'app/client/ui/transitions';
import {showWelcomeQuestions} from 'app/client/ui/WelcomeQuestions';
import {createVideoTourTextButton} from 'app/client/ui/OpenVideoTour';
import {buttonSelect, cssButtonSelect} from 'app/client/ui2018/buttonSelect';
import {colors, isNarrowScreenObs} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
@@ -26,12 +26,12 @@ import {IHomePage} from 'app/common/gristUrls';
import {SortPref, ViewPref} from 'app/common/Prefs';
import * as roles from 'app/common/roles';
import {Document, Workspace} from 'app/common/UserAPI';
import {Computed, computed, dom, DomContents, makeTestId, Observable, observable} from 'grainjs';
import sortBy = require('lodash/sortBy');
import {computed, Computed, dom, DomArg, DomContents, IDisposableOwner,
makeTestId, observable, Observable} from 'grainjs';
import {buildTemplateDocs} from 'app/client/ui/TemplateDocs';
import {localStorageBoolObs} from 'app/client/lib/localStorageObs';
import {bigBasicButton} from 'app/client/ui2018/buttons';
import {getUserOrgPrefObs, getUserOrgPrefsObs} from 'app/client/models/UserPrefs';
import sortBy = require('lodash/sortBy');
const testId = makeTestId('test-dm-');
@@ -45,27 +45,14 @@ export function createDocMenu(home: HomeModel) {
return dom.domComputed(home.loading, loading => (
loading === 'slow' ? css.spinner(loadingSpinner()) :
loading ? null :
createLoadedDocMenu(home)
dom.create(createLoadedDocMenu, home)
));
}
function createUpgradeNudge(home: HomeModel) {
const isLoggedIn = !!home.app.currentValidUser;
const isOnFreePersonal = home.app.currentOrg?.billingAccount?.product?.name === 'starter';
const userOrgPrefs = getUserOrgPrefsObs(home.app);
const seenNudge = getUserOrgPrefObs(userOrgPrefs, 'seenFreeTeamUpgradeNudge');
return dom.maybe(use => isLoggedIn && isOnFreePersonal && !use(seenNudge),
() => buildUpgradeNudge({
onClose: () => seenNudge.set(true),
// On show prices, we will clear the nudge in database once there is some free team site created
// The better way is to read all workspaces that this person have and decide then - but this is done
// asynchronously - so we potentially can show this nudge to people that already have team site.
onUpgrade: () => home.app.showUpgradeModal()
}));
}
function createLoadedDocMenu(home: HomeModel) {
function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) {
const flashDocId = observable<string|null>(null);
const upgradeButton = buildUpgradeButton(owner, home.app);
return css.docList(
showWelcomeQuestions(home.app.userPrefsObs),
css.docMenu(
@@ -83,14 +70,16 @@ function createLoadedDocMenu(home: HomeModel) {
page === 'templates' ? makeLocalViewSettings(home, 'templates') :
workspace ? makeLocalViewSettings(home, workspace.id) :
home;
return [
// Hide the sort option only when showing intro.
((showIntro && page === 'all') ? null :
buildPrefs(viewSettings, {hideSort: showIntro})
((showIntro && page === 'all') ? css.prefSelectors(upgradeButton.showUpgradeButton()) :
// This is float:right element
buildPrefs(viewSettings, {hideSort: showIntro}, upgradeButton.showUpgradeButton())
),
// Build the pinned docs dom. Builds nothing if the selectedOrg is unloaded or
// Build the pinned docs dom. Builds nothing if the selectedOrg is unloaded.
// TODO: this is shown on all pages, but there is a hack in currentWSPinnedDocs that
// removes all pinned docs when on trash page.
dom.maybe((use) => use(home.currentWSPinnedDocs).length > 0, () => [
css.docListHeader(css.docHeaderIconDark('PinBig'), 'Pinned Documents'),
createPinnedDocs(home, home.currentWSPinnedDocs),
@@ -128,7 +117,8 @@ function createLoadedDocMenu(home: HomeModel) {
dom('div',
showIntro ? buildHomeIntro(home) : null,
buildAllDocsBlock(home, home.workspaces, showIntro, flashDocId, viewSettings),
dom.maybe(use => use(isNarrowScreenObs()), () => createUpgradeNudge(home)),
dom.maybe(use => use(isNarrowScreenObs()),
() => upgradeButton.showUpgradeCard()),
shouldShowTemplates(home, showIntro) ? buildAllDocsTemplates(home, viewSettings) : null,
) :
(page === 'trash') ?
@@ -155,7 +145,8 @@ function createLoadedDocMenu(home: HomeModel) {
}),
testId('doclist')
),
dom.maybe(use => !use(isNarrowScreenObs()) && use(home.currentPage) === 'all', () => createUpgradeNudge(home)),
dom.maybe(use => !use(isNarrowScreenObs()) && ['all', 'workspace'].includes(use(home.currentPage)),
() => upgradeButton.showUpgradeCard()),
);
}
@@ -309,7 +300,10 @@ function buildOtherSites(home: HomeModel) {
* If hideSort is true, will hide the sort dropdown: it has no effect on the list of examples, so
* best to hide when those are the only docs shown.
*/
function buildPrefs(viewSettings: ViewSettings, options: {hideSort: boolean}): DomContents {
function buildPrefs(
viewSettings: ViewSettings,
options: {hideSort: boolean},
...args: DomArg<HTMLElement>[]): DomContents {
return css.prefSelectors(
// The Sort selector.
options.hideSort ? null : dom.update(
@@ -330,6 +324,7 @@ function buildPrefs(viewSettings: ViewSettings, options: {hideSort: boolean}): D
cssButtonSelect.cls("-light"),
testId('view-mode')
),
...args
);
}

View File

@@ -1,15 +1,11 @@
import type {AppModel} from 'app/client/models/AppModel';
import {commonUrls} from 'app/common/gristUrls';
import {Disposable} from 'grainjs';
import {Disposable, DomContents, IDisposableOwner, Observable, observable} from 'grainjs';
export function buildUpgradeNudge(options: {
onClose: () => void;
onUpgrade: () => void
export function buildNewSiteModal(context: Disposable, options: {
planName: string,
onCreate?: () => void
}) {
return null;
}
export function buildNewSiteModal(owner: Disposable, current: string | null) {
window.location.href = commonUrls.plans;
}
@@ -17,11 +13,21 @@ export function buildUpgradeModal(owner: Disposable, planName: string) {
window.location.href = commonUrls.plans;
}
export class UpgradeButton extends Disposable {
constructor(appModel: AppModel) {
super();
}
public buildDom() {
return null;
}
export function showTeamUpgradeConfirmation(owner: Disposable) {
}
export interface UpgradeButton {
showUpgradeCard(): DomContents;
showUpgradeButton(): DomContents;
}
export function buildUpgradeButton(owner: IDisposableOwner, app: AppModel): UpgradeButton {
return {
showUpgradeCard : () => null,
showUpgradeButton : () => null,
};
}
export function NEW_DEAL(): Observable<boolean> {
return observable(false);
}