gristlabs_grist-core/app/client/ui/SupportGristPage.ts

290 lines
8.3 KiB
TypeScript
Raw Normal View History

import {buildHomeBanners} from 'app/client/components/Banners';
import {makeT} from 'app/client/lib/localization';
import {AppModel} from 'app/client/models/AppModel';
import {urlState} from 'app/client/models/gristUrlState';
import {TelemetryModel, TelemetryModelImpl} from 'app/client/models/TelemetryModel';
import {AppHeader} from 'app/client/ui/AppHeader';
import {leftPanelBasic} from 'app/client/ui/LeftPanelCommon';
import {pagePanels} from 'app/client/ui/PagePanels';
import {createTopBarHome} from 'app/client/ui/TopBar';
import {cssBreadcrumbs, separator} from 'app/client/ui2018/breadcrumbs';
import {bigBasicButton, bigBasicButtonLink, bigPrimaryButton} from 'app/client/ui2018/buttons';
import {mediaSmall, theme, vars} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {cssLink} from 'app/client/ui2018/links';
import {loadingSpinner} from 'app/client/ui2018/loaders';
import {commonUrls} from 'app/common/gristUrls';
import {TelemetryPrefsWithSources} from 'app/common/InstallAPI';
import {getGristConfig} from 'app/common/urlUtils';
import {Computed, Disposable, dom, makeTestId, Observable, styled} from 'grainjs';
const testId = makeTestId('test-support-grist-page-');
const t = makeT('SupportGristPage');
export class SupportGristPage extends Disposable {
private readonly _model: TelemetryModel = new TelemetryModelImpl(this._appModel);
private readonly _optInToTelemetry = Computed.create(this, this._model.prefs,
(_use, prefs) => {
if (!prefs) { return null; }
return prefs.telemetryLevel.value !== 'off';
})
.onWrite(async (optIn) => {
const telemetryLevel = optIn ? 'limited' : 'off';
await this._model.updateTelemetryPrefs({telemetryLevel});
});
constructor(private _appModel: AppModel) {
super();
this._model.fetchTelemetryPrefs().catch(reportError);
}
public buildDom() {
const panelOpen = Observable.create(this, false);
return pagePanels({
leftPanel: {
panelWidth: Observable.create(this, 240),
panelOpen,
hideOpener: true,
header: dom.create(AppHeader, this._appModel.currentOrgName, this._appModel),
content: leftPanelBasic(this._appModel, panelOpen),
},
headerMain: this._buildMainHeader(),
contentTop: buildHomeBanners(this._appModel),
contentMain: this._buildMainContent(),
});
}
private _buildMainHeader() {
return dom.frag(
cssBreadcrumbs({style: 'margin-left: 16px;'},
cssLink(
urlState().setLinkUrl({}),
t('Home'),
),
separator(' / '),
dom('span', t('Support Grist')),
),
createTopBarHome(this._appModel),
);
}
private _buildMainContent() {
return cssPageContainer(
cssPage(
dom('div',
cssPageTitle(t('Support Grist')),
this._buildTelemetrySection(),
this._buildSponsorshipSection(),
),
),
);
}
private _buildTelemetrySection() {
return cssSection(
cssSectionTitle(t('Telemetry')),
dom.domComputed(this._model.prefs, prefs => {
if (prefs === null) {
return cssSpinnerBox(loadingSpinner());
}
const {activation} = getGristConfig();
if (!activation?.isManager) {
if (prefs.telemetryLevel.value === 'limited') {
return [
cssParagraph(t(
'This instance is opted in to telemetry. Only the site administrator has permission to change this.',
))
];
} else {
return [
cssParagraph(t(
'This instance is opted out of telemetry. Only the site administrator has permission to change this.',
))
];
}
} else {
return [
cssParagraph(t(
'Support Grist by opting in to telemetry, which helps us understand how the product ' +
'is used, so that we can prioritize future improvements.'
)),
cssParagraph(
t('We only collect usage statistics, as detailed in our {{link}}, never document contents.', {
link: telemetryHelpCenterLink(),
}),
),
cssParagraph(t('You can opt out of telemetry at any time from this page.')),
this._buildTelemetrySectionButtons(prefs),
];
}
}),
testId('telemetry-section'),
);
}
private _buildTelemetrySectionButtons(prefs: TelemetryPrefsWithSources) {
const {telemetryLevel: {value, source}} = prefs;
if (source === 'preferences') {
return dom.domComputed(this._optInToTelemetry, (optedIn) => {
if (optedIn) {
return [
cssOptInOutMessage(
t('You have opted in to telemetry. Thank you!'), ' 🙏',
testId('telemetry-section-message'),
),
cssOptOutButton(t('Opt out of Telemetry'),
dom.on('click', () => this._optInToTelemetry.set(false)),
),
];
} else {
return [
cssOptInButton(t('Opt in to Telemetry'),
dom.on('click', () => this._optInToTelemetry.set(true)),
),
];
}
});
} else {
return cssOptInOutMessage(
value !== 'off'
? [t('You have opted in to telemetry. Thank you!'), ' 🙏']
: t('You have opted out of telemetry.'),
testId('telemetry-section-message'),
);
}
}
private _buildSponsorshipSection() {
return cssSection(
cssSectionTitle(t('Sponsor Grist Labs on GitHub')),
cssParagraph(
t(
'Grist software is developed by Grist Labs, which offers free and paid ' +
'hosted plans. We also make Grist code available under a standard free ' +
'and open OSS license (Apache 2.0) on {{link}}.',
{link: gristCoreLink()},
),
),
cssParagraph(
t(
'You can support Grist open-source development by sponsoring ' +
'us on our {{link}}.',
{link: sponsorGristLink()},
),
),
cssParagraph(t(
'We are a small and determined team. Your support matters a lot to us. ' +
'It also shows to others that there is a determined community behind this product.'
)),
cssSponsorButton(
cssButtonIconAndText(icon('Heart'), cssButtonText(t('Manage Sponsorship'))),
{href: commonUrls.githubSponsorGristLabs, target: '_blank'},
),
testId('sponsorship-section'),
);
}
}
function telemetryHelpCenterLink() {
return cssLink(
t('Help Center'),
{href: commonUrls.helpTelemetryLimited, target: '_blank'},
);
}
function sponsorGristLink() {
return cssLink(
t('GitHub Sponsors page'),
{href: commonUrls.githubSponsorGristLabs, target: '_blank'},
);
}
function gristCoreLink() {
return cssLink(
t('GitHub'),
{href: commonUrls.githubGristCore, target: '_blank'},
);
}
const cssPageContainer = styled('div', `
overflow: auto;
padding: 64px 80px;
@media ${mediaSmall} {
& {
padding: 0px;
}
}
`);
const cssPage = styled('div', `
padding: 16px;
max-width: 600px;
width: 100%;
`);
const cssPageTitle = styled('div', `
height: 32px;
line-height: 32px;
margin-bottom: 24px;
color: ${theme.text};
font-size: 24px;
font-weight: ${vars.headerControlTextWeight};
`);
const cssSectionTitle = styled('div', `
height: 24px;
line-height: 24px;
margin-bottom: 24px;
color: ${theme.text};
font-size: ${vars.xlargeFontSize};
font-weight: ${vars.headerControlTextWeight};
`);
const cssSection = styled('div', `
margin-bottom: 60px;
`);
const cssParagraph = styled('div', `
color: ${theme.text};
font-size: 14px;
line-height: 20px;
margin-bottom: 12px;
`);
const cssOptInOutMessage = styled(cssParagraph, `
line-height: 40px;
font-weight: 600;
margin-top: 24px;
margin-bottom: 0px;
`);
const cssOptInButton = styled(bigPrimaryButton, `
margin-top: 24px;
`);
const cssOptOutButton = styled(bigBasicButton, `
margin-top: 24px;
`);
const cssSponsorButton = styled(bigBasicButtonLink, `
margin-top: 24px;
`);
const cssButtonIconAndText = styled('div', `
display: flex;
align-items: center;
`);
const cssButtonText = styled('span', `
margin-left: 8px;
`);
const cssSpinnerBox = styled('div', `
margin-top: 24px;
text-align: center;
`);