Browse Source
- Add login and register pages with form validation and file upload support - Implement AuthContext for global authentication state management - Create ProtectedRoute component for route-based authentication - Update Navbar to show user profile and authentication status - Add media controller debugging and fix file serving paths - Fix nginx configuration for proper API proxying and client-side routing - Update Home page to display personalized content for authenticated users - Integrate profile picture display in navigation bar Technical improvements: - Fix nginx rule priority to handle API routes before static assets - Add proper ES modules support in media controller - Implement JWT token persistence with localStorage - Add loading states and error handling throughout auth flowfix/clean
25 changed files with 816 additions and 71 deletions
@ -0,0 +1,23 @@ |
|||||
|
up: |
||||
|
docker compose up --build -d |
||||
|
|
||||
|
down: |
||||
|
docker compose down |
||||
|
|
||||
|
logs: |
||||
|
docker compose logs -f |
||||
|
|
||||
|
dev: |
||||
|
docker compose -f developpement.yaml up --build -d |
||||
|
|
||||
|
dev-down: |
||||
|
docker compose -f developpement.yaml down |
||||
|
|
||||
|
dev-logs: |
||||
|
docker compose -f developpement.yaml logs -f |
||||
|
|
||||
|
dev-volumes: |
||||
|
docker compose -f developpement.yaml down -v |
||||
|
|
||||
|
dev-build: |
||||
|
docker compose -f developpement.yaml build |
||||
@ -0,0 +1,57 @@ |
|||||
|
import path from 'path'; |
||||
|
import { fileURLToPath } from 'url'; |
||||
|
|
||||
|
const __filename = fileURLToPath(import.meta.url); |
||||
|
const __dirname = path.dirname(__filename); |
||||
|
|
||||
|
export async function getProfilePicture(req, res) { |
||||
|
const file = req.params.file; |
||||
|
|
||||
|
// Try different path approaches
|
||||
|
const possiblePaths = [ |
||||
|
path.join(__dirname, '../../uploads/profiles', file), |
||||
|
path.join(process.cwd(), 'app/uploads/profiles', file), |
||||
|
path.join('/app/app/uploads/profiles', file), |
||||
|
path.join('/app/uploads/profiles', file) |
||||
|
]; |
||||
|
|
||||
|
console.log('Possible paths:', possiblePaths); |
||||
|
console.log('Current working directory:', process.cwd()); |
||||
|
console.log('__dirname:', __dirname); |
||||
|
|
||||
|
// Try the most likely path first (based on your volume mapping)
|
||||
|
const filePath = path.join('/app/app/uploads/profiles', file); |
||||
|
|
||||
|
try { |
||||
|
res.sendFile(filePath, (err) => { |
||||
|
if (err) { |
||||
|
console.error("Error sending profile picture:", err); |
||||
|
res.status(404).json({ error: "Profile picture not found." }); |
||||
|
} else { |
||||
|
console.log("Profile picture sent successfully."); |
||||
|
} |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
console.error("Error fetching profile picture:", error); |
||||
|
res.status(500).json({ error: "Internal server error while fetching profile picture." }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export async function getThumbnail(req, res) { |
||||
|
const file = req.params.file; |
||||
|
const filePath = path.join('/app/app/uploads/thumbnails', file); |
||||
|
|
||||
|
try { |
||||
|
res.sendFile(filePath, (err) => { |
||||
|
if (err) { |
||||
|
console.error("Error sending thumbnail:", err); |
||||
|
res.status(404).json({ error: "Thumbnail not found." }); |
||||
|
} else { |
||||
|
console.log("Thumbnail sent successfully."); |
||||
|
} |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
console.error("Error fetching thumbnail:", error); |
||||
|
res.status(500).json({ error: "Internal server error while fetching thumbnail." }); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
import {Router} from 'express'; |
||||
|
import {getProfilePicture, getThumbnail} from "../controllers/media.controller.js"; |
||||
|
|
||||
|
const router = Router(); |
||||
|
|
||||
|
router.get("/profile/:file", getProfilePicture); |
||||
|
|
||||
|
//router.get("/video/:file", getVideo);
|
||||
|
|
||||
|
router.get("/thumbnail/:file", getThumbnail); |
||||
|
|
||||
|
export default router; |
||||
@ -1,8 +1,10 @@ |
|||||
import { Router } from 'express'; |
import { Router } from 'express'; |
||||
import {getRecommendations} from "../controllers/recommendation.controller.js"; |
import {getRecommendations, getTrendingVideos} from "../controllers/recommendation.controller.js"; |
||||
|
|
||||
const router = Router(); |
const router = Router(); |
||||
|
|
||||
router.get('/', [], getRecommendations); |
router.get('/', [], getRecommendations); |
||||
|
|
||||
|
router.get('/trending', [], getTrendingVideos); |
||||
|
|
||||
export default router; |
export default router; |
||||
@ -0,0 +1,11 @@ |
|||||
|
### GET PROFILE PICTURE (through nginx) |
||||
|
GET http://localhost/api/media/profile/sacha.jpg |
||||
|
|
||||
|
### GET PROFILE PICTURE (direct backend - for testing) |
||||
|
GET http://localhost:8000/api/media/profile/sacha.jpg |
||||
|
|
||||
|
### GET THUMBNAIL (through nginx) |
||||
|
GET http://localhost/api/media/thumbnail/E90B982DE9C5112C.jpg |
||||
|
|
||||
|
### GET THUMBNAIL (direct backend - for testing) |
||||
|
GET http://localhost:8000/api/media/thumbnail/E90B982DE9C5112C.jpg |
||||
@ -1,2 +1,5 @@ |
|||||
### GET NON-AUTHENTICATED RECOMMENDATIONS |
### GET NON-AUTHENTICATED RECOMMENDATIONS |
||||
GET http://localhost:8000/api/recommendations/ |
GET http://localhost:8000/api/recommendations/ |
||||
|
|
||||
|
### GET TRENDING VIDS |
||||
|
GET http://localhost:8000/api/recommendations/trending/ |
||||
@ -1,14 +1,29 @@ |
|||||
import React from 'react'; |
import React from 'react'; |
||||
import { useRoutes } from 'react-router-dom'; |
import { useRoutes } from 'react-router-dom'; |
||||
|
import { AuthProvider, useAuth } from './contexts/AuthContext'; |
||||
import routes from './routes/routes.jsx'; |
import routes from './routes/routes.jsx'; |
||||
|
|
||||
const App = () => { |
const AppContent = () => { |
||||
|
const { loading } = useAuth(); |
||||
const routing = useRoutes(routes); |
const routing = useRoutes(routes); |
||||
|
|
||||
|
if (loading) { |
||||
return ( |
return ( |
||||
<div> |
<div className="min-h-screen bg-linear-to-br from-left-gradient to-right-gradient flex items-center justify-center"> |
||||
{routing} |
<div className="text-white text-2xl font-montserrat">Chargement...</div> |
||||
</div> |
</div> |
||||
); |
); |
||||
|
} |
||||
|
|
||||
|
return <div>{routing}</div>; |
||||
|
}; |
||||
|
|
||||
|
const App = () => { |
||||
|
return ( |
||||
|
<AuthProvider> |
||||
|
<AppContent /> |
||||
|
</AuthProvider> |
||||
|
); |
||||
}; |
}; |
||||
|
|
||||
export default App; |
export default App; |
||||
@ -0,0 +1,27 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Navigate } from 'react-router-dom'; |
||||
|
import { useAuth } from '../contexts/AuthContext'; |
||||
|
|
||||
|
const ProtectedRoute = ({ children, requireAuth = true }) => { |
||||
|
const { isAuthenticated, loading } = useAuth(); |
||||
|
|
||||
|
if (loading) { |
||||
|
return ( |
||||
|
<div className="min-h-screen flex items-center justify-center"> |
||||
|
<div className="text-white text-xl">Chargement...</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
if (requireAuth && !isAuthenticated) { |
||||
|
return <Navigate to="/login" replace />; |
||||
|
} |
||||
|
|
||||
|
if (!requireAuth && isAuthenticated) { |
||||
|
return <Navigate to="/" replace />; |
||||
|
} |
||||
|
|
||||
|
return children; |
||||
|
}; |
||||
|
|
||||
|
export default ProtectedRoute; |
||||
@ -0,0 +1,112 @@ |
|||||
|
import React, { createContext, useContext, useState, useEffect } from 'react'; |
||||
|
|
||||
|
const AuthContext = createContext(); |
||||
|
|
||||
|
export const useAuth = () => { |
||||
|
const context = useContext(AuthContext); |
||||
|
if (!context) { |
||||
|
throw new Error('useAuth must be used within an AuthProvider'); |
||||
|
} |
||||
|
return context; |
||||
|
}; |
||||
|
|
||||
|
export const AuthProvider = ({ children }) => { |
||||
|
const [user, setUser] = useState(null); |
||||
|
const [loading, setLoading] = useState(true); |
||||
|
|
||||
|
// Check if user is logged in on app start |
||||
|
useEffect(() => { |
||||
|
const token = localStorage.getItem('token'); |
||||
|
const userData = localStorage.getItem('user'); |
||||
|
|
||||
|
if (token && userData) { |
||||
|
setUser(JSON.parse(userData)); |
||||
|
} |
||||
|
setLoading(false); |
||||
|
}, []); |
||||
|
|
||||
|
const login = async (username, password) => { |
||||
|
try { |
||||
|
const response = await fetch('/api/users/login', { |
||||
|
method: 'POST', |
||||
|
headers: { |
||||
|
'Content-Type': 'application/json', |
||||
|
}, |
||||
|
body: JSON.stringify({ username, password }), |
||||
|
}); |
||||
|
|
||||
|
const data = await response.json(); |
||||
|
|
||||
|
if (!response.ok) { |
||||
|
throw new Error(data.message || 'Erreur de connexion'); |
||||
|
} |
||||
|
|
||||
|
// Store token and user data |
||||
|
localStorage.setItem('token', data.token); |
||||
|
localStorage.setItem('user', JSON.stringify(data.user)); |
||||
|
setUser(data.user); |
||||
|
|
||||
|
return data; |
||||
|
} catch (error) { |
||||
|
throw error; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const register = async (email, username, password, profileImage) => { |
||||
|
try { |
||||
|
const formData = new FormData(); |
||||
|
formData.append('email', email); |
||||
|
formData.append('username', username); |
||||
|
formData.append('password', password); |
||||
|
|
||||
|
if (profileImage) { |
||||
|
formData.append('profile', profileImage); |
||||
|
} |
||||
|
|
||||
|
const response = await fetch('/api/users/', { |
||||
|
method: 'POST', |
||||
|
body: formData, |
||||
|
}); |
||||
|
|
||||
|
const data = await response.json(); |
||||
|
|
||||
|
if (!response.ok) { |
||||
|
throw new Error(data.message || 'Erreur lors de la création du compte'); |
||||
|
} |
||||
|
|
||||
|
// After successful registration, log the user in |
||||
|
await login(username, password); |
||||
|
|
||||
|
return data; |
||||
|
} catch (error) { |
||||
|
throw error; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const logout = () => { |
||||
|
localStorage.removeItem('token'); |
||||
|
localStorage.removeItem('user'); |
||||
|
setUser(null); |
||||
|
}; |
||||
|
|
||||
|
const getAuthHeaders = () => { |
||||
|
const token = localStorage.getItem('token'); |
||||
|
return token ? { Authorization: `Bearer ${token}` } : {}; |
||||
|
}; |
||||
|
|
||||
|
const value = { |
||||
|
user, |
||||
|
login, |
||||
|
register, |
||||
|
logout, |
||||
|
getAuthHeaders, |
||||
|
isAuthenticated: !!user, |
||||
|
loading |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<AuthContext.Provider value={value}> |
||||
|
{children} |
||||
|
</AuthContext.Provider> |
||||
|
); |
||||
|
}; |
||||
@ -0,0 +1,107 @@ |
|||||
|
import React, { useState } from 'react'; |
||||
|
import { useNavigate } from 'react-router-dom'; |
||||
|
import { useAuth } from '../contexts/AuthContext'; |
||||
|
import Navbar from '../components/Navbar'; |
||||
|
|
||||
|
export default function Login() { |
||||
|
const [formData, setFormData] = useState({ |
||||
|
username: '', |
||||
|
password: '' |
||||
|
}); |
||||
|
const [error, setError] = useState(''); |
||||
|
const [loading, setLoading] = useState(false); |
||||
|
|
||||
|
const navigate = useNavigate(); |
||||
|
const { login } = useAuth(); |
||||
|
|
||||
|
const handleChange = (e) => { |
||||
|
setFormData({ |
||||
|
...formData, |
||||
|
[e.target.name]: e.target.value |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const handleSubmit = async (e) => { |
||||
|
e.preventDefault(); |
||||
|
setError(''); |
||||
|
setLoading(true); |
||||
|
|
||||
|
try { |
||||
|
await login(formData.username, formData.password); |
||||
|
navigate('/'); |
||||
|
} catch (err) { |
||||
|
setError(err.message || 'Erreur de connexion'); |
||||
|
} finally { |
||||
|
setLoading(false); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> |
||||
|
<Navbar isSearchPage={false} /> |
||||
|
|
||||
|
<div className="flex justify-center items-center min-h-screen pt-20"> |
||||
|
<div className="bg-white p-8 rounded-lg shadow-lg w-full max-w-md"> |
||||
|
<h2 className="text-3xl font-bold text-center mb-6 font-montserrat">Connexion</h2> |
||||
|
|
||||
|
{error && ( |
||||
|
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4"> |
||||
|
{error} |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
<form onSubmit={handleSubmit} className="space-y-4"> |
||||
|
<div> |
||||
|
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1"> |
||||
|
Nom d'utilisateur |
||||
|
</label> |
||||
|
<input |
||||
|
type="text" |
||||
|
id="username" |
||||
|
name="username" |
||||
|
value={formData.username} |
||||
|
onChange={handleChange} |
||||
|
required |
||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" |
||||
|
placeholder="Entrez votre nom d'utilisateur" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div> |
||||
|
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1"> |
||||
|
Mot de passe |
||||
|
</label> |
||||
|
<input |
||||
|
type="password" |
||||
|
id="password" |
||||
|
name="password" |
||||
|
value={formData.password} |
||||
|
onChange={handleChange} |
||||
|
required |
||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" |
||||
|
placeholder="Entrez votre mot de passe" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<button |
||||
|
type="submit" |
||||
|
disabled={loading} |
||||
|
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 font-montserrat" |
||||
|
> |
||||
|
{loading ? 'Connexion...' : 'Se connecter'} |
||||
|
</button> |
||||
|
</form> |
||||
|
|
||||
|
<div className="mt-6 text-center"> |
||||
|
<p className="text-gray-600"> |
||||
|
Pas encore de compte ?{' '} |
||||
|
<a href="/register" className="text-blue-600 hover:underline"> |
||||
|
Créer un compte |
||||
|
</a> |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,195 @@ |
|||||
|
import React, { useState } from 'react'; |
||||
|
import { useNavigate } from 'react-router-dom'; |
||||
|
import { useAuth } from '../contexts/AuthContext'; |
||||
|
import Navbar from '../components/Navbar'; |
||||
|
|
||||
|
export default function Register() { |
||||
|
const [formData, setFormData] = useState({ |
||||
|
email: '', |
||||
|
username: '', |
||||
|
password: '', |
||||
|
confirmPassword: '', |
||||
|
profile: null |
||||
|
}); |
||||
|
const [error, setError] = useState(''); |
||||
|
const [loading, setLoading] = useState(false); |
||||
|
const [previewImage, setPreviewImage] = useState(null); |
||||
|
|
||||
|
const navigate = useNavigate(); |
||||
|
const { register } = useAuth(); |
||||
|
|
||||
|
const handleChange = (e) => { |
||||
|
if (e.target.name === 'profile') { |
||||
|
const file = e.target.files[0]; |
||||
|
setFormData({ |
||||
|
...formData, |
||||
|
profile: file |
||||
|
}); |
||||
|
|
||||
|
// Create preview |
||||
|
if (file) { |
||||
|
const reader = new FileReader(); |
||||
|
reader.onload = (e) => setPreviewImage(e.target.result); |
||||
|
reader.readAsDataURL(file); |
||||
|
} else { |
||||
|
setPreviewImage(null); |
||||
|
} |
||||
|
} else { |
||||
|
setFormData({ |
||||
|
...formData, |
||||
|
[e.target.name]: e.target.value |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const handleSubmit = async (e) => { |
||||
|
e.preventDefault(); |
||||
|
setError(''); |
||||
|
|
||||
|
// Validation |
||||
|
if (formData.password !== formData.confirmPassword) { |
||||
|
setError('Les mots de passe ne correspondent pas'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (formData.password.length < 6) { |
||||
|
setError('Le mot de passe doit contenir au moins 6 caractères'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
setLoading(true); |
||||
|
|
||||
|
try { |
||||
|
await register(formData.email, formData.username, formData.password, formData.profile); |
||||
|
navigate('/'); |
||||
|
} catch (err) { |
||||
|
setError(err.message || 'Erreur lors de la création du compte'); |
||||
|
} finally { |
||||
|
setLoading(false); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<div className="min-w-screen min-h-screen bg-linear-to-br from-left-gradient to-right-gradient"> |
||||
|
<Navbar isSearchPage={false} /> |
||||
|
|
||||
|
<div className="flex justify-center items-center min-h-screen pt-20 pb-10"> |
||||
|
<div className="bg-white p-8 rounded-lg shadow-lg w-full max-w-md"> |
||||
|
<h2 className="text-3xl font-bold text-center mb-6 font-montserrat">Créer un compte</h2> |
||||
|
|
||||
|
{error && ( |
||||
|
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4"> |
||||
|
{error} |
||||
|
</div> |
||||
|
)} |
||||
|
|
||||
|
<form onSubmit={handleSubmit} className="space-y-4"> |
||||
|
<div> |
||||
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1"> |
||||
|
Email |
||||
|
</label> |
||||
|
<input |
||||
|
type="email" |
||||
|
id="email" |
||||
|
name="email" |
||||
|
value={formData.email} |
||||
|
onChange={handleChange} |
||||
|
required |
||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" |
||||
|
placeholder="Entrez votre email" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div> |
||||
|
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1"> |
||||
|
Nom d'utilisateur |
||||
|
</label> |
||||
|
<input |
||||
|
type="text" |
||||
|
id="username" |
||||
|
name="username" |
||||
|
value={formData.username} |
||||
|
onChange={handleChange} |
||||
|
required |
||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" |
||||
|
placeholder="Entrez votre nom d'utilisateur" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div> |
||||
|
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1"> |
||||
|
Mot de passe |
||||
|
</label> |
||||
|
<input |
||||
|
type="password" |
||||
|
id="password" |
||||
|
name="password" |
||||
|
value={formData.password} |
||||
|
onChange={handleChange} |
||||
|
required |
||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" |
||||
|
placeholder="Entrez votre mot de passe" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div> |
||||
|
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1"> |
||||
|
Confirmer le mot de passe |
||||
|
</label> |
||||
|
<input |
||||
|
type="password" |
||||
|
id="confirmPassword" |
||||
|
name="confirmPassword" |
||||
|
value={formData.confirmPassword} |
||||
|
onChange={handleChange} |
||||
|
required |
||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" |
||||
|
placeholder="Confirmez votre mot de passe" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div> |
||||
|
<label htmlFor="profile" className="block text-sm font-medium text-gray-700 mb-1"> |
||||
|
Photo de profil (optionnel) |
||||
|
</label> |
||||
|
<input |
||||
|
type="file" |
||||
|
id="profile" |
||||
|
name="profile" |
||||
|
accept="image/*" |
||||
|
onChange={handleChange} |
||||
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" |
||||
|
/> |
||||
|
{previewImage && ( |
||||
|
<div className="mt-2"> |
||||
|
<img |
||||
|
src={previewImage} |
||||
|
alt="Aperçu" |
||||
|
className="w-20 h-20 object-cover rounded-full mx-auto" |
||||
|
/> |
||||
|
</div> |
||||
|
)} |
||||
|
</div> |
||||
|
|
||||
|
<button |
||||
|
type="submit" |
||||
|
disabled={loading} |
||||
|
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 font-montserrat" |
||||
|
> |
||||
|
{loading ? 'Création du compte...' : 'Créer un compte'} |
||||
|
</button> |
||||
|
</form> |
||||
|
|
||||
|
<div className="mt-6 text-center"> |
||||
|
<p className="text-gray-600"> |
||||
|
Déjà un compte ?{' '} |
||||
|
<a href="/login" className="text-blue-600 hover:underline"> |
||||
|
Se connecter |
||||
|
</a> |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
@ -1,7 +1,26 @@ |
|||||
import Home from '../pages/Home.jsx' |
import Home from '../pages/Home.jsx' |
||||
|
import Login from '../pages/Login.jsx' |
||||
|
import Register from '../pages/Register.jsx' |
||||
|
import ProtectedRoute from '../components/ProtectedRoute.jsx' |
||||
|
|
||||
const routes = [ |
const routes = [ |
||||
{ path: "/", element: <Home/> }, |
{ path: "/", element: <Home/> }, |
||||
|
{ |
||||
|
path: "/login", |
||||
|
element: ( |
||||
|
<ProtectedRoute requireAuth={false}> |
||||
|
<Login /> |
||||
|
</ProtectedRoute> |
||||
|
) |
||||
|
}, |
||||
|
{ |
||||
|
path: "/register", |
||||
|
element: ( |
||||
|
<ProtectedRoute requireAuth={false}> |
||||
|
<Register /> |
||||
|
</ProtectedRoute> |
||||
|
) |
||||
|
}, |
||||
] |
] |
||||
|
|
||||
export default routes; |
export default routes; |
||||
Loading…
Reference in new issue