Added Private route and global store
This commit is contained in:
38
frontend/package-lock.json
generated
38
frontend/package-lock.json
generated
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -10,25 +10,27 @@ 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 (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Header />
|
<Header />
|
||||||
<Routes>
|
<Routes>
|
||||||
<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 path="dashboard" element={<Dashboard />} />
|
<Route element={<PrivateRoute />}>
|
||||||
<Route path="bookmark" element={<Bookmark />} />
|
<Route path="dashboard" element={<Dashboard />} />
|
||||||
<Route path="/voting/:pollId" element={<VotingPage />} />
|
<Route path="bookmark" element={<Bookmark />} />
|
||||||
<Route path="/create" element={<CreatePollForm />} />
|
<Route path="/voting/:pollId" element={<VotingPage />} />
|
||||||
</Routes>
|
<Route path="/create" element={<CreatePollForm />} />
|
||||||
</BrowserRouter>
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
41
frontend/src/components/Header/ProfileImage.jsx
Normal file
41
frontend/src/components/Header/ProfileImage.jsx
Normal 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
|
||||||
16
frontend/src/components/PrivateRoute/PrivateRoute.jsx
Normal file
16
frontend/src/components/PrivateRoute/PrivateRoute.jsx
Normal 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
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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('/');
|
||||||
|
|||||||
18
frontend/src/store/useStore.js
Normal file
18
frontend/src/store/useStore.js
Normal 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;
|
||||||
Reference in New Issue
Block a user