From 986285402f68b24c3a71e65419d37dd6df617b5d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 9 Jul 2019 21:25:57 -0700 Subject: [PATCH] Switch to a context store for server stuff to better support things in the future --- .../scripts/api/server/getServerDatabases.ts | 31 ++++++++++ .../scripts/components/FlashMessageRender.tsx | 4 +- .../auth/ForgotPasswordContainer.tsx | 4 +- .../auth/LoginCheckpointContainer.tsx | 4 +- .../components/auth/LoginContainer.tsx | 4 +- .../auth/ResetPasswordContainer.tsx | 4 +- .../forms/UpdateEmailAddressForm.tsx | 8 +-- .../dashboard/forms/UpdatePasswordForm.tsx | 6 +- .../scripts/components/server/Console.tsx | 8 +-- .../components/server/ServerConsole.tsx | 5 +- .../components/server/ServerDatabases.tsx | 24 ++++++++ .../components/server/WebsocketHandler.tsx | 13 ++--- resources/scripts/routers/ServerRouter.tsx | 53 ++++++++++------- resources/scripts/state/flashes.ts | 28 +++++++++ resources/scripts/state/index.ts | 14 +++-- resources/scripts/state/models/flashes.ts | 14 ----- resources/scripts/state/models/server.ts | 43 -------------- resources/scripts/state/server/index.ts | 57 +++++++++++++++++++ .../state/{models => server}/socket.ts | 8 +-- resources/scripts/state/types.d.ts | 24 -------- resources/scripts/state/{models => }/user.ts | 10 ++-- 21 files changed, 218 insertions(+), 148 deletions(-) create mode 100644 resources/scripts/api/server/getServerDatabases.ts create mode 100644 resources/scripts/components/server/ServerDatabases.tsx create mode 100644 resources/scripts/state/flashes.ts delete mode 100644 resources/scripts/state/models/flashes.ts delete mode 100644 resources/scripts/state/models/server.ts create mode 100644 resources/scripts/state/server/index.ts rename resources/scripts/state/{models => server}/socket.ts (70%) delete mode 100644 resources/scripts/state/types.d.ts rename resources/scripts/state/{models => }/user.ts (80%) diff --git a/resources/scripts/api/server/getServerDatabases.ts b/resources/scripts/api/server/getServerDatabases.ts new file mode 100644 index 00000000..835964c2 --- /dev/null +++ b/resources/scripts/api/server/getServerDatabases.ts @@ -0,0 +1,31 @@ +import http from '@/api/http'; + +export interface ServerDatabase { + id: string; + name: string; + username: string; + connectionString: string; + allowConnectionsFrom: string; + password?: string; +} + +export const rawDataToServerDatabase = (data: any): ServerDatabase => ({ + id: data.id, + name: data.name, + username: data.username, + connectionString: `${data.host.address}:${data.host.port}`, + allowConnectionsFrom: data.connections_from, + password: data.relationships && data.relationships.password ? data.relationships.password.attributes.password : undefined, +}); + +export default (uuid: string, includePassword: boolean = true): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/databases`, { + params: includePassword ? { include: 'password' } : undefined, + }) + .then(response => resolve( + (response.data.data || []).map((item: any) => rawDataToServerDatabase(item.attributes)) + )) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/FlashMessageRender.tsx b/resources/scripts/components/FlashMessageRender.tsx index 5b9c51d7..adffd09d 100644 --- a/resources/scripts/components/FlashMessageRender.tsx +++ b/resources/scripts/components/FlashMessageRender.tsx @@ -1,7 +1,7 @@ import React from 'react'; import MessageBox from '@/components/MessageBox'; import { State, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; +import { ApplicationStore } from '@/state'; type Props = Readonly<{ byKey?: string; @@ -10,7 +10,7 @@ type Props = Readonly<{ }>; export default ({ withBottomSpace, spacerClass, byKey }: Props) => { - const flashes = useStoreState((state: State) => state.flashes.items); + const flashes = useStoreState((state: State) => state.flashes.items); let filtered = flashes; if (byKey) { diff --git a/resources/scripts/components/auth/ForgotPasswordContainer.tsx b/resources/scripts/components/auth/ForgotPasswordContainer.tsx index fbf0eb70..668875ca 100644 --- a/resources/scripts/components/auth/ForgotPasswordContainer.tsx +++ b/resources/scripts/components/auth/ForgotPasswordContainer.tsx @@ -4,14 +4,14 @@ import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import FlashMessageRender from '@/components/FlashMessageRender'; +import { ApplicationStore } from '@/state'; export default () => { const [ isSubmitting, setSubmitting ] = React.useState(false); const [ email, setEmail ] = React.useState(''); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); const handleFieldUpdate = (e: React.ChangeEvent) => setEmail(e.target.value); diff --git a/resources/scripts/components/auth/LoginCheckpointContainer.tsx b/resources/scripts/components/auth/LoginCheckpointContainer.tsx index ccc53e5b..54ac3ee4 100644 --- a/resources/scripts/components/auth/LoginCheckpointContainer.tsx +++ b/resources/scripts/components/auth/LoginCheckpointContainer.tsx @@ -4,15 +4,15 @@ import loginCheckpoint from '@/api/auth/loginCheckpoint'; import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import { StaticContext } from 'react-router'; import FlashMessageRender from '@/components/FlashMessageRender'; +import { ApplicationStore } from '@/state'; export default ({ history, location: { state } }: RouteComponentProps<{}, StaticContext, { token?: string }>) => { const [ code, setCode ] = useState(''); const [ isLoading, setIsLoading ] = useState(false); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); if (!state || !state.token) { history.replace('/auth/login'); diff --git a/resources/scripts/components/auth/LoginContainer.tsx b/resources/scripts/components/auth/LoginContainer.tsx index f29a8c59..95538d78 100644 --- a/resources/scripts/components/auth/LoginContainer.tsx +++ b/resources/scripts/components/auth/LoginContainer.tsx @@ -5,14 +5,14 @@ import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import FlashMessageRender from '@/components/FlashMessageRender'; import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; +import { ApplicationStore } from '@/state'; export default ({ history }: RouteComponentProps) => { const [ username, setUsername ] = useState(''); const [ password, setPassword ] = useState(''); const [ isLoading, setLoading ] = useState(false); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); const submit = (e: React.FormEvent) => { e.preventDefault(); diff --git a/resources/scripts/components/auth/ResetPasswordContainer.tsx b/resources/scripts/components/auth/ResetPasswordContainer.tsx index 9467bb92..6954ebd2 100644 --- a/resources/scripts/components/auth/ResetPasswordContainer.tsx +++ b/resources/scripts/components/auth/ResetPasswordContainer.tsx @@ -7,7 +7,7 @@ import { httpErrorToHuman } from '@/api/http'; import LoginFormContainer from '@/components/auth/LoginFormContainer'; import FlashMessageRender from '@/components/FlashMessageRender'; import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; +import { ApplicationStore } from '@/state'; type Props = Readonly & {}>; @@ -17,7 +17,7 @@ export default (props: Props) => { const [ password, setPassword ] = useState(''); const [ passwordConfirm, setPasswordConfirm ] = useState(''); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); const parsed = parse(props.location.search); if (email.length === 0 && parsed.email) { diff --git a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx index 4593908a..1ee88a9c 100644 --- a/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdateEmailAddressForm.tsx @@ -1,11 +1,11 @@ import React from 'react'; import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import { Form, Formik, FormikActions } from 'formik'; import * as Yup from 'yup'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import Field from '@/components/elements/Field'; import { httpErrorToHuman } from '@/api/http'; +import { ApplicationStore } from '@/state'; interface Values { email: string; @@ -18,10 +18,10 @@ const schema = Yup.object().shape({ }); export default () => { - const user = useStoreState((state: State) => state.user.data); - const updateEmail = useStoreActions((state: Actions) => state.user.updateUserEmail); + const user = useStoreState((state: State) => state.user.data); + const updateEmail = useStoreActions((state: Actions) => state.user.updateUserEmail); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); const submit = (values: Values, { resetForm, setSubmitting }: FormikActions) => { clearFlashes('account:email'); diff --git a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx index dce13e65..7c25c128 100644 --- a/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx +++ b/resources/scripts/components/dashboard/forms/UpdatePasswordForm.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import { Form, Formik, FormikActions } from 'formik'; import Field from '@/components/elements/Field'; import * as Yup from 'yup'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import updateAccountPassword from '@/api/account/updateAccountPassword'; import { httpErrorToHuman } from '@/api/http'; +import { ApplicationStore } from '@/state'; interface Values { current: string; @@ -23,8 +23,8 @@ const schema = Yup.object().shape({ }); export default () => { - const user = useStoreState((state: State) => state.user.data); - const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); + const user = useStoreState((state: State) => state.user.data); + const { clearFlashes, addFlash } = useStoreActions((actions: Actions) => actions.flashes); if (!user) { return null; diff --git a/resources/scripts/components/server/Console.tsx b/resources/scripts/components/server/Console.tsx index e21d4d07..66f0b76b 100644 --- a/resources/scripts/components/server/Console.tsx +++ b/resources/scripts/components/server/Console.tsx @@ -2,9 +2,9 @@ import React, { createRef } from 'react'; import { Terminal } from 'xterm'; import * as TerminalFit from 'xterm/lib/addons/fit/fit'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; -import { ApplicationState } from '@/state/types'; import { connect } from 'react-redux'; import { Websocket } from '@/plugins/Websocket'; +import { ServerStore } from '@/state/server'; const theme = { background: 'transparent', @@ -113,8 +113,8 @@ class Console extends React.PureComponent> { } export default connect( - (state: ApplicationState) => ({ - connected: state.server.socket.connected, - instance: state.server.socket.instance, + (state: ServerStore) => ({ + connected: state.socket.connected, + instance: state.socket.instance, }), )(Console); diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index 0264e0d6..19a8d9d1 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -1,10 +1,9 @@ import React from 'react'; import Console from '@/components/server/Console'; -import { State, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; +import { ServerContext } from '@/state/server'; export default () => { - const status = useStoreState((state: State) => state.server.status); + const status = ServerContext.useStoreState(state => state.status.value); return (
diff --git a/resources/scripts/components/server/ServerDatabases.tsx b/resources/scripts/components/server/ServerDatabases.tsx new file mode 100644 index 00000000..a0cf75aa --- /dev/null +++ b/resources/scripts/components/server/ServerDatabases.tsx @@ -0,0 +1,24 @@ +import React, { useEffect } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; +import getServerDatabases from '@/api/server/getServerDatabases'; +import { useStoreState } from 'easy-peasy'; + +export default () => { + useEffect(() => { + getServerDatabases('s'); + }, []); + + return ( +
+
+
+ +
+
+

sfgsfgd

+
+
+
+ ); +}; diff --git a/resources/scripts/components/server/WebsocketHandler.tsx b/resources/scripts/components/server/WebsocketHandler.tsx index 7f74a64e..a8d2f300 100644 --- a/resources/scripts/components/server/WebsocketHandler.tsx +++ b/resources/scripts/components/server/WebsocketHandler.tsx @@ -1,13 +1,12 @@ import React, { useEffect } from 'react'; -import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import { Websocket } from '@/plugins/Websocket'; +import { ServerContext } from '@/state/server'; export default () => { - const server = useStoreState((state: State) => state.server.data); - const instance = useStoreState((state: State) => state.server.socket.instance); - const setServerStatus = useStoreActions((actions: Actions) => actions.server.setServerStatus); - const { setInstance, setConnectionState } = useStoreActions((actions: Actions) => actions.server.socket); + const server = ServerContext.useStoreState(state => state.server.data); + const instance = ServerContext.useStoreState(state => state.socket.instance); + const setServerStatus = ServerContext.useStoreActions(actions => actions.status.setServerStatus); + const { setInstance, setConnectionState } = ServerContext.useStoreActions(actions => actions.socket); useEffect(() => { // If there is already an instance or there is no server, just exit out of this process @@ -20,7 +19,7 @@ export default () => { const socket = new Websocket( `wss://wings.pterodactyl.test:8080/api/servers/${server.uuid}/ws`, - 'CC8kHCuMkXPosgzGO6d37wvhNcksWxG6kTrA' + 'CC8kHCuMkXPosgzGO6d37wvhNcksWxG6kTrA', ); socket.on('SOCKET_OPEN', () => setConnectionState(true)); diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index 62f5ff8e..4e2708b8 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -3,14 +3,16 @@ import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom'; import NavigationBar from '@/components/NavigationBar'; import ServerConsole from '@/components/server/ServerConsole'; import TransitionRouter from '@/TransitionRouter'; -import { Actions, State, useStoreActions, useStoreState } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; import Spinner from '@/components/elements/Spinner'; import WebsocketHandler from '@/components/server/WebsocketHandler'; +import ServerDatabases from '@/components/server/ServerDatabases'; +import { ServerContext } from '@/state/server'; +import { Provider } from 'react-redux'; -export default ({ match, location }: RouteComponentProps<{ id: string }>) => { - const server = useStoreState((state: State) => state.server.data); - const { clearServerState, getServer } = useStoreActions((actions: Actions) => actions.server); +const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { + const server = ServerContext.useStoreState(state => state.server.data); + const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); + const clearServerState = ServerContext.useStoreActions(actions => actions.clearServerState); if (!server) { getServer(match.params.id); @@ -31,22 +33,31 @@ export default ({ match, location }: RouteComponentProps<{ id: string }>) => {
- -
- {!server ? -
- -
- : - - - - - - - } -
-
+ + +
+ {!server ? +
+ +
+ : + + + + + + + + } +
+
+
); }; + +export default (props: RouteComponentProps) => ( + + + +); diff --git a/resources/scripts/state/flashes.ts b/resources/scripts/state/flashes.ts new file mode 100644 index 00000000..666778a1 --- /dev/null +++ b/resources/scripts/state/flashes.ts @@ -0,0 +1,28 @@ +import { Action, action } from 'easy-peasy'; +import { FlashMessageType } from '@/components/MessageBox'; + +export interface FlashStore { + items: FlashMessage[]; + addFlash: Action; + clearFlashes: Action; +} + +export interface FlashMessage { + id?: string; + key?: string; + type: FlashMessageType; + title?: string; + message: string; +} + +const flashes: FlashStore = { + items: [], + addFlash: action((state, payload) => { + state.items.push(payload); + }), + clearFlashes: action((state, payload) => { + state.items = payload ? state.items.filter(flashes => flashes.key !== payload) : []; + }), +}; + +export default flashes; diff --git a/resources/scripts/state/index.ts b/resources/scripts/state/index.ts index 5e19365f..db3181aa 100644 --- a/resources/scripts/state/index.ts +++ b/resources/scripts/state/index.ts @@ -1,13 +1,15 @@ import { createStore } from 'easy-peasy'; -import { ApplicationState } from '@/state/types'; -import flashes from '@/state/models/flashes'; -import user from '@/state/models/user'; -import server from '@/state/models/server'; +import flashes, { FlashStore } from '@/state/flashes'; +import user, { UserStore } from '@/state/user'; -const state: ApplicationState = { +export interface ApplicationStore { + flashes: FlashStore; + user: UserStore; +} + +const state: ApplicationStore = { flashes, user, - server, }; export const store = createStore(state); diff --git a/resources/scripts/state/models/flashes.ts b/resources/scripts/state/models/flashes.ts deleted file mode 100644 index 97c9f080..00000000 --- a/resources/scripts/state/models/flashes.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { action } from 'easy-peasy'; -import { FlashState } from '@/state/types'; - -const flashes: FlashState = { - items: [], - addFlash: action((state, payload) => { - state.items.push(payload); - }), - clearFlashes: action((state, payload) => { - state.items = payload ? state.items.filter(flashes => flashes.key !== payload) : []; - }), -}; - -export default flashes; diff --git a/resources/scripts/state/models/server.ts b/resources/scripts/state/models/server.ts deleted file mode 100644 index c7b7942d..00000000 --- a/resources/scripts/state/models/server.ts +++ /dev/null @@ -1,43 +0,0 @@ -import getServer, { Server } from '@/api/server/getServer'; -import { action, Action, thunk, Thunk } from 'easy-peasy'; -import socket, { SocketState } from './socket'; - -export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running'; - -export interface ServerState { - data?: Server; - status: ServerStatus; - socket: SocketState; - getServer: Thunk>; - setServer: Action; - setServerStatus: Action; - clearServerState: Action; -} - -const server: ServerState = { - socket, - status: 'offline', - getServer: thunk(async (actions, payload) => { - const server = await getServer(payload); - actions.setServer(server); - }), - setServer: action((state, payload) => { - state.data = payload; - }), - setServerStatus: action((state, payload) => { - state.status = payload; - }), - clearServerState: action(state => { - state.data = undefined; - - if (state.socket.instance) { - state.socket.instance.removeAllListeners(); - state.socket.instance.close(); - } - - state.socket.instance = null; - state.socket.connected = false; - }), -}; - -export default server; diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts new file mode 100644 index 00000000..c29e61bf --- /dev/null +++ b/resources/scripts/state/server/index.ts @@ -0,0 +1,57 @@ +import getServer, { Server } from '@/api/server/getServer'; +import { action, Action, createContextStore, thunk, Thunk } from 'easy-peasy'; +import socket, { SocketStore } from './socket'; + +export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running'; + +interface ServerDataStore { + data?: Server; + getServer: Thunk>; + setServer: Action; +} + +const server: ServerDataStore = { + getServer: thunk(async (actions, payload) => { + const server = await getServer(payload); + actions.setServer(server); + }), + setServer: action((state, payload) => { + state.data = payload; + }), +}; + +interface ServerStatusStore { + value: ServerStatus; + setServerStatus: Action; +} + +const status: ServerStatusStore = { + value: 'offline', + setServerStatus: action((state, payload) => { + state.value = payload; + }), +}; + +export interface ServerStore { + server: ServerDataStore; + socket: SocketStore; + status: ServerStatusStore; + clearServerState: Action; +} + +export const ServerContext = createContextStore({ + server, + socket, + status, + clearServerState: action(state => { + state.server.data = undefined; + + if (state.socket.instance) { + state.socket.instance.removeAllListeners(); + state.socket.instance.close(); + } + + state.socket.instance = null; + state.socket.connected = false; + }), +}, { name: 'ServerStore' }); diff --git a/resources/scripts/state/models/socket.ts b/resources/scripts/state/server/socket.ts similarity index 70% rename from resources/scripts/state/models/socket.ts rename to resources/scripts/state/server/socket.ts index 10922fed..e6791066 100644 --- a/resources/scripts/state/models/socket.ts +++ b/resources/scripts/state/server/socket.ts @@ -1,14 +1,14 @@ import { Action, action } from 'easy-peasy'; import { Websocket } from '@/plugins/Websocket'; -export interface SocketState { +export interface SocketStore { instance: Websocket | null; connected: boolean; - setInstance: Action; - setConnectionState: Action; + setInstance: Action; + setConnectionState: Action; } -const socket: SocketState = { +const socket: SocketStore = { instance: null, connected: false, setInstance: action((state, payload) => { diff --git a/resources/scripts/state/types.d.ts b/resources/scripts/state/types.d.ts deleted file mode 100644 index d54afc29..00000000 --- a/resources/scripts/state/types.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { FlashMessageType } from '@/components/MessageBox'; -import { Action } from 'easy-peasy'; -import { UserState } from '@/state/models/user'; -import { ServerState } from '@/state/models/server'; - -export interface ApplicationState { - flashes: FlashState; - user: UserState; - server: ServerState; -} - -export interface FlashState { - items: FlashMessage[]; - addFlash: Action; - clearFlashes: Action; -} - -export interface FlashMessage { - id?: string; - key?: string; - type: FlashMessageType; - title?: string; - message: string; -} diff --git a/resources/scripts/state/models/user.ts b/resources/scripts/state/user.ts similarity index 80% rename from resources/scripts/state/models/user.ts rename to resources/scripts/state/user.ts index c7da19fb..087bfe00 100644 --- a/resources/scripts/state/models/user.ts +++ b/resources/scripts/state/user.ts @@ -12,14 +12,14 @@ export interface UserData { updatedAt: Date; } -export interface UserState { +export interface UserStore { data?: UserData; - setUserData: Action; - updateUserData: Action>; - updateUserEmail: Thunk>; + setUserData: Action; + updateUserData: Action>; + updateUserEmail: Thunk>; } -const user: UserState = { +const user: UserStore = { data: undefined, setUserData: action((state, payload) => { state.data = payload;