everything works!
This commit is contained in:
1 parent
01017bb36e
commit
a32992b17b
19 files changed
+1554
-727
No files matched your search
@@ -271,5 +271,714 @@ function AdminPanel() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default AdminPanel;
|
||||
|
||||
export default AdminPanel;
|
||||
// import { useState, useEffect } from 'react';
|
||||
// import { useAuth } from '../../hooks/useAuth';
|
||||
// import { format } from 'date-fns';
|
||||
// import { FiUpload, FiUsers, FiFile, FiTrash2, FiShare2, FiLoader, FiSearch, FiChevronRight } from 'react-icons/fi';
|
||||
// import api from '../../services/api';
|
||||
|
||||
// function AdminPanel() {
|
||||
// const [documents, setDocuments] = useState([]);
|
||||
// const [users, setUsers] = useState([]);
|
||||
// const [loading, setLoading] = useState(true);
|
||||
// const [error, setError] = useState('');
|
||||
// const [selectedFile, setSelectedFile] = useState(null);
|
||||
// const [uploadTitle, setUploadTitle] = useState('');
|
||||
// const [selectedUser, setSelectedUser] = useState('');
|
||||
// const [searchTerm, setSearchTerm] = useState('');
|
||||
// const [expandedUsers, setExpandedUsers] = useState({});
|
||||
|
||||
// useEffect(() => {
|
||||
// fetchData();
|
||||
// }, []);
|
||||
|
||||
// const fetchData = async () => {
|
||||
// try {
|
||||
// setLoading(true);
|
||||
// const [documentsRes, usersRes] = await Promise.all([
|
||||
// api.get('/admin/documents'),
|
||||
// api.get('/admin/users')
|
||||
// ]);
|
||||
|
||||
// console.log('Documents response:', documentsRes.data);
|
||||
// console.log('Users response:', usersRes.data);
|
||||
|
||||
// // Group documents by user
|
||||
// const groupedDocs = groupDocumentsByUser(documentsRes.data || []);
|
||||
// setDocuments(groupedDocs);
|
||||
// setUsers(usersRes.data || []);
|
||||
// } catch (err) {
|
||||
// setError('Failed to fetch data');
|
||||
// console.error('Error:', err);
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
// const groupDocumentsByUser = (docs) => {
|
||||
// return docs.reduce((acc, doc) => {
|
||||
// const userName = doc.sharedWith?.name || 'Unassigned';
|
||||
// if (!acc[userName]) {
|
||||
// acc[userName] = [];
|
||||
// }
|
||||
// acc[userName].push(doc);
|
||||
// return acc;
|
||||
// }, {});
|
||||
// };
|
||||
|
||||
// const toggleUser = (userName) => {
|
||||
// setExpandedUsers(prev => ({
|
||||
// ...prev,
|
||||
// [userName]: !prev[userName]
|
||||
// }));
|
||||
// };
|
||||
|
||||
// const handleFileUpload = async (e) => {
|
||||
// e.preventDefault();
|
||||
// if (!selectedFile || !uploadTitle || !selectedUser) {
|
||||
// alert('Please fill in all fields');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const formData = new FormData();
|
||||
// formData.append('file', selectedFile);
|
||||
// formData.append('title', uploadTitle);
|
||||
// formData.append('sharedWith', selectedUser);
|
||||
|
||||
// try {
|
||||
// await api.post('/admin/documents', formData, {
|
||||
// headers: {
|
||||
// 'Content-Type': 'multipart/form-data',
|
||||
// },
|
||||
// });
|
||||
|
||||
// setSelectedFile(null);
|
||||
// setUploadTitle('');
|
||||
// setSelectedUser('');
|
||||
// fetchData();
|
||||
// alert('Document uploaded successfully');
|
||||
// } catch (err) {
|
||||
// console.error('Upload error:', err);
|
||||
// alert('Failed to upload document');
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleDelete = async (documentId) => {
|
||||
// if (!window.confirm('Are you sure you want to delete this document?')) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// try {
|
||||
// await api.delete(`/admin/documents/${documentId}`);
|
||||
// fetchData();
|
||||
// alert('Document deleted successfully');
|
||||
// } catch (err) {
|
||||
// console.error('Delete error:', err);
|
||||
// alert('Failed to delete document');
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleShare = async (documentId) => {
|
||||
// const userId = prompt('Enter user ID to share with:');
|
||||
// if (!userId) return;
|
||||
|
||||
// try {
|
||||
// await api.post(`/admin/documents/${documentId}/share`, { userId });
|
||||
// fetchData();
|
||||
// alert('Document shared successfully');
|
||||
// } catch (err) {
|
||||
// console.error('Share error:', err);
|
||||
// alert('Failed to share document');
|
||||
// }
|
||||
// };
|
||||
|
||||
// if (loading) {
|
||||
// return (
|
||||
// <div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
// <div className="flex items-center space-x-3 text-blue-500">
|
||||
// <FiLoader className="w-6 h-6 animate-spin" />
|
||||
// <span className="text-lg font-medium">Loading admin panel...</span>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (error) {
|
||||
// return (
|
||||
// <div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
// <div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg text-red-200">
|
||||
// {error}
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 p-6">
|
||||
// <div className="max-w-7xl mx-auto">
|
||||
// <header className="mb-8">
|
||||
// <h1 className="text-3xl font-bold text-white mb-2">Admin Dashboard</h1>
|
||||
// <p className="text-gray-400">Manage documents and users</p>
|
||||
// </header>
|
||||
|
||||
// {/* Upload Section - remains the same */}
|
||||
// <div className="bg-white/5 backdrop-blur-lg rounded-xl p-6 mb-8">
|
||||
// {/* ... upload form content remains the same ... */}
|
||||
// </div>
|
||||
|
||||
// {/* Search Bar */}
|
||||
// <div className="relative mb-6">
|
||||
// <FiSearch className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
|
||||
// <input
|
||||
// type="text"
|
||||
// placeholder="Search documents..."
|
||||
// value={searchTerm}
|
||||
// onChange={(e) => setSearchTerm(e.target.value)}
|
||||
// className="w-full pl-10 pr-4 py-3 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white placeholder-gray-400"
|
||||
// />
|
||||
// </div>
|
||||
|
||||
// {/* Documents List - Grouped by User */}
|
||||
// <div className="space-y-4">
|
||||
// {Object.entries(documents).map(([userName, userDocs]) => {
|
||||
// const filteredDocs = userDocs.filter(doc =>
|
||||
// doc.title.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
// );
|
||||
|
||||
// if (filteredDocs.length === 0) return null;
|
||||
|
||||
// return (
|
||||
// <div
|
||||
// key={userName}
|
||||
// className="bg-white/5 backdrop-blur-lg rounded-xl overflow-hidden"
|
||||
// >
|
||||
// <button
|
||||
// onClick={() => toggleUser(userName)}
|
||||
// className="w-full px-6 py-4 flex items-center justify-between text-white hover:bg-white/5 transition-colors"
|
||||
// >
|
||||
// <div className="flex items-center space-x-3">
|
||||
// <FiUsers className="w-5 h-5 text-blue-400" />
|
||||
// <span className="font-medium">{userName}</span>
|
||||
// <span className="text-sm text-gray-400">({filteredDocs.length} documents)</span>
|
||||
// </div>
|
||||
// <FiChevronRight
|
||||
// className={`w-5 h-5 transition-transform duration-200 ${
|
||||
// expandedUsers[userName] ? 'rotate-90' : ''
|
||||
// }`}
|
||||
// />
|
||||
// </button>
|
||||
|
||||
// {expandedUsers[userName] && (
|
||||
// <div className="border-t border-gray-700 divide-y divide-gray-700">
|
||||
// {filteredDocs.map(doc => (
|
||||
// <div
|
||||
// key={doc.id}
|
||||
// className="px-6 py-4 flex items-center justify-between hover:bg-white/5"
|
||||
// >
|
||||
// <div className="flex items-center space-x-4">
|
||||
// <FiFile className="w-5 h-5 text-gray-400" />
|
||||
// <div>
|
||||
// <h3 className="text-white font-medium">{doc.title}</h3>
|
||||
// <p className="text-sm text-gray-400">
|
||||
// Added: {format(new Date(doc.createdAt), 'MMM dd, yyyy')}
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <button
|
||||
// onClick={() => handleShare(doc.id)}
|
||||
// className="p-2 text-gray-400 hover:text-blue-400 rounded-lg hover:bg-blue-500/10 transition-colors"
|
||||
// title="Share document"
|
||||
// >
|
||||
// <FiShare2 className="w-4 h-4" />
|
||||
// </button>
|
||||
// <button
|
||||
// onClick={() => handleDelete(doc.id)}
|
||||
// className="p-2 text-gray-400 hover:text-red-400 rounded-lg hover:bg-red-500/10 transition-colors"
|
||||
// title="Delete document"
|
||||
// >
|
||||
// <FiTrash2 className="w-4 h-4" />
|
||||
// </button>
|
||||
// </div>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// );
|
||||
// })}
|
||||
|
||||
// {Object.keys(documents).length === 0 && (
|
||||
// <div className="text-center py-12 text-gray-400">
|
||||
// No documents available
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
|
||||
// }
|
||||
|
||||
// export default AdminPanel;
|
||||
// import { useState, useEffect } from 'react';
|
||||
// import { useAuth } from '../../hooks/useAuth';
|
||||
// import { format } from 'date-fns';
|
||||
// import {
|
||||
// FiUpload,
|
||||
// FiDownload,
|
||||
// FiUsers,
|
||||
// FiFile,
|
||||
// FiTrash2,
|
||||
// FiShare2,
|
||||
// FiLoader,
|
||||
// FiSearch,
|
||||
// FiChevronRight,
|
||||
// FiUserPlus,
|
||||
// FiEdit2,
|
||||
// FiKey
|
||||
// } from 'react-icons/fi';
|
||||
// import api from '../../services/api';
|
||||
|
||||
// function AdminPanel() {
|
||||
// const [documents, setDocuments] = useState({});
|
||||
// const [users, setUsers] = useState([]);
|
||||
// const [expandedUsers, setExpandedUsers] = useState({});
|
||||
// const [loading, setLoading] = useState(true);
|
||||
// const [error, setError] = useState('');
|
||||
// const [selectedFile, setSelectedFile] = useState(null);
|
||||
// const [uploadTitle, setUploadTitle] = useState('');
|
||||
// const [selectedUser, setSelectedUser] = useState('');
|
||||
// const [searchTerm, setSearchTerm] = useState('');
|
||||
// const [activeTab, setActiveTab] = useState('documents'); // 'documents' or 'users'
|
||||
// const [newUser, setNewUser] = useState({ username: '', password: '', name: '', isAdmin: false });
|
||||
|
||||
// useEffect(() => {
|
||||
// fetchData();
|
||||
// }, []);
|
||||
|
||||
// const fetchData = async () => {
|
||||
// try {
|
||||
// setLoading(true);
|
||||
// const [documentsRes, usersRes] = await Promise.all([
|
||||
// api.get('/admin/documents'),
|
||||
// api.get('/admin/users')
|
||||
// ]);
|
||||
|
||||
// const groupedDocs = groupDocumentsByUser(documentsRes.data || []);
|
||||
// const initialExpandedState = Object.keys(groupedDocs).reduce((acc, userName) => {
|
||||
// acc[userName] = false;
|
||||
// return acc;
|
||||
// }, {});
|
||||
|
||||
// setDocuments(groupedDocs);
|
||||
// setUsers(usersRes.data || []);
|
||||
// setExpandedUsers(initialExpandedState);
|
||||
// } catch (err) {
|
||||
// setError('Failed to fetch data');
|
||||
// console.error('Error:', err);
|
||||
// } finally {
|
||||
// setLoading(false);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const groupDocumentsByUser = (docs) => {
|
||||
// return docs.reduce((acc, doc) => {
|
||||
// const userName = doc.sharedWith?.name || 'Unassigned';
|
||||
// if (!acc[userName]) {
|
||||
// acc[userName] = [];
|
||||
// }
|
||||
// acc[userName].push(doc);
|
||||
// return acc;
|
||||
// }, {});
|
||||
// };
|
||||
|
||||
// const handleAddUser = async (e) => {
|
||||
// e.preventDefault();
|
||||
// try {
|
||||
// await api.post('/admin/users', newUser);
|
||||
// setNewUser({ username: '', password: '', name: '', isAdmin: false });
|
||||
// fetchData();
|
||||
// alert('User added successfully');
|
||||
// } catch (err) {
|
||||
// console.error('Error adding user:', err);
|
||||
// alert('Failed to add user');
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleDeleteUser = async (userId) => {
|
||||
// if (!window.confirm('Are you sure you want to delete this user?')) return;
|
||||
// try {
|
||||
// await api.delete(`/admin/users/${userId}`);
|
||||
// fetchData();
|
||||
// alert('User deleted successfully');
|
||||
// } catch (err) {
|
||||
// console.error('Error deleting user:', err);
|
||||
// alert('Failed to delete user');
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleResetPassword = async (userId) => {
|
||||
// const newPassword = prompt('Enter new password:');
|
||||
// if (!newPassword) return;
|
||||
|
||||
// try {
|
||||
// await api.post(`/admin/users/${userId}/reset-password`, { password: newPassword });
|
||||
// alert('Password reset successfully');
|
||||
// } catch (err) {
|
||||
// console.error('Error resetting password:', err);
|
||||
// alert('Failed to reset password');
|
||||
// }
|
||||
// };
|
||||
|
||||
// const handleFileUpload = async (e) => {
|
||||
// e.preventDefault();
|
||||
// if (!selectedFile || !uploadTitle || !selectedUser) {
|
||||
// alert('Please fill in all fields');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const formData = new FormData();
|
||||
// formData.append('file', selectedFile);
|
||||
// formData.append('title', uploadTitle);
|
||||
// formData.append('sharedWith', selectedUser);
|
||||
|
||||
// try {
|
||||
// await api.post('/admin/documents', formData, {
|
||||
// headers: {
|
||||
// 'Content-Type': 'multipart/form-data',
|
||||
// },
|
||||
// });
|
||||
|
||||
// setSelectedFile(null);
|
||||
// setUploadTitle('');
|
||||
// setSelectedUser('');
|
||||
// fetchData();
|
||||
// alert('Document uploaded successfully');
|
||||
// } catch (err) {
|
||||
// console.error('Upload error:', err);
|
||||
// alert('Failed to upload document');
|
||||
// }
|
||||
// };
|
||||
// const toggleUser = (userName) => {
|
||||
// console.log('Toggling user:', userName); // Debug log
|
||||
// setExpandedUsers(prev => ({
|
||||
// ...prev,
|
||||
// [userName]: !prev[userName]
|
||||
// }));
|
||||
// };
|
||||
|
||||
|
||||
|
||||
// const handleDownload = async (s3Key, fileName) => {
|
||||
// try {
|
||||
// const token = localStorage.getItem('token');
|
||||
// // Remove 'documents/' from the s3Key as it's already in the path
|
||||
// const cleanS3Key = s3Key.replace('documents/', '');
|
||||
|
||||
// const response = await fetch(`http://localhost:3000/admin/documents/download/${cleanS3Key}`, {
|
||||
// headers: {
|
||||
// 'Authorization': `Bearer ${token}`
|
||||
// }
|
||||
// });
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error('Download failed');
|
||||
// }
|
||||
|
||||
// const blob = await response.blob();
|
||||
// const url = window.URL.createObjectURL(blob);
|
||||
// const link = document.createElement('a');
|
||||
// link.href = url;
|
||||
// link.download = fileName;
|
||||
// document.body.appendChild(link);
|
||||
// link.click();
|
||||
// document.body.removeChild(link);
|
||||
// window.URL.revokeObjectURL(url);
|
||||
// } catch (err) {
|
||||
// console.error('Error downloading document:', err);
|
||||
// alert('Failed to download document. Please try again.');
|
||||
// }
|
||||
// };
|
||||
|
||||
|
||||
// if (loading) {
|
||||
// return (
|
||||
// <div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
// <div className="flex items-center space-x-3 text-blue-500">
|
||||
// <FiLoader className="w-6 h-6 animate-spin" />
|
||||
// <span className="text-lg font-medium">Loading admin panel...</span>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 p-6">
|
||||
// <div className="max-w-7xl mx-auto mt-20 text-center">
|
||||
// <header className="mb-8">
|
||||
// <h1 className="text-3xl font-bold text-white mb-2">Admin Dashboard</h1>
|
||||
// <p className="text-gray-400">Manage documents and users</p>
|
||||
// </header>
|
||||
|
||||
// {/* Tab Navigation */}
|
||||
// <div className="flex space-x-4 mb-8">
|
||||
// <button
|
||||
// onClick={() => setActiveTab('documents')}
|
||||
// className={`px-4 py-2 rounded-lg transition-colors ${
|
||||
// activeTab === 'documents'
|
||||
// ? 'bg-blue-600 text-white'
|
||||
// : 'text-gray-400 hover:text-white'
|
||||
// }`}
|
||||
// >
|
||||
// Documents
|
||||
// </button>
|
||||
// <button
|
||||
// onClick={() => setActiveTab('users')}
|
||||
// className={`px-4 py-2 rounded-lg transition-colors ${
|
||||
// activeTab === 'users'
|
||||
// ? 'bg-blue-600 text-white'
|
||||
// : 'text-gray-400 hover:text-white'
|
||||
// }`}
|
||||
// >
|
||||
// Users
|
||||
// </button>
|
||||
// </div>
|
||||
|
||||
// {activeTab === 'documents' ? (
|
||||
// <>
|
||||
// {/* Upload Section */}
|
||||
// <div className="bg-white/5 backdrop-blur-lg rounded-xl p-6 mb-8">
|
||||
// <h2 className="text-xl font-semibold text-white mb-4">Upload Document</h2>
|
||||
// <form onSubmit={handleFileUpload} className="space-y-4">
|
||||
// <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-400 mb-1">Document Title</label>
|
||||
// <input
|
||||
// type="text"
|
||||
// value={uploadTitle}
|
||||
// onChange={(e) => setUploadTitle(e.target.value)}
|
||||
// className="w-full px-4 py-2 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white placeholder-gray-400"
|
||||
// placeholder="Enter document title"
|
||||
// />
|
||||
// </div>
|
||||
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-400 mb-1">Share With User</label>
|
||||
// <select
|
||||
// value={selectedUser}
|
||||
// onChange={(e) => setSelectedUser(e.target.value)}
|
||||
// className="w-full px-4 py-2 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white"
|
||||
// >
|
||||
// <option value="">Select user</option>
|
||||
// {Array.isArray(users) && users.map(user => (
|
||||
// <option key={user.id} value={user.id}>{user.name}</option>
|
||||
// ))}
|
||||
// </select>
|
||||
// </div>
|
||||
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-400 mb-1">File</label>
|
||||
// <input
|
||||
// type="file"
|
||||
// onChange={(e) => setSelectedFile(e.target.files[0])}
|
||||
// className="w-full px-4 py-2 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-500 file:text-white hover:file:bg-blue-600"
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <button
|
||||
// type="submit"
|
||||
// className="w-full md:w-auto px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors duration-200 flex items-center justify-center space-x-2"
|
||||
// >
|
||||
// <FiUpload className="w-4 h-4" />
|
||||
// <span>Upload Document</span>
|
||||
// </button>
|
||||
// </form>
|
||||
// </div>
|
||||
|
||||
// {/* Documents List */}
|
||||
// <div className="space-y-4">
|
||||
// {Object.entries(documents).map(([userName, userDocs]) => (
|
||||
// <div
|
||||
// key={userName}
|
||||
// className="bg-white/5 backdrop-blur-lg rounded-xl overflow-hidden"
|
||||
// >
|
||||
// <button
|
||||
// onClick={() => toggleUser(userName)}
|
||||
// className="w-full px-6 py-4 flex items-center justify-between text-white hover:bg-white/5 transition-colors"
|
||||
// >
|
||||
// <div className="flex items-center space-x-3">
|
||||
// <FiUsers className="w-5 h-5 text-blue-400" />
|
||||
// <span className="font-medium">{userName}</span>
|
||||
// <span className="text-sm text-gray-400">({userDocs.length} documents)</span>
|
||||
// </div>
|
||||
// <FiChevronRight
|
||||
// className={`w-5 h-5 transition-transform duration-200 ${
|
||||
// expandedUsers[userName] ? 'rotate-90' : ''
|
||||
// }`}
|
||||
// />
|
||||
// </button>
|
||||
// {expandedUsers[userName] && (
|
||||
// <div className="border-t border-gray-700 divide-y divide-gray-700">
|
||||
// {userDocs.map(doc => (
|
||||
// <div
|
||||
// key={doc.id}
|
||||
// className="px-6 py-4 flex items-center justify-between hover:bg-white/5"
|
||||
// >
|
||||
// <div className="flex items-center space-x-4">
|
||||
// <FiFile className="w-5 h-5 text-gray-400" />
|
||||
// <div>
|
||||
// <h3 className="text-white font-medium">{doc.title}</h3>
|
||||
// <p className="text-sm text-gray-400">
|
||||
// Added: {format(new Date(doc.createdAt), 'MMM dd, yyyy')}
|
||||
// </p>
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <button
|
||||
// onClick={() => handleDownload(doc.s3Key, doc.title)}
|
||||
// className="p-2 text-gray-400 hover:text-blue-400 rounded-lg hover:bg-blue-500/10 transition-colors"
|
||||
// title="Download document"
|
||||
// >
|
||||
// <FiDownload className="w-4 h-4" />
|
||||
// </button>
|
||||
// <button
|
||||
// onClick={() => handleShare(doc.id)}
|
||||
// className="p-2 text-gray-400 hover:text-blue-400 rounded-lg hover:bg-blue-500/10 transition-colors"
|
||||
// title="Share document"
|
||||
// >
|
||||
// <FiShare2 className="w-4 h-4" />
|
||||
// </button>
|
||||
// <button
|
||||
// onClick={() => handleDelete(doc.id)}
|
||||
// className="p-2 text-gray-400 hover:text-red-400 rounded-lg hover:bg-red-500/10 transition-colors"
|
||||
// title="Delete document"
|
||||
// >
|
||||
// <FiTrash2 className="w-4 h-4" />
|
||||
// </button>
|
||||
// </div>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// </>
|
||||
// ) : (
|
||||
// // Users Management Section
|
||||
// <div className="space-y-8">
|
||||
// {/* Add New User Form */}
|
||||
// <div className="bg-white/5 backdrop-blur-lg rounded-xl p-6">
|
||||
// <h2 className="text-xl font-semibold text-white mb-4">Add New User</h2>
|
||||
// <form onSubmit={handleAddUser} className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-400 mb-1">Username</label>
|
||||
// <input
|
||||
// type="text"
|
||||
// value={newUser.username}
|
||||
// onChange={(e) => setNewUser({ ...newUser, username: e.target.value })}
|
||||
// className="w-full px-4 py-2 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white"
|
||||
// required
|
||||
// />
|
||||
// </div>
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-400 mb-1">Password</label>
|
||||
// <input
|
||||
// type="password"
|
||||
// value={newUser.password}
|
||||
// onChange={(e) => setNewUser({ ...newUser, password: e.target.value })}
|
||||
// className="w-full px-4 py-2 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white"
|
||||
// required
|
||||
// />
|
||||
// </div>
|
||||
// <div>
|
||||
// <label className="block text-sm font-medium text-gray-400 mb-1">Full Name</label>
|
||||
// <input
|
||||
// type="text"
|
||||
// value={newUser.name}
|
||||
// onChange={(e) => setNewUser({ ...newUser, name: e.target.value })}
|
||||
// className="w-full px-4 py-2 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white"
|
||||
// required
|
||||
// />
|
||||
// </div>
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <input
|
||||
// type="checkbox"
|
||||
// id="isAdmin"
|
||||
// checked={newUser.isAdmin}
|
||||
// onChange={(e) => setNewUser({ ...newUser, isAdmin: e.target.checked })}
|
||||
// className="w-4 h-4 rounded border-gray-700 text-blue-600 focus:ring-blue-500"
|
||||
// />
|
||||
// <label htmlFor="isAdmin" className="text-sm font-medium text-gray-400">
|
||||
// Admin User
|
||||
// </label>
|
||||
// </div>
|
||||
// <div className="md:col-span-2">
|
||||
// <button
|
||||
// type="submit"
|
||||
// className="w-full md:w-auto px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors duration-200 flex items-center justify-center space-x-2"
|
||||
// >
|
||||
// <FiUserPlus className="w-4 h-4" />
|
||||
// <span>Add User</span>
|
||||
// </button>
|
||||
// </div>
|
||||
// </form>
|
||||
// </div>
|
||||
|
||||
// {/* Users List */}
|
||||
// <div className="bg-white/5 backdrop-blur-lg rounded-xl overflow-hidden">
|
||||
// <div className="px-6 py-4 border-b border-gray-700">
|
||||
// <h2 className="text-xl font-semibold text-white">Users</h2>
|
||||
// </div>
|
||||
// <div className="divide-y divide-gray-700">
|
||||
// {Array.isArray(users) && users.map(user => (
|
||||
// <div
|
||||
// key={user.id}
|
||||
// className="px-6 py-4 flex items-center justify-between hover:bg-white/5"
|
||||
// >
|
||||
// <div className="flex items-center space-x-4">
|
||||
// <FiUsers className="w-5 h-5 text-gray-400" />
|
||||
// <div>
|
||||
// <h3 className="text-white font-medium">{user.name}</h3>
|
||||
// <p className="text-sm text-gray-400">@{user.username}</p>
|
||||
// </div>
|
||||
// {user.isAdmin && (
|
||||
// <span className="px-2 py-1 text-xs font-medium text-blue-400 bg-blue-400/10 rounded-full">
|
||||
// Admin
|
||||
// </span>
|
||||
// )}
|
||||
// </div>
|
||||
|
||||
// <div className="flex items-center space-x-2">
|
||||
// <button
|
||||
// onClick={() => handleResetPassword(user.id)}
|
||||
// className="p-2 text-gray-400 hover:text-blue-400 rounded-lg hover:bg-blue-500/10 transition-colors"
|
||||
// title="Reset password"
|
||||
// >
|
||||
// <FiKey className="w-4 h-4" />
|
||||
// </button>
|
||||
// <button
|
||||
// onClick={() => handleDeleteUser(user.id)}
|
||||
// className="p-2 text-gray-400 hover:text-red-400 rounded-lg hover:bg-red-500/10 transition-colors"
|
||||
// title="Delete user"
|
||||
// >
|
||||
// <FiTrash2 className="w-4 h-4" />
|
||||
// </button>
|
||||
// </div>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// export default AdminPanel;
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
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';
|
||||
import { format } from 'date-fns';
|
||||
import { FiFolder, FiFile, FiDownload, FiChevronRight, FiLoader } from 'react-icons/fi';
|
||||
import { downloadDocument } from '../../services/api';
|
||||
|
||||
function Dashboard() {
|
||||
const [documents, setDocuments] = useState([]);
|
||||
const [documents, setDocuments] = useState({});
|
||||
const [expandedFolders, setExpandedFolders] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState('');
|
||||
@@ -16,23 +15,29 @@ function Dashboard() {
|
||||
useEffect(() => {
|
||||
const fetchDocuments = async () => {
|
||||
try {
|
||||
if (!user?.id) return;
|
||||
|
||||
const response = await getSharedDocuments(user.id);
|
||||
// Group the documents by company and date
|
||||
const groupedDocs = groupDocumentsByCompanyAndDate(response.data);
|
||||
setDocuments(groupedDocs);
|
||||
} catch (err) {
|
||||
setError('Failed to fetch documents');
|
||||
console.error('Error fetching documents:', err);
|
||||
setError('Failed to fetch documents');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (user) {
|
||||
fetchDocuments();
|
||||
}
|
||||
fetchDocuments();
|
||||
}, [user]);
|
||||
|
||||
const groupDocumentsByCompanyAndDate = (docs) => {
|
||||
if (!Array.isArray(docs)) {
|
||||
console.error('Expected array of documents, received:', docs);
|
||||
return {};
|
||||
}
|
||||
|
||||
return docs.reduce((acc, doc) => {
|
||||
const folderName = `${doc.sharedWith?.name || 'Unknown'}-${format(new Date(doc.createdAt), 'yyyy-MM-dd')}`;
|
||||
if (!acc[folderName]) {
|
||||
@@ -43,52 +48,16 @@ function Dashboard() {
|
||||
}, {});
|
||||
};
|
||||
|
||||
// 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 handleDownload = async (s3Key, fileName) => {
|
||||
try {
|
||||
await downloadDocument(s3Key);
|
||||
} 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 toggleFolder = (folderName) => {
|
||||
const toggleFolder = (folderName) => {
|
||||
setExpandedFolders(prev => ({
|
||||
...prev,
|
||||
[folderName]: !prev[folderName]
|
||||
@@ -97,94 +66,87 @@ const toggleFolder = (folderName) => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-screen">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
<div className="flex items-center space-x-3 text-blue-500">
|
||||
<FiLoader className="w-6 h-6 animate-spin" />
|
||||
<span className="text-lg font-medium">Loading documents...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="text-center text-red-600 p-4">
|
||||
{error}
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-900">
|
||||
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-lg text-red-200">
|
||||
{error}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1 className="text-2xl font-bold mb-6">Your Laboratory Reports</h1>
|
||||
|
||||
{Object.entries(documents).length === 0 ? (
|
||||
<p className="text-gray-600">No documents have been shared with you yet.</p>
|
||||
) : (
|
||||
<div className="grid gap-4">
|
||||
{Object.entries(documents).map(([folderName, folderDocs]) => (
|
||||
<motion.div
|
||||
key={folderName}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="bg-white p-4 rounded-lg shadow-md"
|
||||
>
|
||||
<button
|
||||
onClick={() => toggleFolder(folderName)}
|
||||
className="w-full flex items-center text-left p-2 hover:bg-gray-50 rounded-md"
|
||||
>
|
||||
<FiFolder className="text-blue-600 mr-2" />
|
||||
{expandedFolders[folderName] ? (
|
||||
<FiChevronDown className="mr-2" />
|
||||
) : (
|
||||
<FiChevronRight className="mr-2" />
|
||||
)}
|
||||
<span className="font-medium">{folderName}</span>
|
||||
<span className="ml-2 text-sm text-gray-500">
|
||||
({folderDocs.length} {folderDocs.length === 1 ? 'report' : 'reports'})
|
||||
</span>
|
||||
</button>
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 p-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<header className="mb-8 mt-20">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Your Documents</h1>
|
||||
<p className="text-gray-400">Access and manage your shared documents</p>
|
||||
</header>
|
||||
|
||||
{expandedFolders[folderName] && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
className="mt-2 space-y-2 pl-8"
|
||||
<div className="grid gap-6">
|
||||
{Object.entries(documents).length > 0 ? (
|
||||
Object.entries(documents).map(([folderName, docs]) => (
|
||||
<div
|
||||
key={folderName}
|
||||
className="bg-white/5 backdrop-blur-lg rounded-xl overflow-hidden transition-all duration-200 hover:bg-white/10"
|
||||
>
|
||||
<button
|
||||
onClick={() => toggleFolder(folderName)}
|
||||
className="w-full px-6 py-4 flex items-center justify-between text-white hover:bg-white/5 transition-colors"
|
||||
>
|
||||
{folderDocs.map((doc) => (
|
||||
<div
|
||||
key={doc.id}
|
||||
className="bg-white p-6 rounded-lg shadow-md hover:shadow-lg transition-shadow"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<h2 className="text-xl font-semibold">{doc.title}</h2>
|
||||
<span className={`px-2 py-1 text-xs rounded-full ${
|
||||
doc.status === 'completed'
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-yellow-100 text-yellow-800'
|
||||
}`}>
|
||||
{doc.status}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-600">
|
||||
<p>Created: {new Date(doc.createdAt).toLocaleDateString()}</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<button
|
||||
onClick={() => handleDownload(doc.s3Key, doc.title)}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition-colors"
|
||||
<div className="flex items-center space-x-3">
|
||||
<FiFolder className="w-5 h-5 text-blue-400" />
|
||||
<span className="font-medium">{folderName}</span>
|
||||
<span className="text-sm text-gray-400">({docs.length} files)</span>
|
||||
</div>
|
||||
<FiChevronRight
|
||||
className={`w-5 h-5 transition-transform duration-200 ${
|
||||
expandedFolders[folderName] ? 'rotate-90' : ''
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{expandedFolders[folderName] && (
|
||||
<div className="border-t border-gray-700">
|
||||
{docs.map((doc) => (
|
||||
<div
|
||||
key={doc.id}
|
||||
className="px-6 py-3 flex items-center justify-between hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<FiFile className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-200">{doc.title}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleDownload(doc.s3Key)}
|
||||
className="p-2 text-gray-400 hover:text-blue-400 rounded-lg hover:bg-blue-500/10 transition-colors"
|
||||
title="Download document"
|
||||
>
|
||||
<FiDownload className="inline-block mr-2" />
|
||||
Download
|
||||
<FiDownload className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-400">No documents available</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { uploadDocument, getAllUsers } from '../../services/api';
|
||||
import { uploadDocument, getAllUsers, getUserInfo } from '../../services/api';
|
||||
|
||||
function DocumentUpload() {
|
||||
const [file, setFile] = useState(null);
|
||||
@@ -8,9 +8,11 @@ function DocumentUpload() {
|
||||
const [availableUsers, setAvailableUsers] = useState([]);
|
||||
const [status, setStatus] = useState('idle'); // idle, uploading, completed, failed
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [currentUser, setCurrentUser] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUsers();
|
||||
getCurrentUser();
|
||||
}, []);
|
||||
|
||||
const fetchUsers = async () => {
|
||||
@@ -22,18 +24,22 @@ function DocumentUpload() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
const selectedFile = event.target.files[0];
|
||||
setFile(selectedFile);
|
||||
const getCurrentUser = async () => {
|
||||
try {
|
||||
const response = await getUserInfo();
|
||||
console.log('Current user data:', response.data); // Debug log
|
||||
setCurrentUser(response.data);
|
||||
} catch (error) {
|
||||
console.error('Failed to get current user:', error);
|
||||
setErrorMessage('Failed to get current user info');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
console.log('Form submission:', { file, title, selectedUsers });
|
||||
|
||||
if (!file || !title || selectedUsers.length === 0) {
|
||||
setErrorMessage('Please provide a title, file, and select at least one user');
|
||||
if (!file || !title || selectedUsers.length === 0 || !currentUser) {
|
||||
setErrorMessage('Please provide all required information');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,15 +49,18 @@ const handleSubmit = async (event) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('title', title);
|
||||
formData.append('sharedWith', selectedUsers[0]);
|
||||
formData.append('sharedWithId', selectedUsers[0]); // Remove toString()
|
||||
formData.append('uploadedById', currentUser.id); // Remove toString()
|
||||
|
||||
// Debug log
|
||||
console.log('Form Data Contents:', {
|
||||
file: formData.get('file'),
|
||||
title: formData.get('title'),
|
||||
sharedWithId: formData.get('sharedWithId'),
|
||||
uploadedById: formData.get('uploadedById')
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('Sending request with:', {
|
||||
title,
|
||||
sharedWith: selectedUsers[0],
|
||||
fileSize: file.size
|
||||
});
|
||||
|
||||
const response = await uploadDocument(formData);
|
||||
console.log('Upload response:', response);
|
||||
|
||||
@@ -66,6 +75,17 @@ const handleSubmit = async (event) => {
|
||||
setErrorMessage(error.response?.data?.message || 'Upload failed');
|
||||
}
|
||||
};
|
||||
|
||||
function handleFileChange(e) {
|
||||
setFile(e.target.files[0]);
|
||||
}
|
||||
|
||||
const handleUserSelect = (e) => {
|
||||
const values = Array.from(e.target.selectedOptions, option => option.value);
|
||||
console.log('Selected user values:', values);
|
||||
setSelectedUsers(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md mx-auto mt-8 p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 className="text-2xl font-bold mb-6">Upload Document</h2>
|
||||
@@ -103,7 +123,7 @@ const handleSubmit = async (event) => {
|
||||
<select
|
||||
multiple
|
||||
value={selectedUsers}
|
||||
onChange={(e) => setSelectedUsers(Array.from(e.target.selectedOptions, option => option.value))}
|
||||
onChange={handleUserSelect}
|
||||
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
required
|
||||
>
|
||||
|
||||
@@ -1,9 +1,90 @@
|
||||
// import { useState } from 'react';
|
||||
// import { useAuth } from '../../hooks/useAuth';
|
||||
// import { useNavigate } from 'react-router-dom';
|
||||
|
||||
// const Login = () => {
|
||||
// const [username, setUsername] = useState(''); // Changed from email to username
|
||||
// const [password, setPassword] = useState('');
|
||||
// const [error, setError] = useState('');
|
||||
// const { login } = useAuth();
|
||||
// const navigate = useNavigate();
|
||||
|
||||
// const handleSubmit = async (e) => {
|
||||
// e.preventDefault();
|
||||
// setError('');
|
||||
|
||||
// try {
|
||||
// const userData = await login(username, password);
|
||||
// console.log('Login result:', userData);
|
||||
// if (userData?.isAdmin) {
|
||||
// navigate('/admin');
|
||||
// } else {
|
||||
// navigate('/dashboard');
|
||||
// }
|
||||
// } catch (err) {
|
||||
// setError('Invalid credentials');
|
||||
// }
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <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>
|
||||
// </div>
|
||||
// <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>
|
||||
// <input
|
||||
// type="text"
|
||||
// required
|
||||
// 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>
|
||||
// <div>
|
||||
// <input
|
||||
// type="password"
|
||||
// required
|
||||
// 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>
|
||||
|
||||
// <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"
|
||||
// >
|
||||
// Sign in
|
||||
// </button>
|
||||
// </div>
|
||||
// </form>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default Login;
|
||||
import { useState } from 'react';
|
||||
import { useAuth } from '../../hooks/useAuth';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FiUser, FiLock } from 'react-icons/fi'; // Import icons
|
||||
|
||||
const Login = () => {
|
||||
const [username, setUsername] = useState(''); // Changed from email to username
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const { login } = useAuth();
|
||||
@@ -15,8 +96,7 @@ const Login = () => {
|
||||
|
||||
try {
|
||||
const userData = await login(username, password);
|
||||
console.log('Login result:', userData);
|
||||
if (userData?.isAdmin) {
|
||||
if (userData?.isAdmin) {
|
||||
navigate('/admin');
|
||||
} else {
|
||||
navigate('/dashboard');
|
||||
@@ -27,35 +107,39 @@ const Login = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<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>
|
||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800">
|
||||
<div className="w-full max-w-md px-8 py-10 bg-white/5 backdrop-blur-lg rounded-2xl shadow-2xl">
|
||||
<div className="mb-10 text-center">
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Welcome Back</h1>
|
||||
<p className="text-gray-400">Please sign in to continue</p>
|
||||
</div>
|
||||
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{error && (
|
||||
<div className="text-red-500 text-center text-sm">
|
||||
<div className="p-3 text-sm text-red-200 bg-red-500/10 border border-red-500/20 rounded-lg">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<div className="rounded-md shadow-sm -space-y-px">
|
||||
<div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="relative">
|
||||
<FiUser className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
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"
|
||||
className="w-full px-10 py-3 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white placeholder-gray-400 transition-colors"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<div className="relative">
|
||||
<FiLock className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
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"
|
||||
className="w-full px-10 py-3 bg-white/5 border border-gray-700 rounded-lg focus:outline-none focus:border-blue-500 text-white placeholder-gray-400 transition-colors"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
@@ -63,13 +147,17 @@ const Login = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors duration-200 flex items-center justify-center focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-900"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-gray-400">
|
||||
Need help? Contact your administrator
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -22,27 +22,50 @@ api.interceptors.request.use(
|
||||
}
|
||||
);
|
||||
|
||||
export const downloadDocument = async (documentId) => {
|
||||
export const getSharedDocuments = (userId) => {
|
||||
return api.get(`/documents/shared/${userId}`);
|
||||
};
|
||||
|
||||
|
||||
const getToken = () => localStorage.getItem('token');
|
||||
export const downloadDocument = async (key) => {
|
||||
try {
|
||||
const response = await api.get(`/documents/download/${documentId}`, {
|
||||
const response = await api.get(`/documents/download/${encodeURIComponent(key)}`, {
|
||||
responseType: 'blob',
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
'Accept': 'application/octet-stream',
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
|
||||
// Create blob URL and trigger download
|
||||
const blob = new Blob([response.data]);
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
|
||||
// Extract filename from key
|
||||
const fileName = key.split('/').pop();
|
||||
link.download = fileName;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Download error:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const createUser = (userData) => {
|
||||
return api.post('/admin/users', {
|
||||
name: userData.name,
|
||||
email: userData.email,
|
||||
password: userData.password,
|
||||
isAdmin: userData.isAdmin // Add this line
|
||||
isAdmin: userData.isAdmin
|
||||
});
|
||||
};
|
||||
export const login = (username, password) => api.post('/auth/login', { username, password });
|
||||
@@ -51,15 +74,21 @@ export const updateDocumentStatus = (documentId, status) => api.put(`/admin/docu
|
||||
|
||||
export const uploadDocument = async (formData) => {
|
||||
try {
|
||||
// Debug log
|
||||
console.log('Sending to server:', {
|
||||
title: formData.get('title'),
|
||||
sharedWithId: formData.get('sharedWithId'),
|
||||
uploadedById: formData.get('uploadedById')
|
||||
});
|
||||
|
||||
const response = await api.post('/admin/documents', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
console.log('Upload API response:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Upload API error:', error);
|
||||
console.error('API Error:', error.response?.data);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -67,7 +96,7 @@ export const getUserInfo = () => api.get('/auth/user-info');
|
||||
|
||||
|
||||
export const getAllDocuments = () => api.get('/admin/documents');
|
||||
export const getSharedDocuments = (userId) => api.get(`/documents/shared/${userId}`);
|
||||
// export const getSharedDocuments = (userId) => api.get(`/documents/shared/${userId}`);
|
||||
export const getAllUsers = () => api.get('/admin/users');
|
||||
|
||||
export default api;
|
||||
Reference in new issue
Block a user