Added Private route and global store

This commit is contained in:
Manik Maity
2024-11-08 18:53:34 +05:30
parent 81657315f5
commit 700b0136fc
9 changed files with 138 additions and 41 deletions

View File

@@ -15,7 +15,8 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-query": "^3.39.3", "react-query": "^3.39.3",
"react-router-dom": "^6.27.0" "react-router-dom": "^6.27.0",
"zustand": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.13.0", "@eslint/js": "^9.13.0",
@@ -1399,14 +1400,14 @@
"version": "15.7.13", "version": "15.7.13",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
"dev": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.3.12", "version": "18.3.12",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
@@ -2081,7 +2082,7 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/culori": { "node_modules/culori": {
@@ -5955,6 +5956,35 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zustand": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz",
"integrity": "sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
} }
} }
} }

View File

@@ -17,7 +17,8 @@
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-query": "^3.39.3", "react-query": "^3.39.3",
"react-router-dom": "^6.27.0" "react-router-dom": "^6.27.0",
"zustand": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.13.0", "@eslint/js": "^9.13.0",

View File

@@ -10,9 +10,9 @@ import Bookmark from "./pages/Bookmark";
import VotingPage from "./pages/VotingPage"; import VotingPage from "./pages/VotingPage";
import CreatePollForm from "./pages/CreatePollForm"; import CreatePollForm from "./pages/CreatePollForm";
import { QueryClient, QueryClientProvider } from "react-query"; import { QueryClient, QueryClientProvider } from "react-query";
import PrivateRoute from "./components/PrivateRoute/PrivateRoute";
function App() { function App() {
const queryClient = new QueryClient(); const queryClient = new QueryClient();
return ( return (
@@ -23,10 +23,12 @@ function App() {
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/login" element={<LoginPage />} /> <Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<Register />} /> <Route path="/register" element={<Register />} />
<Route element={<PrivateRoute />}>
<Route path="dashboard" element={<Dashboard />} /> <Route path="dashboard" element={<Dashboard />} />
<Route path="bookmark" element={<Bookmark />} /> <Route path="bookmark" element={<Bookmark />} />
<Route path="/voting/:pollId" element={<VotingPage />} /> <Route path="/voting/:pollId" element={<VotingPage />} />
<Route path="/create" element={<CreatePollForm />} /> <Route path="/create" element={<CreatePollForm />} />
</Route>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
</QueryClientProvider> </QueryClientProvider>

View File

@@ -1,8 +1,12 @@
import React from 'react' import React from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import useUserStore from '../../store/useStore'
import ProfileImage from './ProfileImage';
function Header() { function Header() {
const {user} = useUserStore();
return ( return (
<div className="navbar bg-base-100"> <div className="navbar bg-base-100">
<div className="flex-1"> <div className="flex-1">
@@ -10,32 +14,11 @@ function Header() {
</div> </div>
<div className="flex-none"> <div className="flex-none">
<ul className="menu menu-horizontal px-1"> <ul className="menu menu-horizontal px-1">
<li><Link to={"/login"}>Login</Link></li> {user.username ? <li><Link to={"/dashboard"}>Dashboard</Link></li> : <li><Link to={"/login"}>Login</Link></li>}
<li><Link to={'/bookmark'}>Bookmarks</Link></li> <li><Link to={'/bookmark'}>Bookmarks</Link></li>
</ul> </ul>
</div> </div>
<div className="flex-none"> {user.username && <ProfileImage userData={user}/>}
<div className="dropdown dropdown-end">
<div tabIndex={0} role="button" className="btn btn-ghost btn-circle avatar">
<div className="w-10 rounded-full">
<img
alt="Tailwind CSS Navbar component"
src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
</div>
</div>
<ul
tabIndex={0}
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow">
<li>
<Link to={'/dashboard'} className="justify-between" >
Profile
<span className="badge">New</span>
</Link>
</li>
<li><a>Logout</a></li>
</ul>
</div>
</div>
</div> </div>
) )
} }

View File

@@ -0,0 +1,41 @@
import React from 'react'
import { Link } from 'react-router-dom'
import useUserStore from '../../store/useStore';
function ProfileImage({userData}) {
const {setUser} = useUserStore();
const handleLogout = () => {
setUser({});
}
return (
<div className="flex-none">
<div className="dropdown dropdown-end">
<div tabIndex={0} role="button" className="btn btn-ghost btn-circle avatar">
<div className="w-10 rounded-full">
<img
alt="Tailwind CSS Navbar component"
src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
</div>
</div>
<ul
tabIndex={0}
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow">
<li className='font-bold'><a >{userData?.username || "User"}</a></li>
<li>
<Link to={'/dashboard'} className="justify-between" >
Profile
<span className="badge">New</span>
</Link>
</li>
<li><a onClick={handleLogout}>Logout</a></li>
</ul>
</div>
</div>
)
}
export default ProfileImage

View File

@@ -0,0 +1,16 @@
import React from 'react'
import useUserStore from '../../store/useStore';
import { Navigate, Outlet } from 'react-router-dom';
function PrivateRoute() {
const {user} = useUserStore();
return (
<div>
{user.username ? <Outlet/> : <Navigate to={'/login'}/>}
</div>
)
}
export default PrivateRoute

View File

@@ -1,10 +1,12 @@
import React from 'react' import React from 'react'
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import useStore from '../store/useStore';
function Home() { function Home() {
const navigator = useNavigate(); const navigator = useNavigate();
return ( return (
<div className="flex bg-base-200 min-h-screen flex-col items-center text-white p-8"> <div className="flex bg-base-200 min-h-screen flex-col items-center text-white p-8">
<h1 className="text-4xl mt-2 md:text-5xl font-bold mb-6 text-center flex flex-col gap-2 md:block">Welcome to <span className='bg-slate-800 px-4 rounded-md relative'>LivePoll</span></h1> <h1 className="text-4xl mt-2 md:text-5xl font-bold mb-6 text-center flex flex-col gap-2 md:block">Welcome to <span className='bg-slate-800 px-4 rounded-md relative'>LivePoll</span></h1>

View File

@@ -5,6 +5,7 @@ import { Link, useNavigate } from 'react-router-dom';
import { loginService } from '../services/loginService'; import { loginService } from '../services/loginService';
import SpinnerLoader from '../components/Loaders/SpinnerLoader'; import SpinnerLoader from '../components/Loaders/SpinnerLoader';
import InlineTextError from '../components/Errors/InlineTextError'; import InlineTextError from '../components/Errors/InlineTextError';
import useUserStore from '../store/useStore';
const LoginPage = () => { const LoginPage = () => {
@@ -12,8 +13,11 @@ const LoginPage = () => {
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const navigator = useNavigate(); const navigator = useNavigate();
let {setUser} = useUserStore()
const mutation = useMutation(loginService, { const mutation = useMutation(loginService, {
onSuccess: (data) => { onSuccess: (data) => {
setUser(data?.user);
setEmail(''); setEmail('');
setPassword(''); setPassword('');
navigator('/'); navigator('/');

View File

@@ -0,0 +1,18 @@
import {create} from "zustand"
import {createJSONStorage, persist} from "zustand/middleware"
const useUserStore = create(persist((set) => {
return {
user : {},
setUser : (user) => set(() => {
return {
user : user
}
}),
}
}, {
name: "livepoll",
storage: createJSONStorage(() => localStorage),
}));
export default useUserStore;