Summary: Enables configuration of multi-factor authentication from the account page (for users who sign in with email/password), and adds SMS as an authentication method. Test Plan: Project, browser and server tests. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3215pull/121/head
parent
1b4580d92e
commit
0d005eb78d
@ -1,17 +1,5 @@
|
||||
import {TopAppModelImpl} from 'app/client/models/AppModel';
|
||||
import {setUpErrorHandling} from 'app/client/models/errors';
|
||||
import {AccountPage} from 'app/client/ui/AccountPage';
|
||||
import {buildSnackbarDom} from 'app/client/ui/NotifyUI';
|
||||
import {addViewportTag} from 'app/client/ui/viewport';
|
||||
import {attachCssRootVars} from 'app/client/ui2018/cssVars';
|
||||
import {setupPage} from 'app/client/ui/setupPage';
|
||||
import {dom} from 'grainjs';
|
||||
|
||||
// Set up the global styles for variables, and root/body styles.
|
||||
setUpErrorHandling();
|
||||
const topAppModel = TopAppModelImpl.create(null, {});
|
||||
attachCssRootVars(topAppModel.productFlavor);
|
||||
addViewportTag();
|
||||
dom.update(document.body, dom.maybe(topAppModel.appObs, (appModel) => [
|
||||
dom.create(AccountPage, appModel),
|
||||
buildSnackbarDom(appModel.notifier, appModel),
|
||||
]));
|
||||
setupPage((appModel) => dom.create(AccountPage, appModel));
|
||||
|
@ -1,17 +1,4 @@
|
||||
import {TopAppModelImpl} from 'app/client/models/AppModel';
|
||||
import {setUpErrorHandling} from 'app/client/models/errors';
|
||||
import {createErrPage} from 'app/client/ui/errorPages';
|
||||
import {buildSnackbarDom} from 'app/client/ui/NotifyUI';
|
||||
import {addViewportTag} from 'app/client/ui/viewport';
|
||||
import {attachCssRootVars} from 'app/client/ui2018/cssVars';
|
||||
import {dom} from 'grainjs';
|
||||
import {setupPage} from 'app/client/ui/setupPage';
|
||||
|
||||
// Set up the global styles for variables, and root/body styles.
|
||||
setUpErrorHandling();
|
||||
const topAppModel = TopAppModelImpl.create(null, {});
|
||||
attachCssRootVars(topAppModel.productFlavor);
|
||||
addViewportTag();
|
||||
dom.update(document.body, dom.maybe(topAppModel.appObs, (appModel) => [
|
||||
createErrPage(appModel),
|
||||
buildSnackbarDom(appModel.notifier, appModel),
|
||||
]));
|
||||
setupPage((appModel) => createErrPage(appModel));
|
||||
|
@ -0,0 +1,54 @@
|
||||
import {reportError} from 'app/client/models/errors';
|
||||
import {BaseAPI} from 'app/common/BaseAPI';
|
||||
import {dom, Observable} from 'grainjs';
|
||||
|
||||
/**
|
||||
* Handles submission of an HTML form element.
|
||||
*
|
||||
* When the form is submitted, `onSubmit` will be called, followed by
|
||||
* either `onSuccess` or `onError`, depending on whether `onSubmit` threw any
|
||||
* unhandled errors. The `pending` observable is set to true until `onSubmit`
|
||||
* resolves.
|
||||
*/
|
||||
export function handleSubmit<T>(
|
||||
pending: Observable<boolean>,
|
||||
onSubmit: (fields: { [key: string]: string }, form: HTMLFormElement) => Promise<T> = submitForm,
|
||||
onSuccess: (v: T) => void = () => { /* noop */ },
|
||||
onError: (e: unknown) => void = (e) => reportError(e as string | Error)
|
||||
): (elem: HTMLFormElement) => void {
|
||||
return dom.on('submit', async (e, form) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
if (pending.get()) { return; }
|
||||
|
||||
pending.set(true);
|
||||
const result = await onSubmit(formDataToObj(form), form).finally(() => pending.set(false));
|
||||
onSuccess(result);
|
||||
} catch (err) {
|
||||
onError(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a form to a JSON-stringifiable object, ignoring any File fields.
|
||||
*/
|
||||
export function formDataToObj(formElem: HTMLFormElement): { [key: string]: string } {
|
||||
// Use FormData to collect values (rather than e.g. finding <input> elements) to ensure we get
|
||||
// values from all form items correctly (e.g. checkboxes and textareas).
|
||||
const formData = new FormData(formElem);
|
||||
const data: { [key: string]: string } = {};
|
||||
for (const [name, value] of formData.entries()) {
|
||||
if (typeof value === 'string') {
|
||||
data[name] = value;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a form using BaseAPI. Send inputs as JSON, and interpret any reply as JSON.
|
||||
*/
|
||||
export async function submitForm(fields: { [key: string]: string }, form: HTMLFormElement): Promise<any> {
|
||||
return BaseAPI.requestJson(form.action, {method: 'POST', body: JSON.stringify(fields)});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,21 @@
|
||||
import {AppModel, TopAppModelImpl} from 'app/client/models/AppModel';
|
||||
import {setUpErrorHandling} from 'app/client/models/errors';
|
||||
import {buildSnackbarDom} from 'app/client/ui/NotifyUI';
|
||||
import {addViewportTag} from 'app/client/ui/viewport';
|
||||
import {attachCssRootVars} from 'app/client/ui2018/cssVars';
|
||||
import {dom, DomContents} from 'grainjs';
|
||||
|
||||
/**
|
||||
* Sets up error handling and global styles, and replaces the DOM body with
|
||||
* the result of calling `buildPage`.
|
||||
*/
|
||||
export function setupPage(buildPage: (appModel: AppModel) => DomContents) {
|
||||
setUpErrorHandling();
|
||||
const topAppModel = TopAppModelImpl.create(null, {});
|
||||
attachCssRootVars(topAppModel.productFlavor);
|
||||
addViewportTag();
|
||||
dom.update(document.body, dom.maybe(topAppModel.appObs, (appModel) => [
|
||||
buildPage(appModel),
|
||||
buildSnackbarDom(appModel.notifier, appModel),
|
||||
]));
|
||||
}
|
After Width: | Height: | Size: 812 B |
After Width: | Height: | Size: 658 B |
Loading…
Reference in new issue