download working

This commit is contained in:
dimitar committed 2024-11-02 00:08:04 +01:00
1 parent ed1a580b09
commit bc47e1d39a
11 files changed
+544 -355

No files matched your search

+15 -8
View File
@@ -1,4 +1,5 @@
import './App.css'
// import './App.css'
import { AuthProvider } from './hooks/useAuth';
import Navbar from './components/navbar/Navbar'
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from './pages/homepage/Home'
@@ -12,15 +13,16 @@ import Gallery from './components/gallery/Gallery.jsx';
import Certificates from './components/Certificates/Certificates.jsx';
import Clients from './components/clients/clients';
import AdminPanel from './components/adminPanel/AdminPanel';
import Login from './components/login/login';
import Dashboard from './components/dashboard/Dashboard';
import Login from './components/login/login';
import ProtectedRoute from './components/protectedRoute/ProtectedRoute';
function App() {
return (
<div >
<BrowserRouter >
<AuthProvider>
<div >
<BrowserRouter >
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
@@ -31,9 +33,13 @@ function App() {
<Route path='/ultrasound' element={<UltraSound />} />
<Route path='/gallery' element={<Gallery />} />
<Route path='/certificates' element={<Certificates />} />
<Route path='/clients' element={<Clients />} />
<Route path='/clients' element={
<ProtectedRoute>
<Clients />
</ProtectedRoute>
} />
<Route path='/admin' element={
<ProtectedRoute adminOnly={true}>
<ProtectedRoute>
<AdminPanel />
</ProtectedRoute>
} />
@@ -45,8 +51,9 @@ function App() {
} />
</Routes>
<Footer />
</BrowserRouter>
</div>
</BrowserRouter>
</div>
</AuthProvider>
)
}
@@ -4,6 +4,7 @@ import { format } from 'date-fns';
import { FiFolder, FiDownload, FiChevronRight, FiChevronDown } from 'react-icons/fi';
import { useAuth } from '../../hooks/useAuth';
import { getSharedDocuments } from '../../services/api';
import api from '../../services/api';
function Dashboard() {
const [documents, setDocuments] = useState([]);
@@ -42,16 +43,52 @@ function Dashboard() {
}, {});
};
const handleDownload = async (s3Key, fileName) => {
try {
window.open(`http://localhost:3000/documents/shared/download/${s3Key}`, '_blank');
} catch (err) {
console.error('Error downloading document:', err);
alert('Failed to download document. Please try again.');
}
};
// const handleDownload = async (s3Key, fileName) => {
// try {
// const response = await api.get(`/documents/shared/download/${s3Key}`, {
// responseType: 'blob',
// });
// const url = window.URL.createObjectURL(new Blob([response.data]));
// const link = document.createElement('a');
// link.href = url;
// link.setAttribute('download', fileName); // or use the actual filename
// document.body.appendChild(link);
// link.click();
// link.parentNode.removeChild(link);
// } catch (err) {
// console.error('Error downloading document:', err);
// alert('Failed to download document. Please try again.');
// }
// };
// const groupDocumentsByCompanyAndDate = (docs) => {
// return docs.reduce((acc, doc) => {
// const folderName = `${doc.sharedWith?.name || 'Unknown'}-${format(new Date(doc.createdAt), 'yyyy-MM-dd')}`;
// if (!acc[folderName]) {
// acc[folderName] = [];
// }
// acc[folderName].push(doc);
// return acc;
// }, {});
// };
const toggleFolder = (folderName) => {
const handleDownload = async (s3Key, fileName) => {
try {
const response = await api.get(`/documents/shared/download/${s3Key}`, {
responseType: 'blob',
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName); // or use the actual filename
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
} catch (err) {
console.error('Error downloading document:', err);
alert('Failed to download document. Please try again.');
}
};
const toggleFolder = (folderName) => {
setExpandedFolders(prev => ({
...prev,
[folderName]: !prev[folderName]
+37 -121
View File
@@ -1,157 +1,73 @@
import { useState } from 'react';
import { useAuth } from '../../hooks/useAuth';
import { useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import axios from 'axios';
const Login = () => {
const navigate = useNavigate();
const [formData, setFormData] = useState({
username: '',
password: ''
});
const [username, setUsername] = useState(''); // Changed from email to username
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
setError('');
};
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setError('');
try {
const response = await axios.post('http://localhost:3000/auth/login', formData, {
headers: {
'Content-Type': 'application/json',
},
});
if (response.data.access_token) {
// Store the token
localStorage.setItem('token', response.data.access_token);
// Redirect based on user role (assuming you have this info)
const isAdmin = response.data.isAdmin; // Adjust based on your API response
navigate(isAdmin ? '/admin' : '/dashboard');
} else {
setError('Invalid response from server');
}
await login(username, password); // Changed to pass username and password separately
navigate('/dashboard');
} catch (err) {
setError(
err.response?.data?.message ||
'Неуспешна најава. Проверете ги вашите податоци.'
);
} finally {
setIsLoading(false);
setError('Invalid credentials');
}
};
return (
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-white flex items-center justify-center px-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="max-w-md w-full space-y-8 bg-white p-8 rounded-xl shadow-lg"
>
{/* Header */}
<div className="text-center">
<h2 className="text-3xl font-bold text-gray-900">
Најавете се
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
<p className="mt-2 text-sm text-gray-600">
Внесете ги вашите податоци за најава
</p>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
<div className="space-y-4">
{/* Username Field */}
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
{error && (
<div className="text-red-500 text-center text-sm">
{error}
</div>
)}
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label
htmlFor="username"
className="block text-sm font-medium text-gray-700"
>
Корисничко име
</label>
<input
id="username"
name="username"
type="text"
required
value={formData.username}
onChange={handleInputChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm
focus:border-blue-500 focus:ring-blue-500 sm:text-sm
px-3 py-2 border"
aria-label="Username"
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
{/* Password Field */}
<div>
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700"
>
Лозинка
</label>
<input
id="password"
name="password"
type="password"
required
value={formData.password}
onChange={handleInputChange}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm
focus:border-blue-500 focus:ring-blue-500 sm:text-sm
px-3 py-2 border"
aria-label="Password"
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
</div>
{/* Error Message */}
{error && (
<div
className="text-red-500 text-sm text-center bg-red-50 p-2 rounded"
role="alert"
<div>
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
{error}
</div>
)}
{/* Submit Button */}
<button
type="submit"
disabled={isLoading}
className="w-full flex justify-center py-3 px-4 border border-transparent
rounded-full text-sm font-semibold text-white bg-blue-600
hover:bg-blue-700 focus:outline-none focus:ring-2
focus:ring-offset-2 focus:ring-blue-500 transition-colors
duration-300 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? (
<span className="flex items-center">
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Најава во тек...
</span>
) : (
'Најави се'
)}
</button>
Sign in
</button>
</div>
</form>
</motion.div>
</div>
</div>
);
};
@@ -1,23 +1,22 @@
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from '../../hooks/useAuth';
// eslint-disable-next-line react/prop-types
function ProtectedRoute({ children, adminOnly = false }) {
const { user, loading } = useAuth();
const location = useLocation();
import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
if (loading) {
const ProtectedRoute = ({ children }) => {
const { user, isLoading } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (!isLoading && !user) {
navigate('/login');
}
}, [user, isLoading, navigate]);
if (isLoading) {
return <div>Loading...</div>;
}
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
if (adminOnly && !user.isAdmin) {
return <Navigate to="/dashboard" replace />;
}
return children;
}
return user ? children : null;
};
export default ProtectedRoute;
-29
View File
@@ -1,29 +0,0 @@
import { useState, useEffect } from 'react';
import { getUserInfo } from '../services/api';
import * as jwtDecode from 'jwt-decode';
export function useAuth() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUser() {
try {
const token = localStorage.getItem('token');
if (token) {
const decodedToken = jwtDecode.jwtDecode(token);
const response = await getUserInfo(decodedToken.sub);
setUser(response.data);
}
} catch (error) {
console.error('Failed to fetch user info:', error);
} finally {
setLoading(false);
}
}
fetchUser();
}, []);
return { user, loading };
}
+84
View File
@@ -0,0 +1,84 @@
import { createContext, useContext, useState, useEffect } from 'react';
import api from '../services/api';
const AuthContext = createContext(null);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchUser = async () => {
try {
const token = localStorage.getItem('token');
if (!token) {
setIsLoading(false);
return;
}
const response = await api.get('/auth/user-info'); // Updated endpoint
setUser(response.data);
} catch (error) {
console.error('Failed to fetch user info:', error);
localStorage.removeItem('token');
} finally {
setIsLoading(false);
}
};
fetchUser();
}, []);
const login = async (username, password) => {
try {
const response = await api.post('/auth/login', { username, password });
const { access_token } = response.data; // Make sure this matches your backend response
localStorage.setItem('token', access_token);
// After setting token, fetch user info
const userResponse = await api.get('/auth/user-info');
setUser(userResponse.data);
return userResponse.data;
} catch (error) {
console.error('Login error:', error);
throw error;
}
};
// const login = async (username, password) => { // Changed parameters
// try {
// const response = await api.post('/auth/login', { username, password });
// const { token } = response.data; // Updated to match backend response
// localStorage.setItem('token', token);
// // Fetch user info after successful login
// const userResponse = await api.get('/auth/user-info');
// setUser(userResponse.data);
// return userResponse.data;
// } catch (error) {
// console.error('Login error:', error);
// throw error;
// }
// };
const logout = () => {
localStorage.removeItem('token');
setUser(null);
};
return (
<AuthContext.Provider value={{ user, isLoading, login, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
+92 -7
View File
@@ -4,17 +4,102 @@ import axios from 'axios';
const API_URL = 'http://localhost:3000';
const api = axios.create({
baseURL: API_URL
baseURL: API_URL,
// withCredentials: true,
});
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
// const api = axios.create({
// baseURL: 'http://localhost:3000',
// });
// Add a request interceptor
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`; // Make sure this matches your backend expectation
}
return config;
},
(error) => {
return Promise.reject(error);
}
return config;
});
);
export const downloadDocument = async (documentId) => {
try {
const response = await api.get(`/documents/download/${documentId}`, {
responseType: 'blob',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
}
});
return response.data;
} catch (error) {
console.error('Download error:', error);
throw error;
}
};
// Request interceptor
// api.interceptors.request.use((config) => {
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
// return config;
// });
// // Response interceptor
// api.interceptors.response.use(
// (response) => response,
// (error) => {
// if (error.response?.status === 401) {
// localStorage.removeItem('token');
// }
// return Promise.reject(error);
// }
// );
// const api = axios.create({
// baseURL: API_URL,
// withCredentials: true,
// });
// api.interceptors.request.use((config) => {
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
// return config;
// });
// export const createUser = (userData) => api.post('/admin/users', userData);
// const api = axios.create({
// baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
// withCredentials: true,
// });
// // Request interceptor
// api.interceptors.request.use((config) => {
// const token = localStorage.getItem('token');
// if (token) {
// config.headers.Authorization = `Bearer ${token}`;
// }
// return config;
// });
// // Response interceptor
// api.interceptors.response.use(
// (response) => response,
// (error) => {
// if (error.response?.status === 401) {
// localStorage.removeItem('token');
// window.location.href = '/login';
// }
// return Promise.reject(error);
// }
// );
export const createUser = (userData) => {
return api.post('/admin/users', {
name: userData.name,