(core) Welcome intro for viewers on a team site.

Summary:
Adding intro for a viewer on a teamsite.
Showing upgrade button for owners only.

Test Plan: new test

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3557
This commit is contained in:
Jarosław Sadziński 2022-08-03 12:35:45 +02:00
parent b7686fa664
commit c359547f6b
3 changed files with 96 additions and 15 deletions

View File

@ -150,10 +150,9 @@ export class HomeModelImpl extends Disposable implements HomeModel, ViewSettings
return destWS && roles.canEdit(destWS.access) ? destWS : null; return destWS && roles.canEdit(destWS.access) ? destWS : null;
}); });
// Whether to show intro: no docs (other than examples) and user may create docs. // Whether to show intro: no docs (other than examples).
public readonly showIntro = Computed.create(this, this.workspaces, (use, wss) => ( public readonly showIntro = Computed.create(this, this.workspaces, (use, wss) => (
wss.every((ws) => ws.isSupportWorkspace || ws.docs.length === 0) && wss.every((ws) => ws.isSupportWorkspace || ws.docs.length === 0)));
Boolean(use(this.newDocWorkspace))));
private _userOrgPrefs = Observable.create<UserOrgPrefs|undefined>(this, this._app.currentOrg?.userOrgPrefs); private _userOrgPrefs = Observable.create<UserOrgPrefs|undefined>(this, this._app.currentOrg?.userOrgPrefs);

View File

@ -11,30 +11,69 @@ import {cssLink} from 'app/client/ui2018/links';
import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls'; import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls';
import {FullUser} from 'app/common/LoginSessionAPI'; import {FullUser} from 'app/common/LoginSessionAPI';
import * as roles from 'app/common/roles'; import * as roles from 'app/common/roles';
import {dom, DomContents, styled} from 'grainjs'; import {Computed, dom, DomContents, styled} from 'grainjs';
export function buildHomeIntro(homeModel: HomeModel): DomContents { export function buildHomeIntro(homeModel: HomeModel): DomContents {
const isViewer = homeModel.app.currentOrg?.access === roles.VIEWER;
const user = homeModel.app.currentValidUser; const user = homeModel.app.currentValidUser;
if (user) { const isAnonym = !user;
return homeModel.app.isTeamSite ? makeTeamSiteIntro(homeModel) : makePersonalIntro(homeModel, user); const isPersonal = !homeModel.app.isTeamSite;
} else { if (isAnonym) {
return makeAnonIntro(homeModel); return makeAnonIntro(homeModel);
} else if (isPersonal) {
return makePersonalIntro(homeModel, user);
} else { // isTeamSite
if (isViewer) {
return makeViewerTeamSiteIntro(homeModel);
} else {
return makeTeamSiteIntro(homeModel);
} }
}
}
function makeViewerTeamSiteIntro(homeModel: HomeModel) {
const personalOrg = Computed.create(null, use => use(homeModel.app.topAppModel.orgs).find(o => o.owner));
const docLink = (dom.maybe(personalOrg, org => {
return cssLink(
urlState().setLinkUrl({org: org.domain ?? undefined}),
'free, personal site',
testId('welcome-personal-url'));
}));
return [
css.docListHeader(
dom.autoDispose(personalOrg),
`Welcome to ${homeModel.app.currentOrgName}`,
productPill(homeModel.app.currentOrg, {large: true}),
testId('welcome-title')
),
cssIntroLine(
testId('welcome-info'),
"You have read-only access to this site. Currently there are no documents.", dom('br'),
"Any documents created in this site will appear here."),
cssIntroLine(
'Interested in using Grist outside of your team? Visit your ', docLink, '.',
testId('welcome-text')
)
];
} }
function makeTeamSiteIntro(homeModel: HomeModel) { function makeTeamSiteIntro(homeModel: HomeModel) {
const sproutsProgram = cssLink({href: commonUrls.sproutsProgram, target: '_blank'}, 'Sprouts Program'); const sproutsProgram = cssLink({href: commonUrls.sproutsProgram, target: '_blank'}, 'Sprouts Program');
return [ return [
css.docListHeader(`Welcome to ${homeModel.app.currentOrgName}`, css.docListHeader(
`Welcome to ${homeModel.app.currentOrgName}`,
productPill(homeModel.app.currentOrg, {large: true}), productPill(homeModel.app.currentOrg, {large: true}),
testId('welcome-title')), testId('welcome-title')
),
cssIntroLine('Get started by inviting your team and creating your first Grist document.'), cssIntroLine('Get started by inviting your team and creating your first Grist document.'),
(shouldHideUiElement('helpCenter') ? null : (shouldHideUiElement('helpCenter') ? null :
cssIntroLine('Learn more in our ', helpCenterLink(), ', or find an expert via our ', sproutsProgram, '.', cssIntroLine(
testId('welcome-text')) 'Learn more in our ', helpCenterLink(), ', or find an expert via our ', sproutsProgram, '.',
testId('welcome-text')
)
), ),
makeCreateButtons(homeModel), makeCreateButtons(homeModel)
]; ];
} }

