replacing rematch with zustand (#180)

* replacing rematch with zustand

* upgrading dependencies

* next release version
This commit is contained in:
Christian Kellner
2025-09-18 20:09:11 +02:00
committed by GitHub
parent 28e885f6c7
commit 4f79c5cba2
21 changed files with 226 additions and 322 deletions

View File

@@ -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);
}
},
},
};

View File

@@ -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);
}
},
},
};

View File

@@ -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);
}
},
},
};

View File

@@ -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);
}
},
},
};

View File

@@ -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);
}
},
},
};

View File

@@ -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);
}
},
},
};

View File

@@ -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;

View File

@@ -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);
}