diff --git a/app/client/models/HomeModel.ts b/app/client/models/HomeModel.ts index 17dbf812..808e010d 100644 --- a/app/client/models/HomeModel.ts +++ b/app/client/models/HomeModel.ts @@ -150,10 +150,9 @@ export class HomeModelImpl extends Disposable implements HomeModel, ViewSettings 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) => ( - wss.every((ws) => ws.isSupportWorkspace || ws.docs.length === 0) && - Boolean(use(this.newDocWorkspace)))); + wss.every((ws) => ws.isSupportWorkspace || ws.docs.length === 0))); private _userOrgPrefs = Observable.create(this, this._app.currentOrg?.userOrgPrefs); diff --git a/app/client/ui/HomeIntro.ts b/app/client/ui/HomeIntro.ts index b4b44e8e..bc9fa0e2 100644 --- a/app/client/ui/HomeIntro.ts +++ b/app/client/ui/HomeIntro.ts @@ -11,30 +11,69 @@ import {cssLink} from 'app/client/ui2018/links'; import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls'; import {FullUser} from 'app/common/LoginSessionAPI'; 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 { + const isViewer = homeModel.app.currentOrg?.access === roles.VIEWER; const user = homeModel.app.currentValidUser; - if (user) { - return homeModel.app.isTeamSite ? makeTeamSiteIntro(homeModel) : makePersonalIntro(homeModel, user); - } else { + const isAnonym = !user; + const isPersonal = !homeModel.app.isTeamSite; + if (isAnonym) { 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) { const sproutsProgram = cssLink({href: commonUrls.sproutsProgram, target: '_blank'}, 'Sprouts Program'); return [ - css.docListHeader(`Welcome to ${homeModel.app.currentOrgName}`, + css.docListHeader( + `Welcome to ${homeModel.app.currentOrgName}`, 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.'), (shouldHideUiElement('helpCenter') ? null : - cssIntroLine('Learn more in our ', helpCenterLink(), ', or find an expert via our ', sproutsProgram, '.', - testId('welcome-text')) + cssIntroLine( + 'Learn more in our ', helpCenterLink(), ', or find an expert via our ', sproutsProgram, '.', + testId('welcome-text') + ) ), - makeCreateButtons(homeModel), + makeCreateButtons(homeModel) ]; } diff --git a/test/nbrowser/HomeIntro.ts b/test/nbrowser/HomeIntro.ts index 36e73612..981b910a 100644 --- a/test/nbrowser/HomeIntro.ts +++ b/test/nbrowser/HomeIntro.ts @@ -3,7 +3,7 @@ * 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 {server, setupTestSuite} from 'test/nbrowser/testUtils'; @@ -42,6 +42,42 @@ describe('HomeIntro', function() { 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() { it('should show welcome for logged-in user', async function() { // 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) { // Create doc from intro button 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 // can see the intro again. - const checkDocAndRestore = stackWrapFunc(async function(isLoggedIn: boolean, docChecker: () => Promise, + const checkDocAndRestore = async function(isLoggedIn: boolean, docChecker: () => Promise, stepsBackToDocMenu: number = 1) { await gu.waitForDocToLoad(); await gu.dismissWelcomeTourIfNeeded(); @@ -180,7 +223,7 @@ describe('HomeIntro', function() { await driver.wait(async () => !(await driver.find('.test-modal-dialog').isPresent()), 3000); } assert.equal(await driver.find('.test-dm-doc').isPresent(), false); - }); + }; async function testExamplesCollapsing() { assert.equal(await driver.find('.test-dm-pinned-doc-name').isDisplayed(), true);