(core) Polish forms

Summary:
  - Updates styling of form submitted page.
  - Tweaks styling of checkboxes, labels, and questions on form page.
  - Adds new form 404 page.
  - Adds checkbox to not show warning again when publishing or un-publishing a form.
  - Excludes formula, hidden, and attachment columns in submitted form data.
  - Adds placeholder text to form configuration inputs.
  - Improves dark mode styling in Form widget.
  - Updates default title and description of new forms.
  - Updates styling of Form widget buttons.
  - Fixes form success text input handling.

Test Plan: Browser tests.

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D4170
This commit is contained in:
George Gevoian
2024-01-24 01:58:19 -08:00
parent b77c762358
commit 6cb8614017
26 changed files with 769 additions and 302 deletions

View File

@@ -237,7 +237,7 @@ export function attachAppEndpoint(options: AttachOptions): void {
res.send(html);
} else {
const error = await response.json();
throw new ApiError(error?.error ?? 'Failed to fetch form', response.status);
throw new ApiError(error?.error ?? 'An unknown error occurred.', response.status, error?.details);
}
}));
}

View File

@@ -1493,6 +1493,7 @@ export class DocWorkerApi {
question: options.question || col.label || colId,
options,
type,
isFormula: Boolean(col.isFormula && col.formula),
// If this is reference field, we will need to fetch the referenced table.
values: refValues(col)
};
@@ -1529,7 +1530,7 @@ export class DocWorkerApi {
ANOTHER_RESPONSE: Boolean(box.anotherResponse),
// Not trusted content entered by user.
CONTENT: html,
SUCCESS_TEXT: box.successText || `Thank you! Your response has been recorded.`,
SUCCESS_TEXT: box.successText || 'Thank you! Your response has been recorded.',
SUCCESS_URL: redirectUrl,
});
res.status(200).send(renderedHtml);
@@ -1550,40 +1551,37 @@ export class DocWorkerApi {
throw new ApiError('DocData not available', 500);
}
const notFoundError = () => {
throw new ApiError("Oops! The form you're looking for doesn't exist.", 404, {
code: 'FormNotFound',
});
};
// Check that the request is for a valid section in the document.
const sections = docData.getMetaTable('_grist_Views_section');
const section = sections.getRecord(sectionId);
if (!section) {
throw new ApiError('Form not found', 404);
}
if (!section) { return notFoundError(); }
// Check that the section is for a form.
const sectionShareOptions = safeJsonParse(section.shareOptions, {});
if (!sectionShareOptions.form) {
throw new ApiError('Form not found', 400);
}
if (!sectionShareOptions.form) { return notFoundError(); }
// Check that the form is associated with a share.
const viewId = section.parentId;
const pages = docData.getMetaTable('_grist_Pages');
const page = pages.getRecords().find(p => p.viewRef === viewId);
if (!page) {
throw new ApiError('Form not found', 404);
}
if (!page) { return notFoundError(); }
const shares = docData.getMetaTable('_grist_Shares');
const share = shares.getRecord(page.shareRef);
if (!share) {
throw new ApiError('Form not found', 404);
}
if (!share) { return notFoundError(); }
// Check that the share's link id matches the expected link id.
if (share.linkId !== linkId) {
throw new ApiError('Form not found', 404);
}
if (share.linkId !== linkId) { return notFoundError(); }
// Finally, check that both the section and share are published.
if (!sectionShareOptions.publish || !safeJsonParse(share.options, {})?.publish) {
throw new ApiError('Form not published', 400);
throw new ApiError('Oops! This form is no longer published.', 404, {code: 'FormNotFound'});
}
}

View File

@@ -1510,6 +1510,7 @@ export class FlexServer implements GristServer {
if (resp.headersSent || !this._sendAppPage) { return next(err); }
try {
const errPage = (
err.details?.code === 'FormNotFound' ? 'form-not-found' :
err.status === 403 ? 'access-denied' :
err.status === 404 ? 'not-found' :
'other-error'