View File

@ -3,7 +3,7 @@
* page, both for anonymous and logged-in users. * page, both for anonymous and logged-in users.
*/ */
import {assert, driver, stackWrapFunc, WebElement} from 'mocha-webdriver'; import {assert, driver, Key, stackWrapFunc, WebElement} from 'mocha-webdriver';
import * as gu from 'test/nbrowser/gristUtils'; import * as gu from 'test/nbrowser/gristUtils';
import {server, setupTestSuite} from 'test/nbrowser/testUtils'; import {server, setupTestSuite} from 'test/nbrowser/testUtils';
@ -42,6 +42,42 @@ describe('HomeIntro', function() {
it('should render selected Examples workspace specially', testSelectedExamplesPage); it('should render selected Examples workspace specially', testSelectedExamplesPage);
}); });
describe("Viewer on a team site", function() {
it('should show welcome for viewers', async function() {
// Sign in as to a team that has no docs.
await server.simulateLogin("Chimpy", "chimpy@getgrist.com", "FreeTeam");
await driver.get(server.getUrl('freeteam', ''));
await gu.editOrgAcls();
const orgInput = await driver.find('.test-um-member-new input');
await orgInput.sendKeys('charon@getgrist.com', Key.ENTER);
await gu.saveAcls();
await gu.removeLogin();
await server.simulateLogin("Charon", "charon@getgrist.com", "abyss");
await driver.get(server.getUrl('freeteam', ''));
// Check message specific to logged-in user and an empty team site.
assert.match(await driver.findWait('.test-welcome-title', 1000).getText(), new RegExp(`Welcome.* FreeTeam`));
assert.match(await driver.find('.test-welcome-info').getText(),
/You have read-only access to this site.*/);
assert.match(await driver.find('.test-welcome-text').getText(),
/Interested in using Grist outside of your team\? Visit your free, personal site\./);
assert.notMatch(await driver.find('.test-welcome-text').getText(), /sign up/);
await driver.find(".test-welcome-personal-url").click();
await gu.waitForDocMenuToLoad();
assert.equal(
await driver.find('.test-dm-other-sites-message').getText(),
'You are on your personal site. You also have access to the following sites:'
);
await driver.get(server.getUrl('freeteam', ''));
await gu.waitForDocMenuToLoad();
});
it('should not show Other Sites section', testOtherSitesSection);
it('should not show welcome buttons', testNoButtonsOnHome);
it('should show examples workspace with the intro', testExamplesSection);
it('should render selected Examples workspace specially', testSelectedExamplesPage);
});
describe("Logged-in on merged-org", function() { describe("Logged-in on merged-org", function() {
it('should show welcome for logged-in user', async function() { it('should show welcome for logged-in user', async function() {
// Sign in as a new user who has no docs. // Sign in as a new user who has no docs.
@ -117,6 +153,13 @@ describe('HomeIntro', function() {
} }
} }
async function testNoButtonsOnHome() {
const buttons = ['test-intro-templates', 'test-intro-import-doc', 'test-intro-create-doc'];
for (const button of buttons) {
assert.isFalse(await driver.find(`.${button}`).isPresent());
}
}
async function testCreateImport(isLoggedIn: boolean) { async function testCreateImport(isLoggedIn: boolean) {
// Create doc from intro button // Create doc from intro button
await driver.find('.test-intro-create-doc').click(); await driver.find('.test-intro-create-doc').click();
@ -160,7 +203,7 @@ describe('HomeIntro', function() {
// Wait for doc to load, check it, then return to home page, and remove the doc so that we // Wait for doc to load, check it, then return to home page, and remove the doc so that we
// can see the intro again. // can see the intro again.
const checkDocAndRestore = stackWrapFunc(async function(isLoggedIn: boolean, docChecker: () => Promise<void>, const checkDocAndRestore = async function(isLoggedIn: boolean, docChecker: () => Promise<void>,
stepsBackToDocMenu: number = 1) { stepsBackToDocMenu: number = 1) {
await gu.waitForDocToLoad(); await gu.waitForDocToLoad();
await gu.dismissWelcomeTourIfNeeded(); await gu.dismissWelcomeTourIfNeeded();
@ -180,7 +223,7 @@ describe('HomeIntro', function() {
await driver.wait(async () => !(await driver.find('.test-modal-dialog').isPresent()), 3000); await driver.wait(async () => !(await driver.find('.test-modal-dialog').isPresent()), 3000);
} }
assert.equal(await driver.find('.test-dm-doc').isPresent(), false); assert.equal(await driver.find('.test-dm-doc').isPresent(), false);
}); };
async function testExamplesCollapsing() { async function testExamplesCollapsing() {
assert.equal(await driver.find('.test-dm-pinned-doc-name').isDisplayed(), true); assert.equal(await driver.find('.test-dm-pinned-doc-name').isDisplayed(), true);