FIX: Fallback page loading

This commit is contained in:
Tony Air 2021-03-30 12:37:02 +07:00
parent 7f9a0f9d7a
commit 4ffbfa5a01
4 changed files with 443 additions and 417 deletions

View File

@ -2,101 +2,103 @@ import Events from '../_events';
import { cache } from './_apollo.cache'; import { cache } from './_apollo.cache';
import { import {
from, from,
ApolloClient, ApolloClient,
HttpLink, HttpLink,
ApolloLink, ApolloLink,
concat, concat,
} from '@apollo/client'; } from '@apollo/client';
import { onError } from '@apollo/client/link/error'; import { onError } from '@apollo/client/link/error';
const NAME = '_appolo'; const NAME = '_appolo';
const API_META = document.querySelector('meta[name="api_url"]'); const API_META = document.querySelector('meta[name="api_url"]');
const API_URL = API_META const API_URL = API_META ?
? API_META.getAttribute('content') API_META.getAttribute('content') :
: `${window.location.protocol }//${ window.location.host }/graphql`; `${window.location.protocol }//${ window.location.host }/graphql`;
const authMiddleware = new ApolloLink((operation, forward) => { const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers // add the authorization to the headers
operation.setContext({ operation.setContext({
headers: { headers: {
apikey: `${GRAPHQL_API_KEY}`, apikey: `${GRAPHQL_API_KEY}`,
}, },
}); });
return forward(operation); return forward(operation);
}); });
console.info(`%cAPI: ${API_URL}`, 'color:green;font-size:10px'); console.info(`%cAPI: ${API_URL}`, 'color:green;font-size:10px');
const link = from([ const link = from([
authMiddleware, authMiddleware,
new ApolloLink((operation, forward) => { new ApolloLink((operation, forward) => {
operation.setContext({ start: new Date() }); operation.setContext({ start: new Date() });
return forward(operation); return forward(operation);
}), }),
onError(({ operation, response, graphQLErrors, networkError, forward }) => { onError(({ operation, response, graphQLErrors, networkError, forward }) => {
if (operation.operationName === 'IgnoreErrorsQuery') { if (operation.operationName === 'IgnoreErrorsQuery') {
response.errors = null; console.error(`${NAME}: IgnoreErrorsQuery`);
return; response.errors = null;
} return;
}
if (graphQLErrors) if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) => graphQLErrors.forEach(({ message, locations, path }) =>
console.error( console.error(
`${NAME}: [GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, `${NAME}: [GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
), ),
); );
}
if (networkError) { if (networkError) {
/*let msg = ''; /*let msg = '';
switch (networkError.statusCode) { switch (networkError.statusCode) {
case 404: case 404:
msg = 'Not Found.'; msg = 'Not Found.';
break; break;
case 500: case 500:
msg = 'Server issue, please try again latter.'; msg = 'Server issue, please try again latter.';
break; break;
default: default:
msg = 'Something went wrong.'; msg = 'Something went wrong.';
break; break;
}*/ }*/
console.error(`${NAME}: [Network error] ${networkError.statusCode}`);
}
console.error(`${NAME}: [Network error] ${networkError.statusCode}`); console.error(`${NAME}: [APOLLO_ERROR]`);
window.dispatchEvent(new Event(Events.APOLLO_ERROR));
}),
new ApolloLink((operation, forward) => {
return forward(operation).map((data) => {
// data from a previous link
const time = new Date() - operation.getContext().start;
console.log(
`${NAME}: operation ${operation.operationName} took ${time} ms to complete`,
);
window.dispatchEvent(new Event(Events.OFFLINE)); window.dispatchEvent(new Event(Events.ONLINE));
} return data;
}), });
new ApolloLink((operation, forward) => { }),
return forward(operation).map((data) => { new HttpLink({
// data from a previous link uri: API_URL,
const time = new Date() - operation.getContext().start;
console.log(
`${NAME}: operation ${operation.operationName} took ${time} ms to complete`,
);
window.dispatchEvent(new Event(Events.ONLINE)); // Use explicit `window.fetch` so tha outgoing requests
return data; // are captured and deferred until the Service Worker is ready.
}); fetch: (...args) => fetch(...args),
}), credentials: 'same-origin', //'include',
new HttpLink({ connectToDevTools: process.env.NODE_ENV === 'development' ? true : false,
uri: API_URL, }),
// Use explicit `window.fetch` so tha outgoing requests
// are captured and deferred until the Service Worker is ready.
fetch: (...args) => fetch(...args),
credentials: 'same-origin', //'include',
connectToDevTools: process.env.NODE_ENV === 'development' ? true : false,
}),
]); ]);
// Isolate Apollo client so it could be reused // Isolate Apollo client so it could be reused
// in both application runtime and tests. // in both application runtime and tests.
const client = new ApolloClient({ const client = new ApolloClient({
cache, cache,
link, link,
}); });
export { client }; export { client };

