chore: replace redux with zustand

This commit is contained in:
bjarneo 2024-10-29 19:53:48 +01:00
parent 4eabc1cbb1
commit 7fb58405d1
No known key found for this signature in database
GPG Key ID: AA3697C46F530672
13 changed files with 44 additions and 141 deletions

View File

@ -1,15 +0,0 @@
import { USER_LOGIN, USER_LOGIN_CHANGED } from '../util/constants';
export const userLoginChanged = (payload) => {
return {
type: USER_LOGIN_CHANGED,
payload,
};
};
export const userLogin = (payload) => {
return {
type: USER_LOGIN,
payload,
};
};

View File

@ -1,21 +1,17 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { Link, Navigate } from 'react-router-dom';
import { IconFingerprint, IconList, IconLockOff, IconLogin, IconUser } from '@tabler/icons';
import { userLogin, userLoginChanged } from '../../actions/';
import { refresh } from '../../api/authentication.js';
import { getCookie, refreshCookie } from '../../helpers/cookie';
import useAuthStore from '../../stores/authStore';
import Logo from './logo.jsx';
import Nav from './nav';
const Header = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const isLoggedIn = useSelector((state) => state.isLoggedIn);
const username = useSelector((state) => state.username);
const { isLoggedIn, username, setLogin, setLoginStatus } = useAuthStore();
const [isMenuOpened, setIsMenuOpened] = useState(false);
const [openRefreshModal, setOpenRefreshModal] = useState(false);
@ -23,14 +19,13 @@ const Header = () => {
useEffect(() => {
if (!isLoggedIn && username) {
dispatch(userLoginChanged(true));
setLoginStatus(true);
}
const cookie = getCookie();
if (!isLoggedIn && !username && cookie) {
dispatch(userLogin(cookie));
dispatch(userLoginChanged(true));
setLogin(cookie.username);
}
}, [isLoggedIn, username]);
@ -69,8 +64,8 @@ const Header = () => {
setRedirect(true);
}
dispatch(userLogin(cookie));
dispatch(userLoginChanged(true));
setLogin(cookie.username);
setLoginStatus(true);
setOpenRefreshModal(false);
};
@ -177,7 +172,7 @@ const Header = () => {
const NavLinks = ({ mobile, onClick }) => {
const { t } = useTranslation();
const isLoggedIn = useSelector((state) => state.isLoggedIn);
const { isLoggedIn } = useAuthStore();
const links = [
!isLoggedIn && { label: t('sign_up'), icon: IconUser, to: '/signup' },

View File

@ -1,6 +0,0 @@
import { createStore } from 'redux';
import rootReducer from '../reducers';
export default function configureStore() {
return createStore(rootReducer);
}

View File

@ -1,37 +1,23 @@
import { Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import './i18n';
import HemmeligApplication from './app';
import configureStore from './helpers/configureStore';
import './index.css';
const store = configureStore();
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<Provider store={store}>
<Suspense
fallback={
<div
className="fixed inset-0 flex items-center justify-center
bg-gray-900 bg-opacity-100 z-50"
>
<div className="relative">
{/* Loading Spinner */}
<div
className="w-16 h-16 border-4 border-green-500 border-t-transparent
rounded-full animate-spin"
/>
</div>
<Suspense
fallback={
<div className="fixed inset-0 flex items-center justify-center bg-gray-900 bg-opacity-100 z-50">
<div className="relative">
<div className="w-16 h-16 border-4 border-green-500 border-t-transparent rounded-full animate-spin" />
</div>
}
>
<HemmeligApplication />
</Suspense>
</Provider>
</div>
}
>
<HemmeligApplication />
</Suspense>
);

View File

@ -1,16 +0,0 @@
import { USER_LOGIN, USER_LOGIN_CHANGED } from '../util/constants';
const initialState = {
isLoggedIn: false,
};
export default function rootReducer(state = initialState, action) {
switch (action.type) {
case USER_LOGIN_CHANGED:
return { ...state, isLoggedIn: action.payload };
case USER_LOGIN:
return { ...state, ...action.payload };
default:
return state;
}
}

View File

@ -19,7 +19,6 @@ import {
IconX,
} from '@tabler/icons';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { burnSecret } from '../../api/secret';
import CopyButton from '../../components/CopyButton';
@ -27,11 +26,12 @@ import QRLink from '../../components/qrlink';
import Quill from '../../components/quill';
import { Switch } from '../../components/switch';
import config from '../../config';
import useAuthStore from '../../stores/authStore';
import useSecretStore from '../../stores/secretStore';
const Home = () => {
const { t } = useTranslation();
const isLoggedIn = useSelector((state) => state.isLoggedIn);
const { isLoggedIn } = useAuthStore();
const {
formData,

View File

@ -1,17 +1,16 @@
import { IconEye, IconEyeOff, IconLock, IconLogin, IconUser } from '@tabler/icons';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { Navigate } from 'react-router-dom';
import { userLogin } from '../../actions';
import { signIn } from '../../api/authentication';
import ErrorBox from '../../components/error-box';
import SuccessBox from '../../components/success-box';
import useAuthStore from '../../stores/authStore';
const SignIn = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const { setLogin } = useAuthStore();
// Form state
const [formData, setFormData] = useState({
@ -60,7 +59,7 @@ const SignIn = () => {
return;
}
dispatch(userLogin(data));
setLogin(data.username);
setFormErrors({});
setSuccess(true);
};

View File

@ -1,38 +1,29 @@
import { IconLogout } from '@tabler/icons';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { Navigate } from 'react-router-dom';
import { userLogin, userLoginChanged } from '../../actions';
import { signOut } from '../../api/authentication';
import { removeCookie } from '../../helpers/cookie';
import useAuthStore from '../../stores/authStore';
const SignOut = () => {
const dispatch = useDispatch();
const { setLogout } = useAuthStore();
const [redirect, setRedirect] = useState(false);
const { t } = useTranslation();
useEffect(() => {
const performSignOut = async () => {
// Remove cookie first
removeCookie();
// Call sign out API
await signOut();
setLogout();
// Update Redux state
dispatch(userLogin({ username: '' }));
dispatch(userLoginChanged(false));
// Set redirect after delay
setTimeout(() => {
setRedirect(true);
}, 1500);
};
performSignOut();
}, [dispatch]);
}, [setLogout]);
if (!redirect) {
return (

View File

@ -1,17 +1,16 @@
import { IconAt, IconEye, IconEyeOff, IconLock, IconLogin, IconUser } from '@tabler/icons';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { Navigate } from 'react-router-dom';
import { userLogin } from '../../actions';
import { signUp } from '../../api/authentication';
import ErrorBox from '../../components/error-box';
import SuccessBox from '../../components/success-box';
import useAuthStore from '../../stores/authStore';
const SignUp = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const { setLogin } = useAuthStore();
// Form state
const [formData, setFormData] = useState({
@ -62,7 +61,7 @@ const SignUp = () => {
return;
}
dispatch(userLogin(data));
setLogin(data.username);
setFormErrors({});
setSuccess(true);
};

View File

@ -0,0 +1,11 @@
import { create } from 'zustand';
const useAuthStore = create((set) => ({
isLoggedIn: false,
username: '',
setLogin: (username) => set({ isLoggedIn: true, username }),
setLogout: () => set({ isLoggedIn: false, username: '' }),
setLoginStatus: (status) => set({ isLoggedIn: status }),
}));
export default useAuthStore;

42
package-lock.json generated
View File

@ -73,8 +73,6 @@
"react-dom": "^18.0.0",
"react-i18next": "^11.18.5",
"react-quill": "^2.0.0",
"react-redux": "^9.1.2",
"redux": "^5.0.1",
"tailwindcss": "^3.4.14",
"vite": "^5.4.10",
"zustand": "^5.0.0"
@ -3310,13 +3308,6 @@
"parchment": "^1.1.2"
}
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==",
"dev": true,
"license": "MIT"
},
"node_modules/@vitejs/plugin-react": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz",
@ -7619,30 +7610,6 @@
"react-dom": "^16 || ^17 || ^18"
}
},
"node_modules/react-redux": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz",
"integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.3",
"use-sync-external-store": "^1.0.0"
},
"peerDependencies": {
"@types/react": "^18.2.25",
"react": "^18.0",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@ -8068,13 +8035,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/redux": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"dev": true,
"license": "MIT"
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
@ -9202,6 +9162,8 @@
"integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}

View File

@ -94,8 +94,6 @@
"react-dom": "^18.0.0",
"react-i18next": "^11.18.5",
"react-quill": "^2.0.0",
"react-redux": "^9.1.2",
"redux": "^5.0.1",
"tailwindcss": "^3.4.14",
"vite": "^5.4.10",
"zustand": "^5.0.0"

View File

@ -1,17 +1,16 @@
import react from '@vitejs/plugin-react';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import tailwindcss from 'tailwindcss';
import { defineConfig } from 'vite';
const path = fileURLToPath(import.meta.url);
const root = resolve(dirname(path), 'client');
import tailwindcss from 'tailwindcss';
export default defineConfig({
root,
build: {
outDir: 'build',
outDir: resolve(root, 'build'),
},
publicDir: 'public',
plugins: [react()],