2022-10-28 16:11:08 +00:00
|
|
|
import {makeT} from 'app/client/lib/localization';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {AppModel} from 'app/client/models/AppModel';
|
2023-09-26 08:09:06 +00:00
|
|
|
import {getLoginUrl, getMainOrgUrl, getSignupUrl, urlState} from 'app/client/models/gristUrlState';
|
2021-08-18 17:49:34 +00:00
|
|
|
import {AppHeader} from 'app/client/ui/AppHeader';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {leftPanelBasic} from 'app/client/ui/LeftPanelCommon';
|
|
|
|
import {pagePanels} from 'app/client/ui/PagePanels';
|
2024-01-24 09:58:19 +00:00
|
|
|
import {setUpPage} from 'app/client/ui/setUpPage';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {createTopBarHome} from 'app/client/ui/TopBar';
|
|
|
|
import {bigBasicButtonLink, bigPrimaryButtonLink} from 'app/client/ui2018/buttons';
|
2024-01-24 09:58:19 +00:00
|
|
|
import {colors, theme, vars} from 'app/client/ui2018/cssVars';
|
|
|
|
import {icon} from 'app/client/ui2018/icons';
|
|
|
|
import {getPageTitleSuffix} from 'app/common/gristUrls';
|
2022-06-10 19:13:11 +00:00
|
|
|
import {getGristConfig} from 'app/common/urlUtils';
|
2020-10-02 15:10:00 +00:00
|
|
|
import {dom, DomElementArg, makeTestId, observable, styled} from 'grainjs';
|
|
|
|
|
|
|
|
const testId = makeTestId('test-');
|
|
|
|
|
2022-10-28 16:11:08 +00:00
|
|
|
const t = makeT('errorPages');
|
|
|
|
|
2024-01-24 09:58:19 +00:00
|
|
|
export function setUpErrPage() {
|
|
|
|
const {errPage} = getGristConfig();
|
|
|
|
const attachTheme = errPage !== 'form-not-found';
|
|
|
|
setUpPage((appModel) => {
|
|
|
|
return createErrPage(appModel);
|
|
|
|
}, {attachTheme});
|
|
|
|
}
|
|
|
|
|
2020-10-02 15:10:00 +00:00
|
|
|
export function createErrPage(appModel: AppModel) {
|
2024-01-24 09:58:19 +00:00
|
|
|
const {errMessage, errPage} = getGristConfig();
|
|
|
|
return errPage === 'signed-out' ? createSignedOutPage(appModel) :
|
|
|
|
errPage === 'not-found' ? createNotFoundPage(appModel, errMessage) :
|
|
|
|
errPage === 'access-denied' ? createForbiddenPage(appModel, errMessage) :
|
|
|
|
errPage === 'account-deleted' ? createAccountDeletedPage(appModel) :
|
|
|
|
errPage === 'form-not-found' ? createFormNotFoundPage(errMessage) :
|
|
|
|
createOtherErrorPage(appModel, errMessage);
|
2020-10-02 15:10:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a page to show that the user has no access to this org.
|
|
|
|
*/
|
|
|
|
export function createForbiddenPage(appModel: AppModel, message?: string) {
|
2022-12-06 13:57:29 +00:00
|
|
|
document.title = t("Access denied{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())});
|
2022-06-10 19:13:11 +00:00
|
|
|
|
2022-05-18 10:25:14 +00:00
|
|
|
const isAnonym = () => !appModel.currentValidUser;
|
|
|
|
const isExternal = () => appModel.currentValidUser?.loginMethod === 'External';
|
2022-12-06 13:57:29 +00:00
|
|
|
return pagePanelsError(appModel, t("Access denied{{suffix}}", {suffix: ''}), [
|
2020-10-02 15:10:00 +00:00
|
|
|
dom.domComputed(appModel.currentValidUser, user => user ? [
|
2022-12-06 13:57:29 +00:00
|
|
|
cssErrorText(message || t("You do not have access to this organization's documents.")),
|
2023-07-16 16:52:13 +00:00
|
|
|
cssErrorText(t("You are signed in as {{email}}. You can sign in with a different \
|
|
|
|
account, or ask an administrator for access.", {email: dom('b', user.email)})),
|
2020-10-02 15:10:00 +00:00
|
|
|
] : [
|
|
|
|
// This page is not normally shown because a logged out user with no access will get
|
|
|
|
// redirected to log in. But it may be seen if a user logs out and returns to a cached
|
2022-05-18 10:25:14 +00:00
|
|
|
// version of this page or is an external user (connected through GristConnect).
|
2022-12-06 13:57:29 +00:00
|
|
|
cssErrorText(t("Sign in to access this organization's documents.")),
|
2020-10-02 15:10:00 +00:00
|
|
|
]),
|
|
|
|
cssButtonWrap(bigPrimaryButtonLink(
|
2022-12-06 13:57:29 +00:00
|
|
|
isExternal() ? t("Go to main page") :
|
|
|
|
isAnonym() ? t("Sign in") :
|
|
|
|
t("Add account"),
|
2022-05-18 10:25:14 +00:00
|
|
|
{href: isExternal() ? getMainOrgUrl() : getLoginUrl()},
|
2020-10-02 15:10:00 +00:00
|
|
|
testId('error-signin'),
|
|
|
|
))
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a page that shows the user is logged out.
|
|
|
|
*/
|
|
|
|
export function createSignedOutPage(appModel: AppModel) {
|
2022-12-06 13:57:29 +00:00
|
|
|
document.title = t("Signed out{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())});
|
2022-06-10 19:13:11 +00:00
|
|
|
|
2023-01-03 16:45:14 +00:00
|
|
|
return pagePanelsError(appModel, t("Signed out{{suffix}}", {suffix: ''}), [
|
2022-12-06 13:57:29 +00:00
|
|
|
cssErrorText(t("You are now signed out.")),
|
2020-10-02 15:10:00 +00:00
|
|
|
cssButtonWrap(bigPrimaryButtonLink(
|
2022-12-06 13:57:29 +00:00
|
|
|
t("Sign in again"), {href: getLoginUrl()}, testId('error-signin')
|
2020-10-02 15:10:00 +00:00
|
|
|
))
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2023-09-26 08:09:06 +00:00
|
|
|
/**
|
|
|
|
* Creates a page that shows the user is logged out.
|
|
|
|
*/
|
|
|
|
export function createAccountDeletedPage(appModel: AppModel) {
|
|
|
|
document.title = t("Account deleted{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())});
|
|
|
|
|
|
|
|
return pagePanelsError(appModel, t("Account deleted{{suffix}}", {suffix: ''}), [
|
|
|
|
cssErrorText(t("Your account has been deleted.")),
|
|
|
|
cssButtonWrap(bigPrimaryButtonLink(
|
|
|
|
t("Sign up"), {href: getSignupUrl()}, testId('error-signin')
|
|
|
|
))
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2020-10-02 15:10:00 +00:00
|
|
|
/**
|
|
|
|
* Creates a "Page not found" page.
|
|
|
|
*/
|
|
|
|
export function createNotFoundPage(appModel: AppModel, message?: string) {
|
2022-12-06 13:57:29 +00:00
|
|
|
document.title = t("Page not found{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())});
|
2022-06-10 19:13:11 +00:00
|
|
|
|
2022-12-06 13:57:29 +00:00
|
|
|
return pagePanelsError(appModel, t("Page not found{{suffix}}", {suffix: ''}), [
|
2022-12-27 18:35:03 +00:00
|
|
|
cssErrorText(message ||
|
|
|
|
t("The requested page could not be found.{{separator}}Please check the URL and try again.", {
|
|
|
|
separator: dom('br')
|
|
|
|
})),
|
2022-12-06 13:57:29 +00:00
|
|
|
cssButtonWrap(bigPrimaryButtonLink(t("Go to main page"), testId('error-primary-btn'),
|
2020-10-02 15:10:00 +00:00
|
|
|
urlState().setLinkUrl({}))),
|
2022-12-06 13:57:29 +00:00
|
|
|
cssButtonWrap(bigBasicButtonLink(t("Contact support"), {href: 'https://getgrist.com/contact'})),
|
2020-10-02 15:10:00 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2024-01-24 09:58:19 +00:00
|
|
|
/**
|
|
|
|
* Creates a form-specific "Not Found" page.
|
|
|
|
*/
|
|
|
|
export function createFormNotFoundPage(message?: string) {
|
|
|
|
document.title = t("Form not found");
|
|
|
|
|
|
|
|
return cssFormErrorPage(
|
|
|
|
cssFormErrorContainer(
|
|
|
|
cssFormError(
|
|
|
|
cssFormErrorBody(
|
|
|
|
cssFormErrorImage({src: 'forms/form-not-found.svg'}),
|
|
|
|
cssFormErrorText(
|
|
|
|
message ?? t('An unknown error occurred.'),
|
|
|
|
testId('error-text'),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
cssFormErrorFooter(
|
|
|
|
cssFormPoweredByGrist(
|
|
|
|
cssFormPoweredByGristLink(
|
|
|
|
{href: 'https://www.getgrist.com', target: '_blank'},
|
|
|
|
t('Powered by'),
|
|
|
|
cssGristLogo(),
|
|
|
|
)
|
|
|
|
),
|
|
|
|
cssFormBuildForm(
|
|
|
|
cssFormBuildFormLink(
|
|
|
|
{href: 'https://www.getgrist.com', target: '_blank'},
|
|
|
|
t('Build your own form'),
|
|
|
|
icon('Expand'),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-10-02 15:10:00 +00:00
|
|
|
/**
|
|
|
|
* Creates a generic error page with the given message.
|
|
|
|
*/
|
|
|
|
export function createOtherErrorPage(appModel: AppModel, message?: string) {
|
2022-12-06 13:57:29 +00:00
|
|
|
document.title = t("Error{{suffix}}", {suffix: getPageTitleSuffix(getGristConfig())});
|
2022-06-10 19:13:11 +00:00
|
|
|
|
2022-12-06 13:57:29 +00:00
|
|
|
return pagePanelsError(appModel, t("Something went wrong"), [
|
2022-12-06 15:36:14 +00:00
|
|
|
cssErrorText(message ? t('There was an error: {{message}}', {message: addPeriod(message)}) :
|
|
|
|
t('There was an unknown error.')),
|
2022-12-06 13:57:29 +00:00
|
|
|
cssButtonWrap(bigPrimaryButtonLink(t("Go to main page"), testId('error-primary-btn'),
|
2020-10-02 15:10:00 +00:00
|
|
|
urlState().setLinkUrl({}))),
|
2022-12-06 13:57:29 +00:00
|
|
|
cssButtonWrap(bigBasicButtonLink(t("Contact support"), {href: 'https://getgrist.com/contact'})),
|
2020-10-02 15:10:00 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addPeriod(msg: string): string {
|
|
|
|
return msg.endsWith('.') ? msg : msg + '.';
|
|
|
|
}
|
|
|
|
|
|
|
|
function pagePanelsError(appModel: AppModel, header: string, content: DomElementArg) {
|
|
|
|
const panelOpen = observable(false);
|
|
|
|
return pagePanels({
|
|
|
|
leftPanel: {
|
|
|
|
panelWidth: observable(240),
|
|
|
|
panelOpen,
|
|
|
|
hideOpener: true,
|
2023-07-26 22:31:02 +00:00
|
|
|
header: dom.create(AppHeader, appModel),
|
2020-10-02 15:10:00 +00:00
|
|
|
content: leftPanelBasic(appModel, panelOpen),
|
|
|
|
},
|
|
|
|
headerMain: createTopBarHome(appModel),
|
|
|
|
contentMain: cssCenteredContent(cssErrorContent(
|
|
|
|
cssBigIcon(),
|
|
|
|
cssErrorHeader(header, testId('error-header')),
|
|
|
|
content,
|
|
|
|
testId('error-content'),
|
|
|
|
)),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const cssCenteredContent = styled('div', `
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
overflow-y: auto;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssErrorContent = styled('div', `
|
|
|
|
text-align: center;
|
|
|
|
margin: 64px 0 64px;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssBigIcon = styled('div', `
|
|
|
|
display: inline-block;
|
|
|
|
width: 100%;
|
|
|
|
height: 64px;
|
|
|
|
background-image: var(--icon-GristLogo);
|
|
|
|
background-size: contain;
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
background-position: center;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssErrorHeader = styled('div', `
|
|
|
|
font-weight: ${vars.headerControlTextWeight};
|
|
|
|
font-size: ${vars.xxxlargeFontSize};
|
|
|
|
margin: 24px;
|
|
|
|
text-align: center;
|
2022-09-06 01:51:57 +00:00
|
|
|
color: ${theme.text};
|
2020-10-02 15:10:00 +00:00
|
|
|
`);
|
|
|
|
|
|
|
|
const cssErrorText = styled('div', `
|
|
|
|
font-size: ${vars.mediumFontSize};
|
2022-09-06 01:51:57 +00:00
|
|
|
color: ${theme.text};
|
2020-10-02 15:10:00 +00:00
|
|
|
margin: 0 auto 24px auto;
|
|
|
|
max-width: 400px;
|
|
|
|
text-align: center;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssButtonWrap = styled('div', `
|
|
|
|
margin-bottom: 8px;
|
|
|
|
`);
|
2024-01-24 09:58:19 +00:00
|
|
|
|
|
|
|
const cssFormErrorPage = styled('div', `
|
|
|
|
--grist-form-padding: 48px;
|
|
|
|
min-height: 100%;
|
|
|
|
width: 100%;
|
|
|
|
padding-top: 52px;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormErrorContainer = styled('div', `
|
|
|
|
padding-left: 16px;
|
|
|
|
padding-right: 16px;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormError = styled('div', `
|
|
|
|
display: flex;
|
|
|
|
text-align: center;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
border: 1px solid ${colors.darkGrey};
|
|
|
|
border-radius: 3px;
|
|
|
|
max-width: 600px;
|
|
|
|
margin: 0px auto;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormErrorBody = styled('div', `
|
|
|
|
padding: 48px 16px 0px 16px;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormErrorImage = styled('img', `
|
|
|
|
width: 250px;
|
|
|
|
height: 281px;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormErrorText = styled('div', `
|
|
|
|
font-weight: 600;
|
|
|
|
font-size: 16px;
|
|
|
|
line-height: 24px;
|
|
|
|
margin-top: 32px;
|
|
|
|
margin-bottom: 24px;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormErrorFooter = styled('div', `
|
|
|
|
border-top: 1px solid ${colors.darkGrey};
|
|
|
|
padding: 8px 16px;
|
|
|
|
width: 100%;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormPoweredByGrist = styled('div', `
|
|
|
|
color: ${colors.darkText};
|
|
|
|
font-size: 13px;
|
|
|
|
font-style: normal;
|
|
|
|
font-weight: 600;
|
|
|
|
line-height: 16px;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
padding: 0px 10px;
|
|
|
|
margin-left: calc(-1 * var(--grist-form-padding));
|
|
|
|
margin-right: calc(-1 * var(--grist-form-padding));
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormPoweredByGristLink = styled('a', `
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
gap: 8px;
|
|
|
|
color: ${colors.darkText};
|
|
|
|
text-decoration: none;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormBuildForm = styled('div', `
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
margin-top: 8px;
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssFormBuildFormLink = styled('a', `
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
font-size: 11px;
|
|
|
|
line-height: 16px;
|
|
|
|
text-decoration-line: underline;
|
|
|
|
color: ${colors.darkGreen};
|
|
|
|
--icon-color: ${colors.darkGreen};
|
|
|
|
`);
|
|
|
|
|
|
|
|
const cssGristLogo = styled('div', `
|
|
|
|
width: 58px;
|
|
|
|
height: 20.416px;
|
|
|
|
flex-shrink: 0;
|
|
|
|
background: url(forms/logo.png);
|
|
|
|
background-position: 0 0;
|
|
|
|
background-size: contain;
|
|
|
|
background-color: transparent;
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
margin-top: 3px;
|
|
|
|
`);
|