View File

@ -9,192 +9,213 @@ import { Collapse } from 'bootstrap';
import SpinnerUI from './_main.loading-spinner'; import SpinnerUI from './_main.loading-spinner';
const MainUILinks = ((W) => { const MainUILinks = ((W) => {
const NAME = '_main.links'; const NAME = '_main.links';
const D = document; const D = document;
const BODY = D.body; const BODY = D.body;
class MainUILinks { class MainUILinks {
static init() { window
const ui = this; static init() {
ui.GraphPage = null; const ui = this;
ui.GraphPage = null;
console.log(`${NAME}: init`); console.log(`${NAME}: init`);
ui.loaded(); ui.loaded();
// history state switch // history state switch
W.addEventListener('popstate', (e) => { W.addEventListener('popstate', (e) => {
ui.popState(e); ui.popState(e);
}); });
}
static loaded() {
const ui = this;
D.querySelectorAll('.graphql-page').forEach((el, i) => {
const el_id = el.getAttribute('href');
el.setAttribute(`data-${ui.name}-id`, el_id);
el.addEventListener('click', ui.loadClick);
});
}
static setActiveLinks(link) {
const ui = this;
D.querySelectorAll(`[data-${ui.name}-id="${link}"]`).forEach(
(el) => {
el.classList.add('active');
},
);
}
static reset() {
// reset focus
D.activeElement.blur();
// remove active and loading classes
D.querySelectorAll('.graphql-page,.nav-item').forEach((el2) => {
el2.classList.remove('active', 'loading');
});
}
static popState(e) {
const ui = this;
SpinnerUI.show();
if (e.state && e.state.page) {
console.log(`${NAME}: [popstate] load`);
const state = JSON.parse(e.state.page);
state.current = null;
state.popstate = true;
ui.reset();
ui.setActiveLinks(e.state.link);
if (!ui.GraphPage) {
console.log(
`${NAME}: [popstate] GraphPage is missing. Have to render it first`,
);
ui.GraphPage = ReactDOM.render(
<Page />,
document.getElementById('MainContent'),
);
} }
ui.GraphPage.setState(state); static loaded() {
SpinnerUI.hide(); const ui = this;
window.dispatchEvent(new Event(Events.AJAX)); D.querySelectorAll('.graphql-page').forEach((el, i) => {
} else if (e.state && e.state.landing) { const el_id = el.getAttribute('href');
console.log(`${NAME}: [popstate] go to landing`); el.setAttribute(`data-${ui.name}-id`, el_id);
W.location.href = e.state.landing;
} else {
console.warn(`${NAME}: [popstate] state is missing`);
console.log(e);
SpinnerUI.hide();
}
}
// link specific event {this} = current event, not MainUILinks el.addEventListener('click', ui.loadClick);
static loadClick(e) { });
console.groupCollapsed(`${NAME}: load on click`); }
e.preventDefault();
const ui = MainUILinks; static setActiveLinks(link) {
const el = e.currentTarget; const ui = this;
D.querySelectorAll(`[data-${ui.name}-id="${link}"]`).forEach(
SpinnerUI.show(); (el) => {
el.classList.add('active');
ui.reset(); },
el.classList.add('loading');
el.classList.remove('response-404', 'response-500', 'response-523');
BODY.classList.add('ajax-loading');
// hide parent mobile nav
const navs = getParents(el, '.collapse');
if (navs.length) {
navs.forEach((nav) => {
const collapseInst = Collapse.getInstance(nav);
if (collapseInst) {
collapseInst.hide();
}
});
}
// hide parent dropdown
/*const dropdowns = getParents(el, '.dropdown-menu');
if (dropdowns.length) {
const DropdownInst = Dropdown.getInstance(dropdowns[0]);
DropdownInst.hide();
}*/
if (!ui.GraphPage) {
ui.GraphPage = ReactDOM.render(
<Page />,
document.getElementById('MainContent'),
);
}
const link = el.getAttribute('href') || el.getAttribute('data-href');
ui.GraphPage.state.current = el;
ui.GraphPage.load(link)
.then((response) => {
BODY.classList.remove('ajax-loading');
el.classList.remove('loading');
el.classList.add('active');
if (ui.GraphPage.state.Link) {
window.history.pushState({
page: JSON.stringify(ui.GraphPage.state),
link: el.getAttribute(`data-${ui.name}-id`),
},
ui.GraphPage.state.Title,
ui.GraphPage.state.Link,
); );
}
ui.setActiveLinks(ui.GraphPage.state.Link) static reset() {
} // reset focus
D.activeElement.blur();
SpinnerUI.hide(); // remove active and loading classes
D.querySelectorAll('.graphql-page,.nav-item').forEach((el2) => {
el2.classList.remove('active', 'loading');
});
}
window.dispatchEvent(new Event(Events.AJAX)); static popState(e) {
console.groupEnd(`${NAME}: load on click`); const ui = this;
})
.catch((e) => {
console.log(e);
BODY.classList.remove('ajax-loading'); SpinnerUI.show();
el.classList.remove('loading');
el.classList.add('error', `response-${e.status}`); if (e.state && e.state.page) {
/*switch (e.status) { console.log(`${NAME}: [popstate] load`);
case 404: const state = JSON.parse(e.state.page);
el.classList.add('not-found');
break; state.current = null;
case 523: state.popstate = true;
el.classList.add('unreachable');
break; ui.reset();
ui.setActiveLinks(e.state.link);
if (!ui.GraphPage) {
console.log(
`${NAME}: [popstate] GraphPage is missing. Have to render it first`,
);
ui.GraphPage = ReactDOM.render(
<Page />,
document.getElementById('MainContent'),
);
}
ui.GraphPage.setState(state);
SpinnerUI.hide();
window.dispatchEvent(new Event(Events.AJAX));
} else if (e.state && e.state.landing) {
console.log(`${NAME}: [popstate] go to landing`);
W.location.href = e.state.landing;
} else {
console.warn(`${NAME}: [popstate] state is missing`);
console.log(e);
SpinnerUI.hide();
}
}
// link specific event {this} = current event, not MainUILinks
static loadClick(e) {
console.groupCollapsed(`${NAME}: load on click`);
e.preventDefault();
const ui = MainUILinks;
const el = e.currentTarget;
SpinnerUI.show();
ui.reset();
el.classList.add('loading');
el.classList.remove('response-404', 'response-500', 'response-523');
BODY.classList.add('ajax-loading');
// hide parent mobile nav
const navs = getParents(el, '.collapse');
if (navs.length) {
navs.forEach((nav) => {
const collapseInst = Collapse.getInstance(nav);
if (collapseInst) {
collapseInst.hide();
}
});
}
// hide parent dropdown
/*const dropdowns = getParents(el, '.dropdown-menu');
if (dropdowns.length) {
const DropdownInst = Dropdown.getInstance(dropdowns[0]);
DropdownInst.hide();
}*/
if (!ui.GraphPage) {
ui.GraphPage = ReactDOM.render(
<Page />,
document.getElementById('MainContent'),
);
}
const link = el.getAttribute('href') || el.getAttribute('data-href');
ui.GraphPage.state.current = el;
ui.GraphPage.load(link)
.then((response) => {
BODY.classList.remove('ajax-loading');
el.classList.remove('loading');
el.classList.add('active');
D.loading_apollo_link = null;
if (ui.GraphPage.state.Link) {
window.history.pushState({
page: JSON.stringify(ui.GraphPage.state),
link: el.getAttribute(`data-${ui.name}-id`),
},
ui.GraphPage.state.Title,
ui.GraphPage.state.Link,
);
ui.setActiveLinks(ui.GraphPage.state.Link)
}
SpinnerUI.hide();
window.dispatchEvent(new Event(Events.AJAX));
console.groupEnd(`${NAME}: load on click`);
})
.catch((e) => {
console.error(`${NAME}: loading error`);
console.log(e);
BODY.classList.remove('ajax-loading');
el.classList.remove('loading');
el.classList.add('error', `response-${e.status}`);
/*switch (e.status) {
case 500:
break;
case 404:
el.classList.add('not-found');
break;
case 523:
el.classList.add('unreachable');
break;
}*/ }*/
SpinnerUI.hide(); SpinnerUI.hide();
window.dispatchEvent(new Event(Events.AJAX)); window.dispatchEvent(new Event(Events.AJAX));
console.groupEnd(`${NAME}: load on click`); console.groupEnd(`${NAME}: load on click`);
});
console.log(`${NAME}: reloading page ${link}`);
// fallback loading
W.location.href = link;
});
}
} }
}
W.addEventListener(`${Events.LOADED}`, () => { W.addEventListener(`${Events.LOADED}`, () => {
MainUILinks.init(); MainUILinks.init();
}); });
W.addEventListener(`${Events.AJAX}`, () => { W.addEventListener(`${Events.AJAX}`, () => {
MainUILinks.loaded(); MainUILinks.loaded();
}); });
// fallback
/*W.addEventListener(`${Events.APOLLO_ERROR}`, (e) => {
console.error(`${NAME}: [APOLLO_ERROR] loading failure, reloading the page`);
//W.dispatchEvent(new Event(Events.OFFLINE));
if (D.loading_apollo_link) {
W.location.href = D.loading_apollo_link;
}
});*/
})(window); })(window);
export default MainUILinks; export default MainUILinks;

