Basic home, Login, and Auth in client
This commit is contained in:
@@ -1,35 +1,52 @@
|
||||
import { useState } from 'react'
|
||||
import reactLogo from './assets/react.svg'
|
||||
import viteLogo from '/vite.svg'
|
||||
import './App.css'
|
||||
// src/App.jsx
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||
import Login from './components/Login';
|
||||
import Dashboard from './components/Dashboard';
|
||||
import Editor from './components/Editor';
|
||||
|
||||
function App() {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<a href="https://vite.dev" target="_blank">
|
||||
<img src={viteLogo} className="logo" alt="Vite logo" />
|
||||
</a>
|
||||
<a href="https://react.dev" target="_blank">
|
||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
||||
</a>
|
||||
</div>
|
||||
<h1>Vite + React</h1>
|
||||
<div className="card">
|
||||
<button onClick={() => setCount((count) => count + 1)}>
|
||||
count is {count}
|
||||
</button>
|
||||
<p>
|
||||
Edit <code>src/App.jsx</code> and save to test HMR
|
||||
</p>
|
||||
</div>
|
||||
<p className="read-the-docs">
|
||||
Click on the Vite and React logos to learn more
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
function PrivateRoute({ children }) {
|
||||
const { isAuthenticated } = useAuth();
|
||||
return isAuthenticated ? children : <Navigate to="/login" />;
|
||||
}
|
||||
|
||||
export default App
|
||||
function App() {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<Router>
|
||||
<div className="container">
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Dashboard />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/new"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Editor />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/edit/:id"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Editor />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route path="/" element={<Navigate to="/dashboard" />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
52
client/src/components/Login.jsx
Normal file
52
client/src/components/Login.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
// src/components/Login.jsx
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
|
||||
export default function Login() {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const { login } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
|
||||
const success = await login(username, password);
|
||||
if (success) {
|
||||
navigate('/dashboard');
|
||||
} else {
|
||||
setError('Invalid credentials');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login-container">
|
||||
<form onSubmit={handleSubmit} className="login-form">
|
||||
<h2>Login</h2>
|
||||
{error && <div className="error">{error}</div>}
|
||||
<div className="form-group">
|
||||
<label>Username</label>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" className="button">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
46
client/src/contexts/AuthContext.jsx
Normal file
46
client/src/contexts/AuthContext.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// src/contexts/AuthContext.jsx
|
||||
import { createContext, useContext, useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
const AuthContext = createContext();
|
||||
|
||||
export function AuthProvider({ children }) {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('token');
|
||||
setIsAuthenticated(!!token);
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
const login = async (username, password) => {
|
||||
try {
|
||||
const response = await axios.post(`${import.meta.env.VITE_API_URL}/api/auth/login`, {
|
||||
username,
|
||||
password
|
||||
});
|
||||
localStorage.setItem('token', response.data.token);
|
||||
setIsAuthenticated(true);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem('token');
|
||||
setIsAuthenticated(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ isAuthenticated, isLoading, login, logout }}>
|
||||
{!isLoading && children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
return useContext(AuthContext);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
// src/main.jsx
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './styles/main.scss'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
</React.StrictMode>,
|
||||
)
|
||||
92
client/src/styles/main.scss
Normal file
92
client/src/styles/main.scss
Normal file
@@ -0,0 +1,92 @@
|
||||
// src/styles/main.scss
|
||||
$primary-color: #2c3e50;
|
||||
$secondary-color: #3498db;
|
||||
$background-color: #f5f6fa;
|
||||
$text-color: #2c3e50;
|
||||
$border-color: #dcdde1;
|
||||
$error-color: #e74c3c;
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: $background-color;
|
||||
color: $text-color;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
|
||||
h2 {
|
||||
margin: 0 0 20px;
|
||||
color: $primary-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: lighten($error-color, 35%);
|
||||
color: $error-color;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $secondary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: $primary-color;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: darken($primary-color, 10%);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user