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"