diff --git a/.eslintrc.json b/.eslintrc.json index a0d8ed1..8566e25 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,28 +1,145 @@ -{ - "extends": "react-app", - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module", - "ecmaFeatures": { - "jsx": true - } - }, - "rules": { - "semi": "error", - "indent": [ - "error", - 4, - { - "SwitchCase": 1 - } - ], - "quotes": ["warn", "double"], - "max-len": [ - "error", - { - "code": 80, - "tabWidth": 4 - } - ] - } -} +{ + // Thanks, BoresXP@github.com ! + // https://gist.github.com/BoresXP/e404f16a0e153eeb6ce15ce06848f36e + "extends": ["react-app"], + "parserOptions": { + "project": "tsconfig.json", + "tsconfigRootDir": ".", + "ecmaVersion": 6, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "plugins": ["react", "@typescript-eslint"], + "rules": { + "semi": "error", + "indent": [ + "error", + 4, + { + "SwitchCase": 1 + } + ], + "quotes": ["warn", "double"], + "max-len": [ + "error", + { + "code": 80, + "tabWidth": 4 + } + ], + "no-shadow": ["error", { "builtinGlobals": false }], + "no-duplicate-imports": ["error", { "includeExports": true }], + "no-template-curly-in-string": "error", + "block-scoped-var": "error", + "curly": ["error", "all"], + "eqeqeq": "error", + "max-classes-per-file": ["error", 2], + "no-alert": "warn", + "no-console": "warn", + "no-else-return": ["error", { "allowElseIf": false }], + "no-implicit-coercion": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-multi-spaces": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-wrappers": "error", + "no-return-await": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-throw-literal": "error", + "no-unused-expressions": "error", + "no-useless-call": "error", + "no-useless-concat": "error", + "no-useless-return": "error", + "prefer-promise-reject-errors": "error", + "no-undefined": "error", + "array-bracket-newline": ["error", { "multiline": true }], + "comma-dangle": ["error", "always-multiline"], + "comma-style": "error", + "eol-last": "error", + "key-spacing": "error", + "keyword-spacing": "error", + "new-parens": "error", + "no-bitwise": "warn", + "no-lonely-if": "warn", + "no-multiple-empty-lines": "error", + "no-nested-ternary": "error", + "no-new-object": "error", + "no-tabs": ["error", { "allowIndentationTabs": true }], + "no-trailing-spaces": "error", + "no-unneeded-ternary": "error", + "no-whitespace-before-property": "error", + "object-curly-newline": "error", + "object-curly-spacing": ["error", "always"], + "semi-spacing": "error", + "space-before-blocks": "error", + "space-before-function-paren": [ + "error", + { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + } + ], + "space-in-parens": "error", + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": ["error", "always"], + "switch-colon-spacing": "error", + "arrow-body-style": ["error", "as-needed"], + "arrow-parens": ["error", "always"], + "arrow-spacing": "error", + "generator-star-spacing": ["error", "after"], + "no-confusing-arrow": "error", + "no-useless-computed-key": "error", + "no-useless-rename": "error", + "object-shorthand": ["error", "always"], + "prefer-arrow-callback": "warn", + "prefer-destructuring": "error", + "rest-spread-spacing": ["error", "never"], + "template-curly-spacing": "error", + "@typescript-eslint/semi": ["error", "always"], + "@typescript-eslint/member-ordering": "warn", + "@typescript-eslint/no-magic-numbers": [ + "error", + { + "ignoreNumericLiteralTypes": true, + "ignoreEnums": true, + "enforceConst": true, + "ignoreReadonlyClassProperties": true, + "ignore": [0, 1, 2] + } + ], + "@typescript-eslint/brace-style": ["error", "1tbs"], + "@typescript-eslint/quotes": [ + "error", + "double", + { "avoidEscape": true } + ], + "@typescript-eslint/func-call-spacing": ["error", "never"], + "@typescript-eslint/no-useless-constructor": "error", + "@typescript-eslint/prefer-for-of": "warn", + "@typescript-eslint/no-parameter-properties": "error", + "@typescript-eslint/no-unnecessary-type-arguments": "warn", + "@typescript-eslint/prefer-function-type": "warn", + "@typescript-eslint/prefer-readonly": "warn", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/camelcase": ["error", { "properties": "never" }], + "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_$" } + ], + "react/no-access-state-in-setstate": "error", + "react/no-danger": "error", + "react/no-this-in-sfc": "error", + "react/prefer-stateless-function": "error", + "react/jsx-filename-extension": ["error", { "extensions": [".tsx"] }], + "react/jsx-no-bind": "error", + "react/jsx-no-literals": "warn", + "react/jsx-pascal-case": "error" + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 36c87a0..15627fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,25 @@ "files.insertFinalNewline": true, "editor.tabSize": 4, "editor.insertSpaces": true, - "editor.formatOnSave": true + "editor.formatOnSave": true, + "eslint.run": "onSave", + "eslint.autoFixOnSave": false, + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + }, + "[javascriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + } } diff --git a/src/actions/auth.ts b/src/actions/auth.ts index 92f1b8e..35a5621 100644 --- a/src/actions/auth.ts +++ b/src/actions/auth.ts @@ -1,5 +1,5 @@ import req from "../axios"; -import { AxiosResponse, AxiosError } from "axios"; +import { AxiosResponse } from "axios"; import { Dispatch } from "redux"; import Actions from "./list"; @@ -10,44 +10,42 @@ const tokenHeader = "X-Auth-Token"; /** * Redux action creator. Performs an HTTP request, if it succeeds, copies * the token from the response. - * @param username User name - * @param password Password in plain text + * @param user User name + * @param pass Password in plain text */ -export const login = (username: string, password: string) => - function(dispatch: Dispatch): void { - req.post("/login", { - name: username, - password: password, - }).then( - (res: AxiosResponse) => { - setToken(res.data.token); - dispatch({ - type: Actions.LoginSuccess, - username: username, - token: res.data.token, - } as AuthAction); - }, - (err: AxiosError) => { - dispatch({ - type: Actions.LoginFailed, - } as AuthAction); - } - ); - }; +export const login = (user: string, pass: string) => (dispatch: Dispatch) => { + req.post("/login", { + name: user, + password: pass, + }).then( + (res: AxiosResponse) => { + setToken(res.data.token); + dispatch({ + type: Actions.LoginSuccess, + username: user, + token: res.data.token, + } as AuthAction); + }, + () => { + dispatch({ + type: Actions.LoginFailed, + } as AuthAction); + } + ); +}; /** * Redux action creator. Performs an HTTP request, if it succeeds, clears * the token from the cache. */ -export const logout = () => - function(dispatch: Dispatch): void { - req.post("/logout", {}).then(() => { - resetToken(); - dispatch({ - type: Actions.LoggedOut, - } as AuthAction); - }); - }; +export const logout = () => (dispatch: Dispatch) => { + req.post("/logout", {}).then(() => { + resetToken(); + dispatch({ + type: Actions.LoggedOut, + } as AuthAction); + }); +}; /** * Sets an auth token in the app's Axios instance to be used in every request diff --git a/src/actions/tasks.ts b/src/actions/tasks.ts index 6c5bc0c..8cac72a 100644 --- a/src/actions/tasks.ts +++ b/src/actions/tasks.ts @@ -1,5 +1,5 @@ import req from "../axios"; -import { AxiosResponse, AxiosError } from "axios"; +import { AxiosResponse } from "axios"; import { Dispatch } from "redux"; import Actions from "./list"; @@ -35,87 +35,83 @@ const resolveUpdates = (merge: TaskMergeResult, dispatch: Dispatch) => { * deleted, updated, etc. * @param localTasks The local task list */ -export const refetchTasks = (localTasks: Task[]) => - function(dispatch: Dispatch) { - req.get("/task").then( - (res: AxiosResponse) => { - const remoteTasks = res.data["tasks"].map( - (x: TaskRecord): Task => taskFromRecord(x) - ) as Task[]; - const merge = mergeTasks(remoteTasks, localTasks); - resolveUpdates(merge, dispatch); - }, - (err: AxiosError) => { - dispatch({ - type: Actions.TasksFetchError, - } as TaskAction); - } - ); - }; +export const refetchTasks = (localTasks: Task[]) => (dispatch: Dispatch) => { + req.get("/task").then( + (res: AxiosResponse) => { + const remoteTasks = res.data["tasks"].map((x: TaskRecord) => + taskFromRecord(x) + ) as Task[]; + const merge = mergeTasks(remoteTasks, localTasks); + resolveUpdates(merge, dispatch); + }, + () => { + dispatch({ + type: Actions.TasksFetchError, + } as TaskAction); + } + ); +}; /** * Redux action creator. Sends a task creation request to the server, * fetches the ID it receives, and updates the task with that ID. */ -export const createTask = (task: Task) => - function(dispatch: Dispatch) { - req.post("/task", taskToRecord(task)).then( - (res: AxiosResponse) => { - const id: number = res.data["id"]; - task.ID = id; - dispatch({ - type: Actions.TaskCreate, - data: task, - } as TaskAction); - }, - (err: AxiosError) => { - dispatch({ - type: Actions.TaskCreateError, - data: task, - } as TaskAction); - } - ); - }; +export const createTask = (task: Task) => (dispatch: Dispatch) => { + req.post("/task", taskToRecord(task)).then( + (res: AxiosResponse) => { + const { id } = res.data; + task.ID = id; + dispatch({ + type: Actions.TaskCreate, + data: task, + } as TaskAction); + }, + () => { + dispatch({ + type: Actions.TaskCreateError, + data: task, + } as TaskAction); + } + ); +}; /** * Redux action creator. Sends a PATCH request to the server with the * new task details. */ -export const updateTask = (task: Task) => - function(dispatch: Dispatch) { - req.patch(`/task/${task.ID}`, taskToRecord(task)).then( - (res: AxiosResponse) => { - dispatch({ - type: Actions.TaskUpdate, - data: task, - } as TaskAction); - }, - (err: AxiosError) => { - dispatch({ - type: Actions.TaskUpdateError, - data: task, - } as TaskAction); - } - ); - }; +export const updateTask = (task: Task) => (dispatch: Dispatch) => { + req.patch(`/task/${task.ID}`, taskToRecord(task)).then( + () => { + dispatch({ + type: Actions.TaskUpdate, + data: task, + } as TaskAction); + }, + () => { + dispatch({ + type: Actions.TaskUpdateError, + data: task, + } as TaskAction); + } + ); +}; /** * Redux action creator. Sends a DELETE request to the server. */ -export const deleteTask = (task: Task) => - function(dispatch: Dispatch) { - req.delete(`/task/${task.ID}`).then( - (res: AxiosResponse) => { - dispatch({ - type: Actions.TaskDelete, - data: task, - } as TaskAction); - }, - (err: AxiosError) => { - dispatch({ - type: Actions.TaskDeleteError, - data: task, - }); - } - ); - }; +export const deleteTask = (task: Task) => (dispatch: Dispatch) => { + req.delete(`/task/${task.ID}`).then( + () => { + dispatch({ + type: Actions.TaskDelete, + data: task, + } as TaskAction); + }, + () => { + dispatch({ + type: Actions.TaskDeleteError, + data: task, + }); + } + ); +}; diff --git a/src/helpers/tasks.ts b/src/helpers/tasks.ts index 19f7dcc..38782b9 100644 --- a/src/helpers/tasks.ts +++ b/src/helpers/tasks.ts @@ -9,7 +9,7 @@ export const taskFromRecord = (task: TaskRecord): Task => { newTask.ID = task.id as number; newTask.PID = "parent_id" in task ? (task.parent_id as number) : 0; newTask.Text = "text" in task ? (task.text as string) : ""; - newTask.Completed = task.status !== 0 ? true : false; + newTask.Completed = task.status !== 0; newTask.LastMod = task.last_mod as number; return newTask; }; @@ -60,8 +60,12 @@ export const mergeTasks = ( if (local.LastMod > remote.LastMod) { result.push(local); toUpdate.push(local); - } else result.push(remote); - if (local.ToRemove) toDelete.push(local); + } else { + result.push(remote); + } + if (local.ToRemove) { + toDelete.push(local); + } } else { // matching local task not found, adding a new task to the store result.push(remote); @@ -80,19 +84,19 @@ export const mergeTasks = ( } else { result.push(remote); } - if (local.ToRemove) toDelete.push(local); - } else { - if (local.ToSync) { - result.push(local); - toSync.push(local); + if (local.ToRemove) { + toDelete.push(local); } + } else if (local.ToSync) { + result.push(local); + toSync.push(local); } } }); return { - result: result, - toDelete: toDelete, - toSync: toSync, - toUpdate: toUpdate, + result, + toDelete, + toSync, + toUpdate, }; }; diff --git a/src/main.tsx b/src/main.tsx index 3580e0e..3fd609d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -26,8 +26,11 @@ class App extends React.Component { username: this.props.username, } as Props; updateToken = () => { - if (this.props.token) setToken(this.props.token as string); - else resetToken(); + if (this.props.token) { + setToken(this.props.token as string); + } else { + resetToken(); + } }; UNSAFE_componentWillMount = () => this.updateToken(); componentDidUpdate = (prevProps: Props) => { diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 6431bc5..8a55078 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1 +1,2 @@ -/// +/* eslint-disable */ +/// diff --git a/src/serviceWorker.ts b/src/serviceWorker.ts index 61c68bd..825becd 100644 --- a/src/serviceWorker.ts +++ b/src/serviceWorker.ts @@ -1,3 +1,5 @@ +/* eslint-disable */ + // Boilerplate SW added by create-react-app. const isLocalhost = Boolean( diff --git a/src/store.ts b/src/store.ts index 2852601..12b3812 100644 --- a/src/store.ts +++ b/src/store.ts @@ -8,7 +8,7 @@ import rootReducer from "./reducers"; const persistConfig = { key: "root", - storage: storage, + storage, whitelist: ["auth", "task"], }; diff --git a/src/views/components/bulma/level.tsx b/src/views/components/bulma/level.tsx index fb2edcb..aabf499 100644 --- a/src/views/components/bulma/level.tsx +++ b/src/views/components/bulma/level.tsx @@ -6,11 +6,11 @@ import { BulmaLevelProps as BP } from "../../../typings/bulma"; * This is a just a drop-in replacement for manual markup. */ export const Level: React.FC & BP> = (props) => { const classes: string[] = []; - if (props.level) classes.push("level"); - if (props.levelItem) classes.push("level-item"); - if (props.levelLeft) classes.push("level-left"); - if (props.levelRight) classes.push("level-right"); - if (props.isMobile) classes.push("is-mobile"); + props.level && classes.push("level"); + props.levelItem && classes.push("level-item"); + props.levelLeft && classes.push("level-left"); + props.levelRight && classes.push("level-right"); + props.isMobile && classes.push("is-mobile"); return (
{props.children} diff --git a/src/views/components/taskLine.tsx b/src/views/components/taskLine.tsx index 34d2787..ca55c51 100644 --- a/src/views/components/taskLine.tsx +++ b/src/views/components/taskLine.tsx @@ -38,16 +38,13 @@ class TaskLine extends React.Component { render = () => { const { ID, Completed, ToRemove, Text } = this.state.task; let classNames: string[] = ["taskLine", "level", "is-mobile"]; - if (Completed) classNames.push("taskCompleted"); - if (ToRemove) classNames.push("taskToRemove"); + Completed && classNames.push("taskCompleted"); + ToRemove && classNames.push("taskToRemove"); return (
- - #{ID} - {" - "} - + {`#${ID} - `} {Text}
diff --git a/src/views/components/taskList.tsx b/src/views/components/taskList.tsx index c3e24c6..28cbec9 100644 --- a/src/views/components/taskList.tsx +++ b/src/views/components/taskList.tsx @@ -46,7 +46,9 @@ const TaskList: React.FC = (props) => { .toLocaleLowerCase() .includes(value.Text.toLocaleLowerCase()) ); - } else displayTasks = tasks.filter((task) => task.PID === 0); + } else { + displayTasks = tasks.filter((task) => task.PID === 0); + } return (
{displayTasks.map((task) => ( diff --git a/src/views/editorView.tsx b/src/views/editorView.tsx index 1a4c120..2433519 100644 --- a/src/views/editorView.tsx +++ b/src/views/editorView.tsx @@ -23,13 +23,13 @@ const mapStateToProps = (state: Store) => ({ tasks: state.task.tasks, }); -interface Params { - id: string; -} +/** Used to delay navigation before we are finished with stuff. */ +const waitTimeout = 200; + interface Props extends RouteComponentProps { tasks: Task[]; dispatch: ThunkDispatch; - match: Match; + match: Match<{ id: string }>; } interface State { task?: Task; @@ -51,19 +51,16 @@ class EditorView extends React.Component { // editing or creating a new task? if (this.props.match.params.id !== "new") { const id = parseInt(this.props.match.params.id); - const task = this.props.tasks.find( - (task) => task.ID === id - ) as Task; - this.setState(() => ({ - task: task, + const task = this.props.tasks.find((t) => t.ID === id) as Task; + this.setState({ + task, title: task.Text, - })); + }); } else { // create a new task let maxID = 0; - this.props.tasks.forEach( - (task) => task.ID > maxID && (maxID = task.ID) - ); + // find the biggest ID and use + this.props.tasks.forEach((t) => t.ID > maxID && (maxID = t.ID)); const newID = maxID + 1; const newTask = { ID: newID, @@ -82,7 +79,8 @@ class EditorView extends React.Component { componentWillUnmount = () => { document.removeEventListener("keydown", this.handleHotkeys); }; - handleHotkeys = (event: KeyboardEvent) => { + handleHotkeys = (e: KeyboardEvent) => { + const escCode = 27; const textInput = document.getElementById("taskTextInput"); const parentSelect = document.getElementById("taskParentSelect"); const statusBtn = document.getElementById("taskStatusButton"); @@ -90,20 +88,20 @@ class EditorView extends React.Component { document.activeElement === textInput || document.activeElement === parentSelect || document.activeElement === statusBtn; - if (event.key === "b" && !editFocused) { - event.preventDefault(); + if (e.key === "b" && !editFocused) { + e.preventDefault(); this.props.history.push("/"); - } else if (event.key === "s" && !editFocused) { - event.preventDefault(); + } else if (e.key === "s" && !editFocused) { + e.preventDefault(); this.saveChanges(); - } else if (event.key === "d" && !editFocused && !this.state.newTask) { - event.preventDefault(); + } else if (e.key === "d" && !editFocused && !this.state.newTask) { + e.preventDefault(); this.delete(); } else if ( - event.keyCode === 27 && + e.keyCode === escCode && document.activeElement === textInput ) { - event.preventDefault(); + e.preventDefault(); (textInput as HTMLElement).blur(); } }; @@ -111,23 +109,23 @@ class EditorView extends React.Component { this.props.dispatch(deleteTask(this.state.task)); setTimeout(() => { this.props.history.goBack(); - }, 200); + }, waitTimeout); }; - updateText = (event: React.FormEvent) => { + updateText = (e: React.FormEvent) => { const { task } = this.state; - task.Text = event.currentTarget.value; - this.setState(() => ({ task: task })); + task.Text = e.currentTarget.value; + this.setState(() => ({ task })); }; updateStatus = () => { const { task } = this.state; task.Completed = !task.Completed; - this.setState(() => ({ task: task })); + this.setState(() => ({ task })); }; - updateParent = (event: React.FormEvent) => { - const newPID = parseInt(event.currentTarget.value); + updateParent = (e: React.FormEvent) => { + const newPID = parseInt(e.currentTarget.value); const { task } = this.state; task.PID = newPID; - this.setState(() => ({ task: task })); + this.setState(() => ({ task })); }; saveChanges = () => { if (this.state.newTask) { @@ -139,7 +137,7 @@ class EditorView extends React.Component { // pushed stuff to the server setTimeout(() => { this.props.history.goBack(); - }, 200); + }, waitTimeout); }; render = () => { const { task, title } = this.state; @@ -177,9 +175,7 @@ class EditorView extends React.Component { - - #{task.ID} - {title} - + {`#${task.ID} - ${title}`} {/* another div, needed for border styling fixes */} diff --git a/src/views/loginForm.tsx b/src/views/loginForm.tsx index 9eeb947..89f9e37 100644 --- a/src/views/loginForm.tsx +++ b/src/views/loginForm.tsx @@ -40,8 +40,9 @@ class LoginForm extends React.PureComponent { loginFailed: this.props.loginFailed, } as State; componentDidUpdate = (prevProps: Props) => { - if (prevProps.loginFailed !== this.props.loginFailed) + if (prevProps.loginFailed !== this.props.loginFailed) { this.setState({ loginFailed: this.props.loginFailed as boolean }); + } }; updateUserName = (event: React.FormEvent) => this.setState({ username: event.currentTarget.value }); @@ -51,7 +52,7 @@ class LoginForm extends React.PureComponent { this.props.dispatch(login(this.state.username, this.state.password)); toSignup = () => this.props.history.push("/signup"); onKeyPress = (event: React.KeyboardEvent) => { - if (event.key === "Enter") this.login(); + event.key === "Enter" && this.login(); }; render = () => ( diff --git a/src/views/mainView.tsx b/src/views/mainView.tsx index f9808a5..101cbd2 100644 --- a/src/views/mainView.tsx +++ b/src/views/mainView.tsx @@ -50,9 +50,9 @@ class MainView extends React.Component { ); danglingTasks.forEach((task) => this.props.dispatch(deleteTask(task))); }; - updateSearch = (event: React.FormEvent) => + updateSearch = (e: React.FormEvent) => this.setState({ - search: event.currentTarget.value, + search: e.currentTarget.value, }); toNewTask = () => this.props.history.push("/task/new"); componentDidMount = () => { @@ -69,22 +69,22 @@ class MainView extends React.Component { }); } }; - handleHotkeys = (event: KeyboardEvent) => { + handleHotkeys = (e: KeyboardEvent) => { + const escCode = 27; const search = document.getElementById("searchInput"); const searchFocused = document.activeElement === search; - if ((event.ctrlKey || event.metaKey) && event.key === "f") { - event.preventDefault(); + if ((e.ctrlKey || e.metaKey) && e.key === "f") { + e.preventDefault(); (search as HTMLElement).focus(); - } else if (event.key === "n" && !searchFocused) { - event.preventDefault(); + } else if (e.key === "n" && !searchFocused) { + e.preventDefault(); this.toNewTask(); - } else if (event.key === "u" && !searchFocused) { - event.preventDefault(); + } else if (e.key === "u" && !searchFocused) { + e.preventDefault(); this.refetch(); - } else if (event.keyCode === 27 && searchFocused) { - // unfocus search on Esc press + } else if (e.keyCode === escCode && searchFocused) { (document.activeElement as HTMLElement).blur(); - event.preventDefault(); + e.preventDefault(); this.setState({ search: "", }); diff --git a/src/views/signupForm.tsx b/src/views/signupForm.tsx index c1be09e..b404784 100644 --- a/src/views/signupForm.tsx +++ b/src/views/signupForm.tsx @@ -16,6 +16,8 @@ import strings from "./assets/locales"; import "./styles/signupForm.scss"; +const successRedirectDelay = 5000; + enum Status { UNDEFINED, FAILED, @@ -44,7 +46,7 @@ class SignupForm extends React.PureComponent { if (this.state.status === Status.SUCCESS) { setTimeout(() => { this.props.history.push("/"); - }, 5000); + }, successRedirectDelay); } }; signup = () => {