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
3 changes: 2 additions & 1 deletion app/src/components/CommentBox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Clock, Pencil, Trash2 } from 'lucide-react';
import { Loader } from './Loader';

interface CommentBoxProps {
comment: {
Expand Down Expand Up @@ -171,7 +172,7 @@ export function CommentBox({
disabled={isBusy || !editingText.trim()}
className="bg-zinc-900 text-white hover:bg-zinc-800 font-semibold text-xs px-4.5 py-1.5 rounded-lg transition disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer flex items-center gap-1.5"
>
{isBusy && <span className="w-3 h-3 border-2 border-white border-t-transparent rounded-full animate-spin" />}
{isBusy && <Loader size="xs" color="white" />}
Save
</button>
</div>
Expand Down
30 changes: 30 additions & 0 deletions app/src/components/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
interface LoaderProps {
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
color?: 'white' | 'dark' | 'orange' | 'rose';
className?: string;
}

export function Loader({ size = 'md', color = 'dark', className = '' }: LoaderProps) {
const sizeClasses = {
xs: 'w-3 h-3 border-2',
sm: 'w-4 h-4 border-2',
md: 'w-8 h-8 border-[3px]',
lg: 'w-12 h-12 border-4',
xl: 'w-16 h-16 border-4',
};

const colorClasses = {
white: 'border-white/20 border-t-white',
dark: 'border-zinc-200 border-t-zinc-800',
orange: 'border-amber-100 border-t-amber-500',
rose: 'border-rose-100 border-t-rose-500',
};

return (
<div
className={`rounded-full animate-spin ${sizeClasses[size]} ${colorClasses[color]} ${className}`}
role="status"
aria-label="loading"
/>
);
}
3 changes: 2 additions & 1 deletion app/src/pages/admin/AEPostView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { AlertCircle, ServerCrash, ClipboardList, GraduationCap, BedDouble, Building2, Zap, Hammer } from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

// ── Types ─────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -200,7 +201,7 @@ export function AEPostView() {
<MainLayout>
<div className="flex-grow flex items-center justify-center bg-gray-50 py-20">
<div className="text-center">
<div className="w-12 h-12 border-4 border-[#ff9900] border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<Loader size="lg" color="orange" className="mx-auto mb-4" />
<p className="text-gray-600 font-semibold">Fetching posts…</p>
</div>
</div>
Expand Down
9 changes: 5 additions & 4 deletions app/src/pages/admin/AdminPostView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { CommentBox } from '../../components/CommentBox';
import { Loader } from '../../components/Loader';

// ── Types ──────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -603,7 +604,7 @@ export function AdminPostView() {
<MainLayout>
<div className="flex-grow flex items-center justify-center bg-white py-20">
<div className="text-center">
<div className="w-12 h-12 border-4 border-[#ff9900] border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<Loader size="lg" color="orange" className="mx-auto mb-4" />
<p className="text-gray-600 font-semibold">Loading post…</p>
</div>
</div>
Expand Down Expand Up @@ -921,7 +922,7 @@ export function AdminPostView() {
className="inline-flex items-center gap-2 text-xs font-semibold text-white bg-zinc-900 hover:bg-zinc-800 px-4 py-2 rounded-lg transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed disabled:pointer-events-none cursor-pointer"
>
{acting ? (
<span className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
<Loader size="xs" color="white" />
) : <MessageSquare className="w-3.5 h-3.5" />}
Post Comment
</button>
Expand Down Expand Up @@ -950,7 +951,7 @@ export function AdminPostView() {
className="inline-flex items-center gap-2 text-xs font-semibold text-white bg-amber-500 hover:bg-amber-600 px-4 py-2 rounded-lg transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed disabled:pointer-events-none cursor-pointer"
>
{acting ? (
<span className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
<Loader size="xs" color="white" />
) : btn.icon}
{btn.label}
</button>
Expand Down Expand Up @@ -1000,7 +1001,7 @@ export function AdminPostView() {
className={`inline-flex items-center gap-2 text-xs font-semibold text-white ${buttonColorClass} px-4 py-2 rounded-lg transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed disabled:pointer-events-none cursor-pointer`}
>
{acting ? (
<span className="w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full animate-spin" />
<Loader size="xs" color="white" />
) : btn.icon}
{btn.label}
</button>
Expand Down
3 changes: 2 additions & 1 deletion app/src/pages/admin/JEPostView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { AlertCircle, ServerCrash, ClipboardList, GraduationCap, BedDouble, Building2, Zap, Hammer } from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

// ── Types ─────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -185,7 +186,7 @@ export function JEPostView() {
<MainLayout>
<div className="flex-grow flex items-center justify-center bg-gray-50 py-20">
<div className="text-center">
<div className="w-12 h-12 border-4 border-[#ff9900] border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<Loader size="lg" color="orange" className="mx-auto mb-4" />
<p className="text-gray-600 font-semibold">Fetching posts…</p>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion app/src/pages/admin/XENPostView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { AlertCircle, ServerCrash, ClipboardList, GraduationCap, BedDouble, Building2, Zap, Hammer } from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

// ── Types ─────────────────────────────────────────────────────────────────────

Expand Down Expand Up @@ -187,7 +188,7 @@ export function XENPostView() {
<MainLayout>
<div className="flex-grow flex items-center justify-center bg-gray-50 py-20">
<div className="text-center">
<div className="w-12 h-12 border-4 border-[#ff9900] border-t-transparent rounded-full animate-spin mx-auto mb-4" />
<Loader size="lg" color="orange" className="mx-auto mb-4" />
<p className="text-gray-600 font-semibold">Fetching posts…</p>
</div>
</div>
Expand Down
8 changes: 2 additions & 6 deletions app/src/pages/auth/AccountResetPass.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Link, useSearchParams, useNavigate } from 'react-router-dom';
import { Eye, EyeOff } from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

const roleToApi: Record<string, string> = {
faculty: '/api/auth/faculty/reset-password',
Expand Down Expand Up @@ -203,12 +204,7 @@ export function AccountResetPass() {
disabled={loading}
className={`inline-flex items-center gap-2 bg-[#16a34a] hover:bg-[#15803d] text-white font-semibold py-2.5 px-8 rounded-lg transition-colors duration-200 text-sm active:scale-[0.98] ${loading ? 'opacity-70 cursor-not-allowed' : 'cursor-pointer'}`}
>
{loading && (
<svg className="animate-spin w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{loading && <Loader size="sm" color="white" />}
{loading ? 'Resetting…' : 'Reset Password'}
</button>
{loginPath && (
Expand Down
8 changes: 2 additions & 6 deletions app/src/pages/auth/CentreHeadForgotPassword.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

export function CentreHeadForgotPassword() {
const [email, setEmail] = useState('');
Expand Down Expand Up @@ -94,12 +95,7 @@ export function CentreHeadForgotPassword() {
disabled={loading || submitted}
className={`inline-flex items-center gap-2 bg-[#16a34a] hover:bg-[#15803d] text-white font-semibold py-2.5 px-8 rounded-lg transition-colors duration-200 text-sm active:scale-[0.98] ${(loading || submitted) ? 'opacity-70 cursor-not-allowed' : 'cursor-pointer'}`}
>
{loading && (
<svg className="animate-spin w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{loading && <Loader size="sm" color="white" />}
{loading ? 'Sending…' : submitted ? 'Link Sent' : 'Send Reset Link'}
</button>
<Link to="/centre-head/login" className="text-sm text-[#666666] hover:text-[#111111] transition-colors cursor-pointer">
Expand Down
8 changes: 2 additions & 6 deletions app/src/pages/auth/CentreHeadLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Eye, EyeOff } from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

export function CentreHeadLogin() {
const [email, setEmail] = useState('');
Expand Down Expand Up @@ -134,12 +135,7 @@ export function CentreHeadLogin() {
disabled={loading}
className={`inline-flex items-center gap-2 bg-[#16a34a] hover:bg-[#15803d] text-white font-semibold py-2.5 px-8 rounded-lg transition-colors duration-200 text-sm active:scale-[0.98] ${loading ? 'opacity-70 cursor-not-allowed' : 'cursor-pointer'}`}
>
{loading && (
<svg className="animate-spin w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{loading && <Loader size="sm" color="white" />}
{loading ? 'Logging in…' : 'Login'}
</button>
<Link to="/centre-head/signup" className="text-sm text-[#666666] hover:text-[#111111] transition-colors cursor-pointer">
Expand Down
8 changes: 2 additions & 6 deletions app/src/pages/auth/CentreHeadSignup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Eye, EyeOff } from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';
import { BUILDINGS } from '../../constants/models';

export function CentreHeadSignup() {
Expand Down Expand Up @@ -191,12 +192,7 @@ export function CentreHeadSignup() {
disabled={loading}
className={`inline-flex items-center gap-2 bg-[#16a34a] hover:bg-[#15803d] text-white font-semibold py-2.5 px-8 rounded-lg transition-colors duration-200 text-sm active:scale-[0.98] ${loading ? 'opacity-70 cursor-not-allowed' : 'cursor-pointer'}`}
>
{loading && (
<svg className="animate-spin w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{loading && <Loader size="sm" color="white" />}
{loading ? 'Registering…' : 'Register as Centre Head'}
</button>
<Link
Expand Down
8 changes: 2 additions & 6 deletions app/src/pages/auth/FacultyForgotPassword.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

export function FacultyForgotPassword() {
const [email, setEmail] = useState('');
Expand Down Expand Up @@ -94,12 +95,7 @@ export function FacultyForgotPassword() {
disabled={loading || submitted}
className={`inline-flex items-center gap-2 bg-[#16a34a] hover:bg-[#15803d] text-white font-semibold py-2.5 px-8 rounded-lg transition-colors duration-200 text-sm active:scale-[0.98] ${(loading || submitted) ? 'opacity-70 cursor-not-allowed' : 'cursor-pointer'}`}
>
{loading && (
<svg className="animate-spin w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{loading && <Loader size="sm" color="white" />}
{loading ? 'Sending…' : submitted ? 'Link Sent' : 'Send Reset Link'}
</button>
<Link to="/faculty/login" className="text-sm text-[#666666] hover:text-[#111111] transition-colors cursor-pointer">
Expand Down
8 changes: 2 additions & 6 deletions app/src/pages/auth/FacultyLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Eye, EyeOff } from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

export function FacultyLogin() {
const [email, setEmail] = useState('');
Expand Down Expand Up @@ -134,12 +135,7 @@ export function FacultyLogin() {
disabled={loading}
className={`inline-flex items-center gap-2 bg-[#16a34a] hover:bg-[#15803d] text-white font-semibold py-2.5 px-8 rounded-lg transition-colors duration-200 text-sm active:scale-[0.98] ${loading ? 'opacity-70 cursor-not-allowed' : 'cursor-pointer'}`}
>
{loading && (
<svg className="animate-spin w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{loading && <Loader size="sm" color="white" />}
{loading ? 'Logging in…' : 'Login'}
</button>
<Link to="/faculty/signup" className="text-sm text-[#666666] hover:text-[#111111] transition-colors cursor-pointer">
Expand Down
8 changes: 2 additions & 6 deletions app/src/pages/auth/FacultySignup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { Eye, EyeOff } from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';
import { DEPARTMENTS, BLOCK_LABELS, BLOCK_TYPES } from '../../constants/models';

export function FacultySignup() {
Expand Down Expand Up @@ -251,12 +252,7 @@ export function FacultySignup() {
disabled={loading}
className={`inline-flex items-center gap-2 bg-[#16a34a] hover:bg-[#15803d] text-white font-semibold py-2.5 px-8 rounded-lg transition-colors duration-200 text-sm active:scale-[0.98] ${loading ? 'opacity-70 cursor-not-allowed' : 'cursor-pointer'}`}
>
{loading && (
<svg className="animate-spin w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{loading && <Loader size="sm" color="white" />}
{loading ? 'Registering…' : 'Register as Faculty'}
</button>
<Link
Expand Down
8 changes: 2 additions & 6 deletions app/src/pages/auth/StaffLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Eye, EyeOff } from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

function dashboardFor(position: string): string {
if (position.startsWith('XEN')) return '/admin/xen';
Expand Down Expand Up @@ -140,12 +141,7 @@ export function StaffLogin() {
disabled={loading}
className={`inline-flex items-center gap-2 bg-[#16a34a] hover:bg-[#15803d] text-white font-semibold py-2.5 px-8 rounded-lg transition-colors duration-200 text-sm active:scale-[0.98] ${loading ? 'opacity-70 cursor-not-allowed' : 'cursor-pointer'}`}
>
{loading && (
<svg className="animate-spin w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
</svg>
)}
{loading && <Loader size="sm" color="white" />}
{loading ? 'Logging in…' : 'Login to Portal'}
</button>
</div>
Expand Down
3 changes: 2 additions & 1 deletion app/src/pages/auth/VerifyAccount.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import { Link, useSearchParams, useNavigate } from 'react-router-dom';
import { MainLayout } from '../../components/layout/MainLayout';
import { Loader } from '../../components/Loader';

type VerifyStatus = 'loading' | 'success' | 'error' | 'no-token';

Expand Down Expand Up @@ -91,7 +92,7 @@ export function VerifyAccount() {
{/* Loading */}
{status === 'loading' && (
<div className="flex flex-col items-center gap-5 text-center">
<div className="w-16 h-16 rounded-full border-4 border-[#E5E5E5] border-t-[#111111] animate-spin" />
<Loader size="xl" color="dark" className="w-16 h-16" />
<p className="text-gray-500 font-medium">Verifying your account, please wait…</p>
</div>
)}
Expand Down
Loading
Loading