Document_track/src/App.
jsx
import React, { useEffect, useState } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate, useNavigate } from 'react-router-
dom';
import Header from './Header.jsx';
import Footer from './Footer.jsx';
import Home from './pages/Home.jsx';
import LoginPage from './pages/LoginPage.jsx';
import DashboardPage from './pages/DashboardPage.jsx';
import AdminDashboard from './components/Dashboard/AdminDashboard.jsx';
import UploadPage from './pages/UploadPage.jsx';
import AlertsPage from './pages/AlertsPage.jsx';
import './index.css';
function App() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
fetch('http://localhost:5000/profile', {
headers: { Authorization: `Bearer ${token}` },
})
.then((res) => res.json())
.then((data) => {
if (data.username) setUser(data);
})
.finally(() => setLoading(false));
} else {
setLoading(false);
}, []);
const handleLogout = () => {
localStorage.removeItem('token');
setUser(null);
window.location.href = '/';
};
if (loading) return <div>Loading...</div>;
return (
<Router>
<div className="App">
<Header user={user} onLogout={handleLogout} />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/dashboard" element={user ? <AdminDashboard /> : <Navigate
to="/login" />} />
<Route path="/upload" element={user ? <UploadPage /> : <Navigate to="/login" />} />
<Route path="/alerts" element={user ? <AlertsPage /> : <Navigate to="/login" />} />
</Routes>
<Footer />
</div>
</Router>
);
export default App;
Document_track/src/Pages/DashboardPage.jsx
import React, { useEffect, useState } from 'react';
import AdminDashboard from '../components/Dashboard/AdminDashboard';
import UserDashboard from '../components/Dashboard/UserDashboard';
const DashboardPage = () => {
const [role, setRole] = useState(null);
useEffect(() => {
const fetchRole = async () => {
const token = localStorage.getItem('token');
const res = await fetch('http://localhost:5000/get-role', {
headers: { Authorization: `Bearer ${token}` },
});
const data = await res.json();
setRole(data.role);
};
fetchRole();
}, []);
if (!role) return <p>Loading...</p>;
return role === 'admin' ? <AdminDashboard /> : <UserDashboard />;
};
export default DashboardPage;
Document_track/src/components/Doashboard/AdminDashboard.jsx
import React, { useEffect, useState } from 'react';
import './Dashboard.css';
const AdminDashboard = () => {
const [docs, setDocs] = useState([]);
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
useEffect(() => {
fetch('http://localhost:5000/documents', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
})
.then(res => res.json())
.then(setDocs);
}, []);
const toggleVisibility = async (id) => {
await fetch(`http://localhost:5000/documents/${id}/toggle-visibility`, {
method: 'PATCH',
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
});
setDocs(prev => prev.map(d => d.id === id ? { ...d, visible: !d.visible } : d));
};
const filteredDocs = docs.filter(doc => {
const matchesSearch = doc.name.toLowerCase().includes(search.toLowerCase());
const matchesStatus = statusFilter === 'all' || doc.status === statusFilter;
return matchesSearch && matchesStatus;
});
const total = docs.length;
const activeCount = docs.filter(d => d.status === 'active').length;
const expiredCount = docs.filter(d => d.status === 'expired').length;
return (
<div className="dashboard-container">
<h2>Admin Dashboard</h2>
<div className="summary-cards">
<div className="card total">Total: {total}</div>
<div className="card active">Active: {activeCount}</div>
<div className="card expired">Expired: {expiredCount}</div>
</div>
<div className="filter-section">
<input
type="text"
placeholder="Search documents..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<select value={statusFilter} onChange={(e) => setStatusFilter(e.target.value)}>
<option value="all">All</option>
<option value="active">Active</option>
<option value="expired">Expired</option>
</select>
</div>
<table className="dashboard-table">
<thead>
<tr>
<th>Name</th>
<th>Expiry</th>
<th>Status</th>
<th>Visible</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{filteredDocs.map(doc => (
<tr key={doc.id}>
<td>{doc.name}</td>
<td>{doc.expiry_date}</td>
<td>
<span className={`status-tag ${doc.status}`}>{doc.status}</span>
</td>
<td>{doc.visible ? 'Yes' : 'No'}</td>
<td>
<button onClick={() => toggleVisibility(doc.id)}>
Toggle
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default AdminDashboard;
Document_track/src/components/Dashboard/UserDashboard.jsx
import React, { useEffect, useState } from 'react';
import './Dashboard.css';
const UserDashboard = () => {
const [docs, setDocs] = useState([]);
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
useEffect(() => {
fetch('http://localhost:5000/documents', {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
})
.then(res => res.json())
.then(setDocs);
}, []);
const filteredDocs = docs.filter(doc => {
const matchesSearch = doc.name.toLowerCase().includes(search.toLowerCase());
const matchesStatus = statusFilter === 'all' || doc.status === statusFilter;
return matchesSearch && matchesStatus;
});
const total = docs.length;
const activeCount = docs.filter(d => d.status === 'active').length;
const expiredCount = docs.filter(d => d.status === 'expired').length;
return (
<div className="dashboard-container">
<h2>My Documents</h2>
<div className="summary-cards">
<div className="card total">Total: {total}</div>
<div className="card active">Active: {activeCount}</div>
<div className="card expired">Expired: {expiredCount}</div>
</div>
<div className="filter-section">
<input
type="text"
placeholder="Search documents..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<select value={statusFilter} onChange={(e) => setStatusFilter(e.target.value)}>
<option value="all">All</option>
<option value="active">Active</option>
<option value="expired">Expired</option>
</select>
</div>
<table className="dashboard-table">
<thead>
<tr>
<th>Name</th>
<th>Expiry</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{filteredDocs.map(doc => (
<tr key={doc.id}>
<td>{doc.name}</td>
<td>{doc.expiry_date}</td>
<td>
<span className={`status-tag ${doc.status}`}>{doc.status}</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default UserDashboard;
Document_track/src/components/Login/Login.jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import './Login.css';
const Login = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const [attemptsLeft, setAttemptsLeft] = useState(5);
const [showChangePassword, setShowChangePassword] = useState(false);
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const navigate = useNavigate();
const handleLogin = async (e) => {
e.preventDefault();
setError('');
setSuccess('');
try {
const response = await fetch('http://localhost:5000/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
const data = await response.json();
if (response.ok) {
localStorage.setItem('token', data.token);
navigate('/dashboard'); // Adjust route if needed
} else {
setError(data.message || 'Login failed');
if (data.attemptsLeft !== undefined) {
setAttemptsLeft(data.attemptsLeft);
} catch (err) {
setError('Network error. Please try again.');
};
const handleChangePassword = async (e) => {
e.preventDefault();
setError('');
setSuccess('');
if (newPassword !== confirmPassword) {
setError("New passwords don't match");
return;
try {
const response = await fetch('http://localhost:5000/change-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`,
},
body: JSON.stringify({ currentPassword, newPassword }),
});
const data = await response.json();
if (response.ok) {
setSuccess('Password changed successfully!');
setShowChangePassword(false);
setCurrentPassword('');
setNewPassword('');
setConfirmPassword('');
} else {
setError(data.message || 'Password change failed');
} catch (err) {
setError('Network error. Please try again.');
};
return (
<div className="login-container">
<h2>Login to Document Tracker</h2>
{error && <div className="error-message">{error}</div>}
{success && <div className="success-message">{success}</div>}
{attemptsLeft < 5 && (
<div className="attempts-warning">
Attempts left today: {attemptsLeft}
</div>
)}
{!showChangePassword ? (
<form onSubmit={handleLogin}>
<div className="form-group">
<label>Employee ID:</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="login-button">
Login
</button>
<button
type="button"
className="change-password-button"
onClick={() => setShowChangePassword(true)}
>
Change Password
</button>
</form>
):(
<form onSubmit={handleChangePassword}>
<h3>Change Password</h3>
<div className="form-group">
<label>Current Password:</label>
<input
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>New Password:</label>
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
/>
</div>
<div className="form-group">
<label>Confirm New Password:</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
</div>
<button type="submit" className="login-button">
Submit
</button>
<button
type="button"
className="cancel-button"
onClick={() => {
setShowChangePassword(false);
setError('');
setSuccess('');
}}
>
Cancel
</button>
</form>
)}
</div>
);
};
export default Login;
Document_track/src/pages/LoginPage.jsx
import React from 'react';
import Login from '../components/Login/Login';
const LoginPage = () => {
return (
<div className="login-page-container">
<Login />
</div>
);
};
export default LoginPage;
Document_track/backend/auth.py
from flask import Flask, request, jsonify
from flask_cors import CORS
from werkzeug.security import check_password_hash
import os
from werkzeug.utils import secure_filename
from database import create_connection, verify_token
from datetime import date, timedelta
from database import (
create_connection, get_user_by_username, get_user_by_id, create_user,
update_user_password,
record_login_attempt, get_login_attempts_count, generate_token,
verify_token
app = Flask(__name__)
CORS(app) # Enable Cross-Origin requests
MAX_ATTEMPTS_PER_DAY = 5
@app.route('/login', methods=['POST'])
def login():
data = request.get_json()
username = data.get('username')
password = data.get('password')
ip_address = request.remote_addr
user = get_user_by_username(username)
if not user:
return jsonify({'message': 'Invalid username or password', 'attemptsLeft':
MAX_ATTEMPTS_PER_DAY}), 401
# SECURE: Compare hashed password
if not check_password_hash(user['password_hash'], password):
record_login_attempt(user['id'], ip_address, False)
attempts = get_login_attempts_count(username, ip_address)
return jsonify({
'message': 'Invalid username or password',
'attemptsLeft': max(0, MAX_ATTEMPTS_PER_DAY - attempts)
}), 401
record_login_attempt(user['id'], ip_address, True)
token = generate_token(user['id'])
return jsonify({
'message': 'Login successful',
'token': token
})
@app.route('/change-password', methods=['POST'])
def change_password():
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return jsonify({'message': 'Missing or invalid token'}), 401
token = auth_header.split(' ')[1]
user_id = verify_token(token)
if not user_id:
return jsonify({'message': 'Invalid or expired token'}), 401
data = request.get_json()
current_password = data.get('currentPassword')
new_password = data.get('newPassword')
user = get_user_by_id(user_id)
if not user:
return jsonify({'message': 'User not found'}), 404
if not check_password_hash(user['password_hash'], current_password):
return jsonify({'message': 'Current password is incorrect'}), 401
# Hash the new password before storing (secure)
from werkzeug.security import generate_password_hash
new_password_hash = generate_password_hash(new_password)
if update_user_password(user['id'], new_password_hash):
return jsonify({'message': 'Password changed successfully'})
else:
return jsonify({'message': 'Failed to change password'}), 500
@app.route('/documents', methods=['GET'])
def get_documents():
token = request.headers.get('Authorization', '').split(' ')[-1]
user_id = verify_token(token)
if not user_id:
return jsonify({'message': 'Unauthorized'}), 401
today = date.today()
conn = create_connection()
cursor = conn.cursor(dictionary=True)
# Update status in DB before fetching
cursor.execute("UPDATE documents SET status = 'expired' WHERE expiry_date < %s",
(today,))
cursor.execute("UPDATE documents SET status = 'active' WHERE expiry_date >= %s",
(today,))
conn.commit()
user = get_user_by_id(user_id)
if user['role'] == 'admin':
cursor.execute("""
SELECT id, name, expiry_date, visible, status
FROM documents
""")
else:
cursor.execute("""
SELECT id, name, expiry_date, status
FROM documents
WHERE visible = TRUE
""")
documents = cursor.fetchall()
cursor.close()
conn.close()
return jsonify(documents)
@app.route('/documents/<int:doc_id>/toggle-visibility', methods=['PATCH'])
def toggle_visibility(doc_id):
token = request.headers.get('Authorization', '').split(' ')[-1]
user_id = verify_token(token)
if not user_id:
return jsonify({'message': 'Unauthorized'}), 401
user = get_user_by_id(user_id)
if user['role'] != 'admin':
return jsonify({'message': 'Forbidden'}), 403
conn = create_connection()
cursor = conn.cursor()
cursor.execute("UPDATE documents SET visible = NOT visible WHERE id = %s", (doc_id,))
conn.commit()
cursor.close()
conn.close()
return jsonify({'message': 'Visibility toggled'})
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'pdf', 'docx', 'jpg', 'png'}
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_document():
token = request.headers.get('Authorization', '').replace('Bearer ', '')
user_id = verify_token(token)
if not user_id:
return jsonify({'message': 'Unauthorized'}), 401
file = request.files.get('file')
name = request.form.get('name')
description = request.form.get('description')
expiry_date = request.form.get('expiry_date')
if not file or not allowed_file(file.filename):
return jsonify({'message': 'Invalid or missing file'}), 400
filename = secure_filename(file.filename)
file_path = os.path.join(UPLOAD_FOLDER, filename)
file.save(file_path)
conn = create_connection()
cursor = conn.cursor()
cursor.execute("""
INSERT INTO documents (user_id, name, description, expiry_date)
VALUES (%s, %s, %s, %s)
""", (user_id, name, description, expiry_date))
conn.commit()
cursor.close()
conn.close()
return jsonify({'message': 'Document uploaded successfully'}), 200
@app.route('/alerts', methods=['GET'])
def get_expiring_documents():
token = request.headers.get('Authorization', '').split(' ')[-1]
user_id = verify_token(token)
if not user_id:
return jsonify({'message': 'Unauthorized'}), 401
today = date.today()
next_week = today + timedelta(days=7)
user = get_user_by_id(user_id)
conn = create_connection()
cursor = conn.cursor(dictionary=True)
if user['role'] == 'admin':
cursor.execute("""
SELECT id, name, expiry_date
FROM documents
WHERE expiry_date BETWEEN %s AND %s
""", (today, next_week))
else:
cursor.execute("""
SELECT id, name, expiry_date
FROM documents
WHERE visible = TRUE AND expiry_date BETWEEN %s AND %s
""", (today, next_week))
results = cursor.fetchall()
cursor.close()
conn.close()
return jsonify(results)
if __name__ == '__main__':
app.run(debug=True, port=5000)