diff --git a/app/src/components/CommentBox.tsx b/app/src/components/CommentBox.tsx index 7cfaeee..3504b96 100644 --- a/app/src/components/CommentBox.tsx +++ b/app/src/components/CommentBox.tsx @@ -1,4 +1,5 @@ import { Clock, Pencil, Trash2 } from 'lucide-react'; +import { Loader } from './Loader'; interface CommentBoxProps { comment: { @@ -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 && } + {isBusy && } Save diff --git a/app/src/components/Loader.tsx b/app/src/components/Loader.tsx new file mode 100644 index 0000000..e21ff05 --- /dev/null +++ b/app/src/components/Loader.tsx @@ -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 ( +
+ ); +} diff --git a/app/src/pages/admin/AEPostView.tsx b/app/src/pages/admin/AEPostView.tsx index 774b4e2..008d9a4 100644 --- a/app/src/pages/admin/AEPostView.tsx +++ b/app/src/pages/admin/AEPostView.tsx @@ -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 ───────────────────────────────────────────────────────────────────── @@ -200,7 +201,7 @@ export function AEPostView() {
-
+

Fetching posts…

diff --git a/app/src/pages/admin/AdminPostView.tsx b/app/src/pages/admin/AdminPostView.tsx index fd990fd..c33f522 100644 --- a/app/src/pages/admin/AdminPostView.tsx +++ b/app/src/pages/admin/AdminPostView.tsx @@ -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 ────────────────────────────────────────────────────────────────────── @@ -603,7 +604,7 @@ export function AdminPostView() {
-
+

Loading post…

@@ -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 ? ( - + ) : } Post Comment @@ -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 ? ( - + ) : btn.icon} {btn.label} @@ -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 ? ( - + ) : btn.icon} {btn.label} diff --git a/app/src/pages/admin/JEPostView.tsx b/app/src/pages/admin/JEPostView.tsx index 06b84e8..1e1c155 100644 --- a/app/src/pages/admin/JEPostView.tsx +++ b/app/src/pages/admin/JEPostView.tsx @@ -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 ───────────────────────────────────────────────────────────────────── @@ -185,7 +186,7 @@ export function JEPostView() {
-
+

Fetching posts…

diff --git a/app/src/pages/admin/XENPostView.tsx b/app/src/pages/admin/XENPostView.tsx index 0c93059..e786a83 100644 --- a/app/src/pages/admin/XENPostView.tsx +++ b/app/src/pages/admin/XENPostView.tsx @@ -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 ───────────────────────────────────────────────────────────────────── @@ -187,7 +188,7 @@ export function XENPostView() {
-
+

Fetching posts…

diff --git a/app/src/pages/auth/AccountResetPass.tsx b/app/src/pages/auth/AccountResetPass.tsx index dbbf88e..4ff1d07 100644 --- a/app/src/pages/auth/AccountResetPass.tsx +++ b/app/src/pages/auth/AccountResetPass.tsx @@ -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 = { faculty: '/api/auth/faculty/reset-password', @@ -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 && ( - - - - - )} + {loading && } {loading ? 'Resetting…' : 'Reset Password'} {loginPath && ( diff --git a/app/src/pages/auth/CentreHeadForgotPassword.tsx b/app/src/pages/auth/CentreHeadForgotPassword.tsx index 0bdd1f8..fd08ee4 100644 --- a/app/src/pages/auth/CentreHeadForgotPassword.tsx +++ b/app/src/pages/auth/CentreHeadForgotPassword.tsx @@ -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(''); @@ -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 && ( - - - - - )} + {loading && } {loading ? 'Sending…' : submitted ? 'Link Sent' : 'Send Reset Link'} diff --git a/app/src/pages/auth/CentreHeadLogin.tsx b/app/src/pages/auth/CentreHeadLogin.tsx index cc2c6fe..9dc7936 100644 --- a/app/src/pages/auth/CentreHeadLogin.tsx +++ b/app/src/pages/auth/CentreHeadLogin.tsx @@ -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(''); @@ -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 && ( - - - - - )} + {loading && } {loading ? 'Logging in…' : 'Login'} diff --git a/app/src/pages/auth/CentreHeadSignup.tsx b/app/src/pages/auth/CentreHeadSignup.tsx index e8b8737..74a6f79 100644 --- a/app/src/pages/auth/CentreHeadSignup.tsx +++ b/app/src/pages/auth/CentreHeadSignup.tsx @@ -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() { @@ -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 && ( - - - - - )} + {loading && } {loading ? 'Registering…' : 'Register as Centre Head'} - {loading && ( - - - - - )} + {loading && } {loading ? 'Sending…' : submitted ? 'Link Sent' : 'Send Reset Link'} diff --git a/app/src/pages/auth/FacultyLogin.tsx b/app/src/pages/auth/FacultyLogin.tsx index 122a9a4..c68e0d1 100644 --- a/app/src/pages/auth/FacultyLogin.tsx +++ b/app/src/pages/auth/FacultyLogin.tsx @@ -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(''); @@ -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 && ( - - - - - )} + {loading && } {loading ? 'Logging in…' : 'Login'} diff --git a/app/src/pages/auth/FacultySignup.tsx b/app/src/pages/auth/FacultySignup.tsx index 318a930..45f54f8 100644 --- a/app/src/pages/auth/FacultySignup.tsx +++ b/app/src/pages/auth/FacultySignup.tsx @@ -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() { @@ -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 && ( - - - - - )} + {loading && } {loading ? 'Registering…' : 'Register as Faculty'} - {loading && ( - - - - - )} + {loading && } {loading ? 'Logging in…' : 'Login to Portal'}
diff --git a/app/src/pages/auth/VerifyAccount.tsx b/app/src/pages/auth/VerifyAccount.tsx index f0d5460..5a525b4 100644 --- a/app/src/pages/auth/VerifyAccount.tsx +++ b/app/src/pages/auth/VerifyAccount.tsx @@ -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'; @@ -91,7 +92,7 @@ export function VerifyAccount() { {/* Loading */} {status === 'loading' && (
-
+

Verifying your account, please wait…

)} diff --git a/app/src/pages/auth/WardenForgotPassword.tsx b/app/src/pages/auth/WardenForgotPassword.tsx index 825ac5f..226b8c2 100644 --- a/app/src/pages/auth/WardenForgotPassword.tsx +++ b/app/src/pages/auth/WardenForgotPassword.tsx @@ -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 WardenForgotPassword() { const [email, setEmail] = useState(''); @@ -94,12 +95,7 @@ export function WardenForgotPassword() { 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 && ( - - - - - )} + {loading && } {loading ? 'Sending…' : submitted ? 'Link Sent' : 'Send Reset Link'} diff --git a/app/src/pages/auth/WardenLogin.tsx b/app/src/pages/auth/WardenLogin.tsx index dd7626d..b084622 100644 --- a/app/src/pages/auth/WardenLogin.tsx +++ b/app/src/pages/auth/WardenLogin.tsx @@ -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 WardenLogin() { const [email, setEmail] = useState(''); @@ -134,12 +135,7 @@ export function WardenLogin() { 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 && ( - - - - - )} + {loading && } {loading ? 'Logging in…' : 'Login'} diff --git a/app/src/pages/auth/WardenSignup.tsx b/app/src/pages/auth/WardenSignup.tsx index 36dc736..f969314 100644 --- a/app/src/pages/auth/WardenSignup.tsx +++ b/app/src/pages/auth/WardenSignup.tsx @@ -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 { HOSTELS } from '../../constants/models'; export function WardenSignup() { @@ -191,12 +192,7 @@ export function WardenSignup() { 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 && ( - - - - - )} + {loading && } {loading ? 'Registering…' : 'Register as Warden'} - {loading && ( - - - - - )} + {loading && } {loading ? 'Submitting…' : 'Submit Complaint'}
diff --git a/app/src/pages/post/FacultyPost.tsx b/app/src/pages/post/FacultyPost.tsx index 3349183..cee551c 100644 --- a/app/src/pages/post/FacultyPost.tsx +++ b/app/src/pages/post/FacultyPost.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { MainLayout } from '../../components/layout/MainLayout'; +import { Loader } from '../../components/Loader'; import { POST_PLACES, POST_TYPES } from '../../constants/models'; export function FacultyPost() { @@ -161,12 +162,7 @@ export function FacultyPost() { 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 && ( - - - - - )} + {loading && } {loading ? 'Submitting…' : 'Submit Complaint'}
diff --git a/app/src/pages/post/PostView.tsx b/app/src/pages/post/PostView.tsx index 32b5006..7b8f4ec 100644 --- a/app/src/pages/post/PostView.tsx +++ b/app/src/pages/post/PostView.tsx @@ -8,6 +8,7 @@ import { import { MainLayout } from '../../components/layout/MainLayout'; import { POST_PLACES } from '../../constants/models'; import { CommentBox } from '../../components/CommentBox'; +import { Loader } from '../../components/Loader'; type Role = 'faculty' | 'warden' | 'centrehead'; @@ -159,7 +160,7 @@ export function PostView() {
-
+

Loading complaint details…

@@ -313,7 +314,7 @@ export function PostView() { disabled={isBusy} className="inline-flex items-center gap-1 px-3 py-1.5 bg-emerald-600 hover:bg-emerald-700 text-xs font-semibold text-white rounded-lg transition disabled:opacity-40 cursor-pointer" > - {isBusy ?
: } + {isBusy ? : } Save @@ -611,7 +612,7 @@ export function PostView() { className="inline-flex items-center gap-1.5 text-xs font-semibold text-white bg-zinc-900 hover:bg-zinc-800 disabled:bg-zinc-200 disabled:text-zinc-400 px-4 py-2 rounded-lg transition-all duration-200 disabled:cursor-not-allowed cursor-pointer" > {commentSubmitting && ( - + )} Comment diff --git a/app/src/pages/post/WardenPost.tsx b/app/src/pages/post/WardenPost.tsx index 1385cc6..b81220c 100644 --- a/app/src/pages/post/WardenPost.tsx +++ b/app/src/pages/post/WardenPost.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { MainLayout } from '../../components/layout/MainLayout'; +import { Loader } from '../../components/Loader'; import { POST_TYPES } from '../../constants/models'; export function WardenPost() { @@ -163,12 +164,7 @@ export function WardenPost() { 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 && ( - - - - - )} + {loading && } {loading ? 'Submitting…' : 'Submit Complaint'}
diff --git a/app/src/pages/profile/Profile.tsx b/app/src/pages/profile/Profile.tsx index 5f62f84..5ee478d 100644 --- a/app/src/pages/profile/Profile.tsx +++ b/app/src/pages/profile/Profile.tsx @@ -7,6 +7,7 @@ import { import { MainLayout } from '../../components/layout/MainLayout'; import { ComplaintCard } from '../../components/ComplaintCard'; import type { ComplaintPost, EditForm, Role } from '../../components/ComplaintCard'; +import { Loader } from '../../components/Loader'; interface ProfileData { name?: string; @@ -83,7 +84,7 @@ export function Profile() {
-
+

Loading profile data…

@@ -293,7 +294,7 @@ export function Profile() { {postsLoading && (
-
+ Fetching your complaints…
)}