View File

@ -11,53 +11,53 @@ const D = document;
const BODY = document.body; const BODY = document.body;
class Page extends Component { class Page extends Component {
state = { state = {
type: [], type: [],
shown: false, shown: false,
Title: 'Loading ...', Title: 'Loading ...',
loading: true, loading: true,
error: false, error: false,
current: null, current: null,
ID: null, ID: null,
URLSegment: null, URLSegment: null,
ClassName: 'Page', ClassName: 'Page',
CSSClass: null, CSSClass: null,
Summary: null, Summary: null,
Link: null, Link: null,
URL: null, URL: null,
HTML: null, HTML: null,
Elements: [], Elements: [],
page: null, page: null,
}; };
componentDidUpdate() { componentDidUpdate() {
const ui = this; const ui = this;
if (ui.state.Title) { if (ui.state.Title) {
document.title = ui.state.Title; document.title = ui.state.Title;
} }
} }
constructor(props) { constructor(props) {
super(props); super(props);
const ui = this; const ui = this;
ui.name = ui.constructor.name; ui.name = ui.constructor.name;
ui.empty_state = ui.state; ui.empty_state = ui.state;
console.log(`${ui.name}: init`); console.log(`${ui.name}: init`);
} }
isOnline = () => { isOnline = () => {
return BODY.classList.contains('is-online'); return BODY.classList.contains('is-online');
}; };
load = (link) => { load = (link) => {
const ui = this; const ui = this;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const query = gql(`query Pages { const query = gql(`query Pages {
readPages(Link: "${link}") { readPages(Link: "${link}") {
edges { edges {
node { node {
@ -76,133 +76,135 @@ class Page extends Component {
} }
}`); }`);
if (!ui.isOnline()) { if (!ui.isOnline()) {
const data = client.readQuery({ query }); const data = client.readQuery({ query });
if (data && ui.processResponse(data)) { if (data && ui.processResponse(data)) {
console.log(`${ui.name}: Offline cached response`); console.log(`${ui.name}: Offline cached response`);
resolve(data); resolve(data);
} else { } else {
console.log(`${ui.name}: No offline response`); console.log(`${ui.name}: No offline response`);
ui.setState( ui.setState(
Object.assign(ui.empty_state, { Object.assign(ui.empty_state, {
Title: 'Offline', Title: 'Offline',
CSSClass: 'graphql__status-523', CSSClass: 'graphql__status-523',
Summary: Summary: "You're Offline. The page is not available now.",
"You're Offline. The page is not available now.", loading: false,
loading: false, }),
}), );
);
reject({ status: 523 }); reject({ status: 523 });
} }
} else { } else {
if (!ui.state.loading) { if (!ui.state.loading) {
ui.setState(ui.empty_state); ui.setState(ui.empty_state);
} }
client client
.query({ .query({
query: query, query: query,
fetchPolicy: ui.isOnline() ? 'no-cache' : 'cache-first', fetchPolicy: ui.isOnline() ? 'no-cache' : 'cache-first',
}) })
.then((resp) => { .then((resp) => {
// write to cache // write to cache
const data = resp.data; const data = resp.data;
client.writeQuery({ query, data: data }); client.writeQuery({ query, data: data });
if (ui.processResponse(data)) { if (ui.processResponse(data)) {
console.log(`${ui.name}: got the server response`); console.log(`${ui.name}: got the server response`);
resolve(data); resolve(data);
} else { } else {
console.log(`${ui.name}: not found`); console.log(`${ui.name}: not found`);
reject({ status: 404 }); reject({ status: 404 });
} }
}); })
} .catch((error) => {
}); reject({ status: 500, error: error });
}; });
}
});
};
processResponse = (data) => { processResponse = (data) => {
const ui = this; const ui = this;
if (!data.readPages.edges.length) { if (!data.readPages.edges.length) {
console.log(`${ui.name}: not found`); console.log(`${ui.name}: not found`);
ui.setState( ui.setState(
Object.assign(ui.empty_state, { Object.assign(ui.empty_state, {
Title: 'Not Found', Title: 'Not Found',
CSSClass: 'graphql__status-404', CSSClass: 'graphql__status-404',
Summary: 'The page is not found.', Summary: 'The page is not found.',
loading: false, loading: false,
}), }),
); );
return false; return false;
} }
const page = data.readPages.edges[0].node; const page = data.readPages.edges[0].node;
ui.setState({ ui.setState({
ID: page.ID, ID: page.ID,
Title: page.Title, Title: page.Title,
ClassName: page.ClassName, ClassName: page.ClassName,
URLSegment: page.URLSegment, URLSegment: page.URLSegment,
CSSClass: page.CSSClass, CSSClass: page.CSSClass,
Summary: page.Summary, Summary: page.Summary,
Link: page.Link, Link: page.Link,
HTML: page.HTML, HTML: page.HTML,
Elements: [],//page.Elements.edges, Elements: [], //page.Elements.edges,
loading: false, loading: false,
}); });
return true; return true;
}; };
getHtml = (html) => { getHtml = (html) => {
const decodeHtmlEntity = (input) => { const decodeHtmlEntity = (input) => {
var doc = new DOMParser().parseFromString(input, 'text/html'); var doc = new DOMParser().parseFromString(input, 'text/html');
return doc.documentElement.textContent; return doc.documentElement.textContent;
}; };
return { __html: decodeHtmlEntity(html) }; return { __html: decodeHtmlEntity(html) };
}; };
render() { render() {
const ui = this; const ui = this;
const name = ui.name; const name = ui.name;
const className = `elemental-area graphql__page page-${ui.state.CSSClass} url-${ui.state.URLSegment}`; const className = `elemental-area graphql__page page-${ui.state.CSSClass} url-${ui.state.URLSegment}`;
const ElementItem = (props) => ( const ElementItem = (props) => (
<div dangerouslySetInnerHTML={props.html}></div> <div dangerouslySetInnerHTML={props.html}></div>
); );
let html = ''; let html = '';
if(ui.state.HTML) { if (ui.state.HTML) {
console.log(`${ui.name}: HTML only`); console.log(`${ui.name}: HTML only`);
html = ui.state.HTML; html = ui.state.HTML;
}else if (ui.state.Elements.length) { } else if (ui.state.Elements.length) {
console.log(`${ui.name}: render`); console.log(`${ui.name}: render`);
ui.state.Elements.map((el) => { ui.state.Elements.map((el) => {
html += el.node.Render; html += el.node.Render;
}); });
} else if (ui.state.Summary && ui.state.Summary.length) { } else if (ui.state.Summary && ui.state.Summary.length) {
console.log(`${ui.name}: summary only`); console.log(`${ui.name}: summary only`);
html = `<div class="summary">${ui.state.Summary}</div>`; html = `<div class="summary">${ui.state.Summary}</div>`;
} }
if(ui.state.loading){ if (ui.state.loading) {
const spinner = D.getElementById('PageLoading'); const spinner = D.getElementById('PageLoading');
html = `<div class="loading">Loading ...</div>`; html = `<div class="loading">Loading ...</div>`;
} }
return ( return (
<div <div
className={className} className={className}
dangerouslySetInnerHTML={ui.getHtml(html)} dangerouslySetInnerHTML={ui.getHtml(html)}
></div> ></div>
); );
} }
} }
export default Page; export default Page;

View File

@ -3,41 +3,42 @@
*/ */
export default { export default {
AJAX: 'ajax-load', APOLLO_ERROR: 'apollo-error',
AJAXMAIN: 'ajax-main-load', AJAX: 'ajax-load',
MAININIT: 'main-init', AJAXMAIN: 'ajax-main-load',
TABHIDDEN: 'tab-hidden', MAININIT: 'main-init',
TABFOCUSED: 'tab-focused', TABHIDDEN: 'tab-hidden',
OFFLINE: 'offline', TABFOCUSED: 'tab-focused',
ONLINE: 'online', OFFLINE: 'offline',
BACKONLINE: 'back-online', ONLINE: 'online',
TOUCHENABLE: 'touch-enabled', BACKONLINE: 'back-online',
TOUCHDISABLED: 'touch-disabled', TOUCHENABLE: 'touch-enabled',
LOADED: 'load', TOUCHDISABLED: 'touch-disabled',
SWIPELEFT: 'swipeleft panleft', LOADED: 'load',
SWIPERIGHT: 'swiperight panright', SWIPELEFT: 'swipeleft panleft',
ALLERTAPPEARED: 'alert-appeared', SWIPERIGHT: 'swiperight panright',
ALERTREMOVED: 'alert-removed', ALLERTAPPEARED: 'alert-appeared',
LODEDANDREADY: 'load-ready', ALERTREMOVED: 'alert-removed',
LAZYIMAGEREADY: 'image-lazy-bg-loaded', LODEDANDREADY: 'load-ready',
LAZYIMAGESREADY: 'images-lazy-loaded', LAZYIMAGEREADY: 'image-lazy-bg-loaded',
MAPLOADED: 'map-loaded', LAZYIMAGESREADY: 'images-lazy-loaded',
MAPAPILOADED: 'map-api-loaded', MAPLOADED: 'map-loaded',
MAPMARKERCLICK: 'map-marker-click', MAPAPILOADED: 'map-api-loaded',
MAPPOPUPCLOSE: 'map-popup-close', MAPMARKERCLICK: 'map-marker-click',
SCROLL: 'scroll', MAPPOPUPCLOSE: 'map-popup-close',
RESIZE: 'resize', SCROLL: 'scroll',
CAROUSEL_READY: 'bs.carousel.ready', RESIZE: 'resize',
SET_TARGET_UPDATE: 'set-target-update', CAROUSEL_READY: 'bs.carousel.ready',
RESTORE_FIELD: 'restore-field', SET_TARGET_UPDATE: 'set-target-update',
FORM_INIT_BASICS: 'form-basics', RESTORE_FIELD: 'restore-field',
FORM_INIT_STEPPED: 'form-init-stepped', FORM_INIT_BASICS: 'form-basics',
FORM_INIT_VALIDATE: 'form-init-validate', FORM_INIT_STEPPED: 'form-init-stepped',
FORM_INIT_VALIDATE_FIELD: 'form-init-validate-field', FORM_INIT_VALIDATE: 'form-init-validate',
FORM_INIT_STORAGE: 'form-init-storage', FORM_INIT_VALIDATE_FIELD: 'form-init-validate-field',
FORM_VALIDATION_FAILED: 'form-validation-failed', FORM_INIT_STORAGE: 'form-init-storage',
FORM_STEPPED_NEW_STEP: 'form-new-step', FORM_VALIDATION_FAILED: 'form-validation-failed',
FORM_STEPPED_FIRST_STEP: 'form-first-step', FORM_STEPPED_NEW_STEP: 'form-new-step',
FORM_STEPPED_LAST_STEP: 'form-last-step', FORM_STEPPED_FIRST_STEP: 'form-first-step',
FORM_FIELDS: 'input,textarea,select', FORM_STEPPED_LAST_STEP: 'form-last-step',
FORM_FIELDS: 'input,textarea,select',
}; };