Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 36 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,52 @@
name: CI Pipeline
name: Backend CI Pipeline

on:
push:
branches: [ develop ]
branches:
- main
- develop

pull_request:
branches: [ develop ]
branches:
- main
- develop

jobs:
build-and-test:
backend-ci:
runs-on: ubuntu-latest

defaults:
run:
working-directory: server

env:
NODE_ENV: test
PORT: 5000
DATABASE_URL: postgresql://test:test@localhost:5432/testdb
JWT_SECRET: github-actions-secret

steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: 22
cache: npm
cache-dependency-path: server/package-lock.json

- name: Install dependencies
run: npm install
- name: Install Dependencies
run: npm ci

- name: Run Tests (Placeholder)
# Instead of running 'npm test', we just echo to pass the step
run: |
echo "Test suite is currently under development."
echo "Skipping formal test execution."
exit 0
- name: TypeScript Build
run: npm run build

- name: Unit Tests
run: npm run test:unit

- name: Integration Tests
run: npm run test:integration

- name: Coverage Report
run: npm run test:coverage
2 changes: 1 addition & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Library Management System</title>
</head>
Expand Down
7 changes: 7 additions & 0 deletions client/public/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
123 changes: 123 additions & 0 deletions client/src/features/admin/components/AdminLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React from "react";
import { Outlet, NavLink, useNavigate } from "react-router-dom";
import { useAuthStore } from "../../../store/authStore";
import { motion } from "framer-motion";

// Lucide Icons matching your clean, balanced stroke layouts
import {
LayoutDashboard,
Users,
ShieldAlert,
LogOut,
Library,
User
} from "lucide-react";

export const AdminLayout: React.FC = () => {
const { user, logout } = useAuthStore();
const navigate = useNavigate();

const handleSignOut = () => {
logout();
navigate("/login");
};

const navItems = [
{ name: "Admin Dashboard", path: "/admin/dashboard", icon: LayoutDashboard, color: "text-blue-600" },
{ name: "Manage Users", path: "/admin/users", icon: Users, color: "text-amber-700" },
{ name: "Manage Librarians", path: "/admin/librarians", icon: ShieldAlert, color: "text-rose-600" },
];

return (
<div className="flex h-screen w-screen overflow-hidden bg-amber-50/40 font-sans text-slate-800 antialiased selection:bg-amber-200">

{/* Sidebar Navigation - Warm Archival Minimalist Layout (Matching 72 width) */}
<aside className="w-72 bg-white border-r border-amber-100 flex flex-col justify-between relative z-20 shadow-xs shrink-0">
<div className="p-6">
<div className="flex items-center gap-3.5 py-3 border-b border-slate-100 mb-6">

{/* Elegant Institutional Identity Icon */}
<div className="w-10 h-10 bg-slate-900 text-amber-100 rounded-lg flex items-center justify-center shadow-xs shrink-0">
<Library size={20} className="stroke-[2.2]" />
</div>

<div className="flex flex-col">
<span className="font-bold text-base tracking-tight text-slate-900 leading-tight">LMS</span>
<span className="text-xs text-slate-400 font-bold uppercase tracking-wider mt-0.5">Admin Portal</span>
</div>
</div>

{/* Navigation links optimized with comfortable typography scales & indicators */}
<nav className="space-y-1.5">
{navItems.map((item) => {
const IconComponent = item.icon;
return (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
`flex items-center gap-4 px-4 py-3.5 rounded-lg text-sm font-semibold tracking-wide transition-all duration-150 ${
isActive
? "bg-slate-100 text-slate-950 font-bold shadow-xs border-l-4 border-slate-900 rounded-l-none pl-3"
: "text-slate-600 hover:bg-slate-50 hover:text-slate-950"
}`
}
>
<IconComponent size={18} className={`stroke-[2.2] shrink-0 ${item.color}`} />
<span>{item.name}</span>
</NavLink>
);
})}
</nav>
</div>

{/* Sidebar Footer - Spacious Action Deck matches exactly */}
<div className="p-5 border-t border-slate-100 bg-slate-50/60">
<button
onClick={handleSignOut}
className="flex w-full items-center justify-center gap-2.5 px-4 py-3 rounded-lg text-sm font-bold text-orange-700 bg-orange-50 hover:bg-orange-100 border border-orange-200/60 transition-all cursor-pointer shadow-2xs"
>
<LogOut size={16} className="stroke-[2.5]" />
<span>Logout System</span>
</button>
</div>
</aside>

{/* Main Structural Area */}
<div className="flex-1 flex flex-col overflow-hidden">

{/* Header Frame - Formatted with identical Height h-22 and Side Padding */}
<header className="h-22 bg-white border-b border-amber-100 flex items-center justify-between px-10 shadow-2xs shrink-0">
<div>
<h1 className="text-lg font-bold text-slate-900 tracking-tight">Admin Dashboard</h1>
<p className="text-sm text-slate-400 font-medium mt-0.5">Core directory controls and active network administration</p>
</div>

<div className="flex items-center gap-5">
<div className="text-right hidden sm:block">
<p className="text-xs text-slate-400 font-medium tracking-wide uppercase mt-1">
ROLE: <span className="text-rose-600 font-bold">{user?.role || "ADMIN"}</span>
</p>
</div>

{/* User Profile Avatar Frame */}
<div className="w-11 h-11 bg-slate-50 border border-slate-200/60 text-slate-700 rounded-lg flex items-center justify-center shadow-2xs">
<User size={18} className="stroke-[2.2]" />
</div>
</div>
</header>

{/* Content View Injection Portal using your clean transition specs */}
<main className="flex-1 overflow-y-auto p-10 bg-transparent">
<motion.div
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
>
<Outlet />
</motion.div>
</main>
</div>
</div>
);
};
73 changes: 73 additions & 0 deletions client/src/features/admin/components/DeleteLibrarianProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";

interface DeleteLibrarianProfileProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
librarianName: string;
isPending: boolean;
}

export const DeleteLibrarianProfile: React.FC<DeleteLibrarianProfileProps> = ({
isOpen,
onClose,
onConfirm,
librarianName,
isPending,
}) => {
if (!isOpen) return null;

return (
<div className="fixed inset-0 bg-slate-900/40 backdrop-blur-xs flex items-center justify-center z-50 p-4 font-sans">
{/* Container: Matches the clean off-white base with soft linen-amber border */}
<div className="bg-white rounded-2xl shadow-xl w-full max-w-sm p-6 border border-amber-100/80 animate-zoom-in">

<div className="text-center">
{/* Header: Crisp text-base alignment with deep slate-ink tone */}
<h3 className="text-base font-bold text-slate-900 tracking-tight">
Revoke Administrative Access
</h3>

{/* Main Paragraph: Structured at text-sm slate-600 for high editorial legibility */}
<p className="text-sm text-slate-600 mt-4 leading-relaxed">
Are you completely confident about stripping{" "}
<strong className="text-slate-900 font-bold">"{librarianName}"</strong> of all administrative privileges and access layers?
</p>

{/* Callout Block: Shipped with premium cream/rose warning surface tokens */}
<div className="text-xs text-rose-900 font-medium mt-5 bg-rose-50/60 border border-rose-100 p-4 rounded-xl text-left leading-relaxed">
<span className="font-bold uppercase tracking-wider block mb-1 text-rose-950 text-[11px]">
⚠️ Critical Reminder:
</span>
This action completely cleanses their system profile registry. They will instantly lose terminal authentication rights and management access across the network.
</div>
</div>

{/* Modal Action Footers - Crisp Touchpoints */}
<div className="mt-6 flex justify-end gap-3 pt-5 border-t border-slate-100 text-xs font-bold tracking-wide">

{/* Cancel/Abort Button */}
<button
type="button"
onClick={onClose}
disabled={isPending}
className="px-4 py-3 bg-slate-50 border border-slate-200 text-slate-700 rounded-xl transition-all hover:bg-slate-100 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
Abort
</button>

{/* Action Button: Dark editorial signature signature button block */}
<button
type="button"
onClick={onConfirm}
disabled={isPending}
className="px-4 py-3 bg-slate-900 hover:bg-slate-800 text-amber-50 rounded-xl transition-all cursor-pointer shadow-sm disabled:bg-slate-700 disabled:cursor-not-allowed min-w-32.5 text-center"
>
{isPending ? "Revoking..." : "Confirm Deletion"}
</button>

</div>
</div>
</div>
);
};
73 changes: 73 additions & 0 deletions client/src/features/admin/components/DeleteUserModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";

interface DeleteUserModalProps {
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
userName: string;
isPending: boolean;
}

export const DeleteUserModal: React.FC<DeleteUserModalProps> = ({
isOpen,
onClose,
onConfirm,
userName,
isPending,
}) => {
if (!isOpen) return null;

return (
<div className="fixed inset-0 bg-slate-900/40 backdrop-blur-xs flex items-center justify-center z-50 p-4 font-sans">
{/* Container: Matches the clean off-white base with soft linen-amber border */}
<div className="bg-white rounded-2xl shadow-xl w-full max-w-sm p-6 border border-amber-100/80 animate-zoom-in">

<div className="text-center">
{/* Header: Crisp text-base alignment with deep slate-ink tone */}
<h3 className="text-base font-bold text-slate-900 tracking-tight">
Confirm User Account Purge
</h3>

{/* Main Paragraph: Structured at text-sm slate-600 for high editorial legibility */}
<p className="text-sm text-slate-600 mt-4 leading-relaxed">
Are you sure you want to completely delete the system profile record for{" "}
<strong className="text-slate-900 font-bold">"{userName}"</strong> from the library management core engine?
</p>

{/* Callout Block: Shipped with premium cream/rose warning surface tokens */}
<div className="text-xs text-rose-900 font-medium mt-5 bg-rose-50/60 border border-rose-100 p-4 rounded-xl text-left leading-relaxed">
<span className="font-bold uppercase tracking-wider block mb-1 text-rose-950 text-[11px]">
⚠️ Irreversible Action:
</span>
This process instantly flushes out their system user registry parameters, active resource rentals, tracking workflows, and systemic archival logs completely.
</div>
</div>

{/* Modal Action Footers - Crisp Touchpoints */}
<div className="mt-6 flex justify-end gap-3 pt-5 border-t border-slate-100 text-xs font-bold tracking-wide">

{/* Cancel Button */}
<button
type="button"
onClick={onClose}
disabled={isPending}
className="px-4 py-3 bg-slate-50 border border-slate-200 text-slate-700 rounded-xl transition-all hover:bg-slate-100 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
>
Cancel
</button>

{/* Action Button: Dark editorial signature signature button block */}
<button
type="button"
onClick={onConfirm}
disabled={isPending}
className="px-4 py-3 bg-slate-900 hover:bg-slate-800 text-amber-50 rounded-xl transition-all cursor-pointer shadow-sm disabled:bg-slate-700 disabled:cursor-not-allowed min-w-30 text-center"
>
{isPending ? "Purging Files..." : "Confirm Delete"}
</button>

</div>
</div>
</div>
);
};
Loading
Loading