Basic home, Login, and Auth in client

This commit is contained in:
2024-12-01 16:59:07 +00:00
parent 62cca139af
commit 0ba95b0dab
5 changed files with 247 additions and 39 deletions

View File

@@ -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;

View 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>
);
}

View 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);
}

View File

@@ -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>,
)

View 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%);
}
}