diff --git a/package.json b/package.json index 34063c6..50344b2 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fredy", - "version": "12.0.2", + "version": "12.1.0", "description": "[F]ind [R]eal [E]states [d]amn eas[y].", "scripts": { "prepare": "husky", @@ -57,8 +57,6 @@ ], "dependencies": { "@douyinfe/semi-ui": "2.86.0", - "@rematch/core": "2.2.0", - "@rematch/loading": "2.1.2", "@sendgrid/mail": "8.1.5", "@visactor/react-vchart": "^2.0.4", "@visactor/vchart": "^2.0.4", @@ -80,19 +78,17 @@ "puppeteer": "^24.22.0", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-stealth": "^2.11.2", - "query-string": "9.3.0", + "query-string": "9.3.1", "react": "18.3.1", "react-dom": "18.3.1", - "react-redux": "9.2.0", "react-router": "7.9.1", "react-router-dom": "7.9.1", - "redux": "5.0.1", - "redux-thunk": "3.1.0", "restana": "5.1.0", "serve-static": "2.2.0", "slack": "11.0.2", "vite": "7.1.6", - "x-var": "^3.0.1" + "x-var": "^3.0.1", + "zustand": "^5.0.8" }, "devDependencies": { "@babel/core": "7.28.4", @@ -110,7 +106,6 @@ "lint-staged": "16.1.6", "mocha": "11.7.2", "nodemon": "^3.1.10", - "prettier": "3.6.2", - "redux-logger": "3.0.6" + "prettier": "3.6.2" } } diff --git a/ui/src/App.jsx b/ui/src/App.jsx index 919fd7b..24d0162 100644 --- a/ui/src/App.jsx +++ b/ui/src/App.jsx @@ -6,7 +6,7 @@ import GeneralSettings from './views/generalSettings/GeneralSettings'; import JobMutation from './views/jobs/mutation/JobMutation'; import UserMutator from './views/user/mutation/UserMutator'; import JobInsight from './views/jobs/insights/JobInsight.jsx'; -import { useDispatch, useSelector } from 'react-redux'; +import { useActions, useSelector } from './services/state/store'; import { Routes, Route, Navigate } from 'react-router-dom'; import Logout from './components/logout/Logout'; import Logo from './components/logo/Logo'; @@ -20,20 +20,20 @@ import TrackingModal from './components/tracking/TrackingModal.jsx'; import { Banner } from '@douyinfe/semi-ui'; export default function FredyApp() { - const dispatch = useDispatch(); + const actions = useActions(); const [loading, setLoading] = React.useState(true); const currentUser = useSelector((state) => state.user.currentUser); const settings = useSelector((state) => state.generalSettings.settings); useEffect(() => { async function init() { - await dispatch.user.getCurrentUser(); + await actions.user.getCurrentUser(); if (!needsLogin()) { - await dispatch.provider.getProvider(); - await dispatch.jobs.getJobs(); - await dispatch.jobs.getProcessingTimes(); - await dispatch.notificationAdapter.getAdapter(); - await dispatch.generalSettings.getGeneralSettings(); + await actions.provider.getProvider(); + await actions.jobs.getJobs(); + await actions.jobs.getProcessingTimes(); + await actions.notificationAdapter.getAdapter(); + await actions.generalSettings.getGeneralSettings(); } setLoading(false); } diff --git a/ui/src/Index.jsx b/ui/src/Index.jsx index e8bc374..2cfb723 100644 --- a/ui/src/Index.jsx +++ b/ui/src/Index.jsx @@ -1,8 +1,6 @@ import React from 'react'; -import { reduxStore } from './services/rematch/store'; import { HashRouter } from 'react-router-dom'; -import { Provider } from 'react-redux'; import { createRoot } from 'react-dom/client'; import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US'; import { LocaleProvider } from '@douyinfe/semi-ui'; @@ -18,11 +16,9 @@ initVChartSemiTheme({ }); root.render( - - - - - - - , + + + + + , ); diff --git a/ui/src/services/rematch/models/demoMode.js b/ui/src/services/rematch/models/demoMode.js deleted file mode 100644 index d25276e..0000000 --- a/ui/src/services/rematch/models/demoMode.js +++ /dev/null @@ -1,24 +0,0 @@ -import { xhrGet } from '../../xhr'; -export const demoMode = { - state: { - demoMode: false, - }, - reducers: { - setDemoMode: (state, payload) => { - return { - ...state, - demoMode: payload.demoMode, - }; - }, - }, - effects: { - async getDemoMode() { - try { - const response = await xhrGet('/api/demo'); - this.setDemoMode(response.json); - } catch (Exception) { - console.error('Error while trying to get resource for api/demo. Error:', Exception); - } - }, - }, -}; diff --git a/ui/src/services/rematch/models/generalSettings.js b/ui/src/services/rematch/models/generalSettings.js deleted file mode 100644 index 5b3ef63..0000000 --- a/ui/src/services/rematch/models/generalSettings.js +++ /dev/null @@ -1,25 +0,0 @@ -import { xhrGet } from '../../xhr'; -export const generalSettings = { - state: { - settings: {}, - }, - reducers: { - //only admins - setGeneralSettings: (state, payload) => { - return { - ...state, - settings: payload, - }; - }, - }, - effects: { - async getGeneralSettings() { - try { - const response = await xhrGet('/api/admin/generalSettings'); - this.setGeneralSettings(response.json); - } catch (Exception) { - console.error('Error while trying to get resource for api/admin/generalSettings. Error:', Exception); - } - }, - }, -}; diff --git a/ui/src/services/rematch/models/jobs.js b/ui/src/services/rematch/models/jobs.js deleted file mode 100644 index bc3deed..0000000 --- a/ui/src/services/rematch/models/jobs.js +++ /dev/null @@ -1,57 +0,0 @@ -import { xhrGet } from '../../xhr'; -export const jobs = { - state: { - jobs: [], - insights: {}, - processingTimes: {}, - }, - reducers: { - setJobs: (state, payload) => { - return { - ...state, - jobs: Object.freeze(payload), - }; - }, - setProcessingTimes: (state, payload) => { - return { - ...state, - processingTimes: Object.freeze(payload), - }; - }, - setJobInsights: (state, payload, jobId) => { - return { - ...state, - insights: { - ...state.insights, - [jobId]: Object.freeze(payload), - }, - }; - }, - }, - effects: { - async getJobs() { - try { - const response = await xhrGet('/api/jobs'); - this.setJobs(response.json); - } catch (Exception) { - console.error(`Error while trying to get resource for api/jobs. Error:`, Exception); - } - }, - async getProcessingTimes() { - try { - const response = await xhrGet('/api/jobs/processingTimes'); - this.setProcessingTimes(response.json); - } catch (Exception) { - console.error(`Error while trying to get resource for api/processingTimes. Error:`, Exception); - } - }, - async getInsightDataForJob(jobId) { - try { - const response = await xhrGet(`/api/jobs/insights/${jobId}`); - this.setJobInsights(response.json, jobId); - } catch (Exception) { - console.error(`Error while trying to get resource for api/jobs/insights. Error:`, Exception); - } - }, - }, -}; diff --git a/ui/src/services/rematch/models/notificationAdapter.js b/ui/src/services/rematch/models/notificationAdapter.js deleted file mode 100644 index 4987bab..0000000 --- a/ui/src/services/rematch/models/notificationAdapter.js +++ /dev/null @@ -1,19 +0,0 @@ -import { xhrGet } from '../../xhr'; -export const notificationAdapter = { - state: [], - reducers: { - setAdapter: (state, payload) => { - return [...Object.freeze(payload)]; - }, - }, - effects: { - async getAdapter() { - try { - const response = await xhrGet('/api/jobs/notificationAdapter'); - this.setAdapter(response.json); - } catch (Exception) { - console.error(`Error while trying to get resource for api/jobs/notificationAdapter. Error:`, Exception); - } - }, - }, -}; diff --git a/ui/src/services/rematch/models/provider.js b/ui/src/services/rematch/models/provider.js deleted file mode 100644 index da9396b..0000000 --- a/ui/src/services/rematch/models/provider.js +++ /dev/null @@ -1,19 +0,0 @@ -import { xhrGet } from '../../xhr'; -export const provider = { - state: [], - reducers: { - setProvider: (state, payload) => { - return [...Object.freeze(payload)]; - }, - }, - effects: { - async getProvider() { - try { - const response = await xhrGet('/api/jobs/provider'); - this.setProvider(response.json); - } catch (Exception) { - console.error(`Error while trying to get resource for api/jobs/provider. Error:`, Exception); - } - }, - }, -}; diff --git a/ui/src/services/rematch/models/user.js b/ui/src/services/rematch/models/user.js deleted file mode 100644 index b23c333..0000000 --- a/ui/src/services/rematch/models/user.js +++ /dev/null @@ -1,40 +0,0 @@ -import { xhrGet } from '../../xhr'; -export const user = { - state: { - users: [], - currentUser: null, - }, - reducers: { - //only admins - setUsers: (state, payload) => { - return { - ...state, - users: payload, - }; - }, - setCurrentUser: (state, payload) => { - return { - ...state, - currentUser: Object.freeze(payload), - }; - }, - }, - effects: { - async getUsers() { - try { - const response = await xhrGet('/api/admin/users'); - this.setUsers(response.json); - } catch (Exception) { - console.error('Error while trying to get resource for api/admin/users. Error:', Exception); - } - }, - async getCurrentUser() { - try { - const response = await xhrGet('/api/login/user'); - this.setCurrentUser(response.json); - } catch (Exception) { - console.error('Error while trying to get resource for api/login/user. Error:', Exception); - } - }, - }, -}; diff --git a/ui/src/services/rematch/store.js b/ui/src/services/rematch/store.js deleted file mode 100644 index 9be3aea..0000000 --- a/ui/src/services/rematch/store.js +++ /dev/null @@ -1,29 +0,0 @@ -import { notificationAdapter } from './models/notificationAdapter'; -import { generalSettings } from './models/generalSettings'; -import createLoadingPlugin from '@rematch/loading'; -import { provider } from './models/provider'; -import { createLogger } from 'redux-logger'; -import { jobs } from './models/jobs'; -import { user } from './models/user'; -import { demoMode } from './models/demoMode.js'; -import { init } from '@rematch/core'; -const middleware = []; -if (process.env.NODE_ENV === 'development') { - middleware.push(createLogger({ duration: false, collapsed: (getState, action, logEntry) => !logEntry.error })); -} -const store = init({ - name: 'fredy', - models: { - notificationAdapter, - generalSettings, - demoMode, - provider, - jobs, - user, - }, - plugins: [createLoadingPlugin({})], - redux: { - middlewares: middleware, - }, -}); -export const reduxStore = store; diff --git a/ui/src/services/state/store.js b/ui/src/services/state/store.js new file mode 100644 index 0000000..9757857 --- /dev/null +++ b/ui/src/services/state/store.js @@ -0,0 +1,171 @@ +/** + * Zustand store for Fredy ui state. + */ +import { create } from 'zustand'; +import { shallow } from 'zustand/shallow'; +import { xhrGet } from '../xhr.js'; + +const logger = (config) => (set, get, api) => + config( + (partial, replace) => { + const prev = get(); + set(partial, replace); + const next = get(); + if (process.env.NODE_ENV !== 'production') { + /* eslint-disable no-console */ + console.info('[zustand] state changed:', { prev, next }); + /* eslint-enable no-console */ + } + }, + get, + api, + ); + +// Create the Zustand store with slices and actions +export const useFredyState = create( + logger( + (set) => { + // Async actions that directly set state (no separate reducer concept) + const effects = { + notificationAdapter: { + async getAdapter() { + try { + const response = await xhrGet('/api/jobs/notificationAdapter'); + set(() => ({ notificationAdapter: Object.freeze([...response.json]) })); + } catch (Exception) { + console.error(`Error while trying to get resource for api/jobs/notificationAdapter. Error:`, Exception); + } + }, + }, + generalSettings: { + async getGeneralSettings() { + try { + const response = await xhrGet('/api/admin/generalSettings'); + set((state) => ({ generalSettings: { ...state.generalSettings, settings: response.json } })); + } catch (Exception) { + console.error('Error while trying to get resource for api/admin/generalSettings. Error:', Exception); + } + }, + }, + provider: { + async getProvider() { + try { + const response = await xhrGet('/api/jobs/provider'); + set(() => ({ provider: Object.freeze([...response.json]) })); + } catch (Exception) { + console.error(`Error while trying to get resource for api/jobs/provider. Error:`, Exception); + } + }, + }, + jobs: { + async getJobs() { + try { + const response = await xhrGet('/api/jobs'); + set((state) => ({ jobs: { ...state.jobs, jobs: Object.freeze(response.json) } })); + } catch (Exception) { + console.error(`Error while trying to get resource for api/jobs. Error:`, Exception); + } + }, + async getProcessingTimes() { + try { + const response = await xhrGet('/api/jobs/processingTimes'); + set((state) => ({ jobs: { ...state.jobs, processingTimes: Object.freeze(response.json) } })); + } catch (Exception) { + console.error(`Error while trying to get resource for api/processingTimes. Error:`, Exception); + } + }, + async getInsightDataForJob(jobId) { + try { + const response = await xhrGet(`/api/jobs/insights/${jobId}`); + set((state) => ({ + jobs: { + ...state.jobs, + insights: { ...state.jobs.insights, [jobId]: Object.freeze(response.json) }, + }, + })); + } catch (Exception) { + console.error(`Error while trying to get resource for api/jobs/insights. Error:`, Exception); + } + }, + }, + user: { + async getUsers() { + try { + const response = await xhrGet('/api/admin/users'); + set((state) => ({ user: { ...state.user, users: response.json } })); + } catch (Exception) { + console.error('Error while trying to get resource for api/admin/users. Error:', Exception); + } + }, + async getCurrentUser() { + try { + const response = await xhrGet('/api/login/user'); + set((state) => ({ user: { ...state.user, currentUser: Object.freeze(response.json) } })); + } catch (Exception) { + console.error('Error while trying to get resource for api/login/user. Error:', Exception); + } + }, + }, + demoMode: { + async getDemoMode() { + try { + const response = await xhrGet('/api/demo'); + set((state) => ({ + demoMode: { ...state.demoMode, demoMode: response.json.demoMode }, + })); + } catch (Exception) { + console.error('Error while trying to get resource for api/demo. Error:', Exception); + } + }, + }, + }; + + // Initial state + const initial = { + notificationAdapter: [], + generalSettings: { settings: {} }, + demoMode: { demoMode: false }, + provider: [], + jobs: { jobs: [], insights: {}, processingTimes: {} }, + user: { users: [], currentUser: null }, + }; + + // Expose actions by grouping them per slice + const actions = { + notificationAdapter: { ...effects.notificationAdapter }, + generalSettings: { ...effects.generalSettings }, + demoMode: { ...effects.demoMode }, + provider: { ...effects.provider }, + jobs: { ...effects.jobs }, + user: { ...effects.user }, + }; + + return { + ...initial, + __actions: { actions }, + }; + }, + { name: 'fredy' }, + ), +); + +/** + * Selector hook, drop-in replacement for react-redux useSelector. + * Pass a selector function and optional equality function. Defaults to shallow comparison. + * @template T + * @param {(state: FredyState) => T} selector + * @param {(a: T, b: T) => boolean} [equalityFn] + * @returns {T} + */ +export function useSelector(selector, equalityFn = shallow) { + return useFredyState(selector, equalityFn); +} + +/** + * Actions hook returning grouped async actions per slice. + * Example: const { jobs } = useActions(); await jobs.getJobs(); + * @returns {{notificationAdapter: any, generalSettings: any, demoMode: any, provider: any, jobs: any, user: any}} + */ +export function useActions() { + return useFredyState((s) => s.__actions.actions); +} diff --git a/ui/src/views/generalSettings/GeneralSettings.jsx b/ui/src/views/generalSettings/GeneralSettings.jsx index 3418a08..835cde4 100644 --- a/ui/src/views/generalSettings/GeneralSettings.jsx +++ b/ui/src/views/generalSettings/GeneralSettings.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useActions, useSelector } from '../../services/state/store'; import { Divider, TimePicker, Button, Checkbox, Input } from '@douyinfe/semi-ui'; import { InputNumber } from '@douyinfe/semi-ui'; @@ -36,7 +36,7 @@ function formatFromTBackend(time) { } const GeneralSettings = function GeneralSettings() { - const dispatch = useDispatch(); + const actions = useActions(); const [loading, setLoading] = React.useState(true); const settings = useSelector((state) => state.generalSettings.settings); @@ -51,7 +51,7 @@ const GeneralSettings = function GeneralSettings() { React.useEffect(() => { async function init() { - await dispatch.generalSettings.getGeneralSettings(); + await actions.generalSettings.getGeneralSettings(); setLoading(false); } diff --git a/ui/src/views/jobs/Jobs.jsx b/ui/src/views/jobs/Jobs.jsx index fe7e927..002c9c4 100644 --- a/ui/src/views/jobs/Jobs.jsx +++ b/ui/src/views/jobs/Jobs.jsx @@ -1,7 +1,7 @@ import React from 'react'; import JobTable from '../../components/table/JobTable'; -import { useSelector, useDispatch } from 'react-redux'; +import { useSelector, useActions } from '../../services/state/store'; import { xhrDelete, xhrPut } from '../../services/xhr'; import { useNavigate } from 'react-router-dom'; import ProcessingTimes from './ProcessingTimes'; @@ -13,13 +13,13 @@ export default function Jobs() { const jobs = useSelector((state) => state.jobs.jobs); const processingTimes = useSelector((state) => state.jobs.processingTimes); const navigate = useNavigate(); - const dispatch = useDispatch(); + const actions = useActions(); const onJobRemoval = async (jobId) => { try { await xhrDelete('/api/jobs', { jobId }); Toast.success('Job successfully remove'); - await dispatch.jobs.getJobs(); + await actions.jobs.getJobs(); } catch (error) { Toast.error(error); } @@ -29,7 +29,7 @@ export default function Jobs() { try { await xhrPut(`/api/jobs/${jobId}/status`, { status }); Toast.success('Job status successfully changed'); - await dispatch.jobs.getJobs(); + await actions.jobs.getJobs(); } catch (error) { Toast.error(error); } diff --git a/ui/src/views/jobs/insights/JobInsight.jsx b/ui/src/views/jobs/insights/JobInsight.jsx index 507cc85..51ea715 100644 --- a/ui/src/views/jobs/insights/JobInsight.jsx +++ b/ui/src/views/jobs/insights/JobInsight.jsx @@ -2,20 +2,20 @@ import React from 'react'; import { roundToHour } from '../../../services/time/timeService'; import Headline from '../../../components/headline/Headline'; -import { useDispatch, useSelector } from 'react-redux'; +import { useActions, useSelector } from '../../../services/state/store'; import { useParams } from 'react-router-dom'; import Linechart from './Linechart'; const JobInsight = function JobInsight() { - const dispatch = useDispatch(); + const actions = useActions(); const insights = useSelector((state) => state.jobs.insights); const jobs = useSelector((state) => state.jobs.jobs); const params = useParams(); React.useEffect(() => { - dispatch.jobs.getInsightDataForJob(params.jobId); - dispatch.jobs.getJobs(); + actions.jobs.getInsightDataForJob(params.jobId); + actions.jobs.getJobs(); }, []); const getData = () => { diff --git a/ui/src/views/jobs/mutation/JobMutation.jsx b/ui/src/views/jobs/mutation/JobMutation.jsx index d0172de..9ea32cf 100644 --- a/ui/src/views/jobs/mutation/JobMutation.jsx +++ b/ui/src/views/jobs/mutation/JobMutation.jsx @@ -5,7 +5,7 @@ import NotificationAdapterTable from '../../../components/table/NotificationAdap import ProviderTable from '../../../components/table/ProviderTable'; import ProviderMutator from './components/provider/ProviderMutator'; import Headline from '../../../components/headline/Headline'; -import { useDispatch, useSelector } from 'react-redux'; +import { useActions, useSelector } from '../../../services/state/store'; import { xhrPost } from '../../../services/xhr'; import { useNavigate, useParams } from 'react-router-dom'; import { Divider, Input, Switch, Button, TagInput, Toast } from '@douyinfe/semi-ui'; @@ -34,7 +34,7 @@ export default function JobMutator() { const [notificationAdapterData, setNotificationAdapterData] = useState(defaultNotificationAdapter); const [enabled, setEnabled] = useState(defaultEnabled); const navigate = useNavigate(); - const dispatch = useDispatch(); + const actions = useActions(); const isSavingEnabled = () => { return Boolean(notificationAdapterData.length && providerData.length && name); @@ -50,7 +50,7 @@ export default function JobMutator() { enabled, jobId: jobToBeEdit?.id || null, }); - await dispatch.jobs.getJobs(); + await actions.jobs.getJobs(); Toast.success('Job successfully saved...'); navigate('/jobs'); } catch (Exception) { diff --git a/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx b/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx index ae43d71..3687a8e 100644 --- a/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx +++ b/ui/src/views/jobs/mutation/components/notificationAdapter/NotificationAdapterMutator.jsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import { transform } from '../../../../../services/transformer/notificationAdapterTransformer'; import { xhrPost } from '../../../../../services/xhr'; import Help from './NotificationHelpDisplay'; -import { useSelector } from 'react-redux'; +import { useSelector } from '../../../../../services/state/store'; import { Banner, Button, Form, Modal, Select, Switch } from '@douyinfe/semi-ui'; import './NotificationAdapterMutator.less'; diff --git a/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx b/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx index e7865b2..710b216 100644 --- a/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx +++ b/ui/src/views/jobs/mutation/components/provider/ProviderMutator.jsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Banner, Modal, Select, Input } from '@douyinfe/semi-ui'; import { transform } from '../../../../../services/transformer/providerTransformer'; -import { useSelector } from 'react-redux'; +import { useSelector } from '../../../../../services/state/store'; import { IconLikeHeart } from '@douyinfe/semi-icons'; import './ProviderMutator.less'; diff --git a/ui/src/views/login/Login.jsx b/ui/src/views/login/Login.jsx index 4e22a23..79abe69 100644 --- a/ui/src/views/login/Login.jsx +++ b/ui/src/views/login/Login.jsx @@ -4,14 +4,14 @@ import cityBackground from '../../assets/city_background.jpg'; import Logo from '../../components/logo/Logo'; import { xhrPost } from '../../services/xhr'; import { useNavigate } from 'react-router-dom'; -import { useDispatch, useSelector } from 'react-redux'; +import { useActions, useSelector } from '../../services/state/store'; import { Input, Button, Banner, Toast } from '@douyinfe/semi-ui'; import './login.less'; import { IconUser, IconLock } from '@douyinfe/semi-icons'; export default function Login() { - const dispatch = useDispatch(); + const actions = useActions(); const [username, setUserName] = React.useState(''); const [password, setPassword] = React.useState(''); const [error, setError] = React.useState(null); @@ -20,7 +20,7 @@ export default function Login() { useEffect(() => { async function init() { - await dispatch.demoMode.getDemoMode(); + await actions.demoMode.getDemoMode(); } init(); @@ -46,7 +46,7 @@ export default function Login() { Toast.success('Login successful!'); - await dispatch.user.getCurrentUser(); + await actions.user.getCurrentUser(); navigate('/jobs'); }; diff --git a/ui/src/views/user/Users.jsx b/ui/src/views/user/Users.jsx index 435d8fc..7ac3485 100644 --- a/ui/src/views/user/Users.jsx +++ b/ui/src/views/user/Users.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { Toast } from '@douyinfe/semi-ui'; import UserTable from '../../components/table/UserTable'; -import { useDispatch, useSelector } from 'react-redux'; +import { useActions, useSelector } from '../../services/state/store'; import { IconPlus } from '@douyinfe/semi-icons'; import { Button } from '@douyinfe/semi-ui'; import UserRemovalModal from './UserRemovalModal'; @@ -12,7 +12,7 @@ import { useNavigate } from 'react-router-dom'; import './Users.less'; const Users = function Users() { - const dispatch = useDispatch(); + const actions = useActions(); const [loading, setLoading] = React.useState(true); const users = useSelector((state) => state.user.users); const [userIdToBeRemoved, setUserIdToBeRemoved] = React.useState(null); @@ -20,7 +20,7 @@ const Users = function Users() { React.useEffect(() => { async function init() { - await dispatch.user.getUsers(); + await actions.user.getUsers(); setLoading(false); } @@ -32,8 +32,8 @@ const Users = function Users() { await xhrDelete('/api/admin/users', { userId: userIdToBeRemoved }); Toast.success('User successfully remove'); setUserIdToBeRemoved(null); - await dispatch.jobs.getJobs(); - await dispatch.user.getUsers(); + await actions.jobs.getJobs(); + await actions.user.getUsers(); } catch (error) { Toast.error(error); setUserIdToBeRemoved(null); diff --git a/ui/src/views/user/mutation/UserMutator.jsx b/ui/src/views/user/mutation/UserMutator.jsx index befd2ab..68c0383 100644 --- a/ui/src/views/user/mutation/UserMutator.jsx +++ b/ui/src/views/user/mutation/UserMutator.jsx @@ -2,7 +2,7 @@ import React from 'react'; import { xhrGet, xhrPost } from '../../../services/xhr'; import { useNavigate, useParams } from 'react-router-dom'; -import { useDispatch } from 'react-redux'; +import { useActions } from '../../../services/state/store'; import { Divider, Input, Switch, Button, Toast } from '@douyinfe/semi-ui'; import './UserMutator.less'; import { SegmentPart } from '../../../components/segment/SegmentPart'; @@ -16,7 +16,7 @@ const UserMutator = function UserMutator() { const [isAdmin, setIsAdmin] = React.useState(false); const navigate = useNavigate(); - const dispatch = useDispatch(); + const actions = useActions(); React.useEffect(() => { async function init() { @@ -48,7 +48,7 @@ const UserMutator = function UserMutator() { password2, isAdmin, }); - await dispatch.user.getUsers(); + await actions.user.getUsers(); Toast.success('User successfully saved...'); navigate('/users'); } catch (error) { diff --git a/yarn.lock b/yarn.lock index 0314f9c..f746938 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1350,16 +1350,6 @@ tar-fs "^3.1.0" yargs "^17.7.2" -"@rematch/core@2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@rematch/core/-/core-2.2.0.tgz#c4e6cc9d369d341afe2345842f43c255b7a44e90" - integrity sha512-Sj3nC/2X+bOBZeOf4jdJ00nhCcx9wLbVK9SOs6eFR4Y1qKXqRY0hGigbQgfTpCdjRFlwTHHfN3m41MlNvMhDgw== - -"@rematch/loading@2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@rematch/loading/-/loading-2.1.2.tgz#1dc680d445cd2d1234489cb69816278d02cf2216" - integrity sha512-3fWUvWkIxP+BEi2LCKYKaUkMFCT0MDcN1xQD19tPNufMry7skqybahqm9/ugs9wIji1n3ObF7yHkrb01E+N3Tw== - "@resvg/resvg-js-android-arm-eabi@2.4.1": version "2.4.1" resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz#49dc9722f95096f8aff70186deae8e148d60dce5" @@ -1727,11 +1717,6 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== -"@types/use-sync-external-store@^0.0.6": - version "0.0.6" - resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" - integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== - "@types/yauzl@^2.9.1": version "2.10.3" resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" @@ -2814,11 +2799,6 @@ decompress-response@^6.0.0: dependencies: mimic-response "^3.1.0" -deep-diff@^0.3.5: - version "0.3.8" - resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" - integrity sha512-yVn6RZmHiGnxRKR9sJb3iVV2XTF1Ghh2DiWRZ3dMnGc43yUdWWF/kX6lQyk3+P84iprfWKU/8zFTrlkvtFm1ug== - deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -6074,10 +6054,10 @@ qs@^6.14.0: dependencies: side-channel "^1.1.0" -query-string@9.3.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.3.0.tgz#f2d60d6b4442cb445f374b5ff749b937b2cccd03" - integrity sha512-IQHOQ9aauHAApwAaUYifpEyLHv6fpVGVkMOnwPzcDScLjbLj8tLsILn6unSW79NafOw1llh8oK7Gd0VwmXBFmA== +query-string@9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.3.1.tgz#d0c93e6c7fb7c17bdf04aa09e382114580ede270" + integrity sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw== dependencies: decode-uri-component "^0.4.1" filter-obj "^5.1.0" @@ -6141,14 +6121,6 @@ react-is@^18.2.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -react-redux@9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.2.0.tgz#96c3ab23fb9a3af2cb4654be4b51c989e32366f5" - integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g== - dependencies: - "@types/use-sync-external-store" "^0.0.6" - use-sync-external-store "^1.4.0" - react-refresh@^0.17.0: version "0.17.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53" @@ -6270,23 +6242,6 @@ recma-stringify@^1.0.0: unified "^11.0.0" vfile "^6.0.0" -redux-logger@3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" - integrity sha512-JoCIok7bg/XpqA1JqCqXFypuqBbQzGQySrhFzewB7ThcnysTO30l4VCst86AuB9T9tuT03MAA56Jw2PNhRSNCg== - dependencies: - deep-diff "^0.3.5" - -redux-thunk@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" - integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== - -redux@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" - integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== - reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" @@ -7440,11 +7395,6 @@ url-join@^4.0.0: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -use-sync-external-store@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" - integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== - util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -7697,6 +7647,11 @@ zod@^3.24.1: resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== +zustand@^5.0.8: + version "5.0.8" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.8.tgz#b998a0c088c7027a20f2709141a91cb07ac57f8a" + integrity sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw== + zwitch@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"