chore: replace redux with zustand
This commit is contained in:
parent
4eabc1cbb1
commit
7fb58405d1
@ -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,
|
||||
};
|
||||
};
|
@ -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' },
|
||||
|
@ -1,6 +0,0 @@
|
||||
import { createStore } from 'redux';
|
||||
import rootReducer from '../reducers';
|
||||
|
||||
export default function configureStore() {
|
||||
return createStore(rootReducer);
|
||||
}
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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 (
|
||||
|
@ -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);
|
||||
};
|
||||
|
11
client/stores/authStore.js
Normal file
11
client/stores/authStore.js
Normal 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
42
package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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()],
|
||||
|
Loading…
x
Reference in New Issue
Block a user