Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
a84b068
Merge pull request #140 from a-zahi2002/main-dev
Hashini-Herath Jun 19, 2026
770521f
feat: implement authentication system with login/registration pages, …
a-zahi2002 Jul 1, 2026
3802331
Merge pull request #150 from a-zahi2002/Nisal
a-zahi2002 Jul 1, 2026
4a93715
Merge pull request #151 from a-zahi2002/Nisal
a-zahi2002 Jul 1, 2026
16dc8b1
Merge pull request #152 from a-zahi2002/Zahi
a-zahi2002 Jul 1, 2026
959e2f1
Merge pull request #153 from a-zahi2002/main-dev
a-zahi2002 Jul 1, 2026
4386bb7
Merge pull request #154 from a-zahi2002/main-dev
a-zahi2002 Jul 1, 2026
eaba91b
Merge pull request #155 from a-zahi2002/main
a-zahi2002 Jul 1, 2026
f4dd126
feat: implement user registration with pending approval status and se…
a-zahi2002 Jul 1, 2026
b9267d8
Merge branch 'Zahi' of https://github.com/a-zahi2002/Capstone---URMS …
a-zahi2002 Jul 1, 2026
7469946
feat: implement tab-isolated session management and user registration…
a-zahi2002 Jul 1, 2026
80eae63
Merge pull request #156 from a-zahi2002/Zahi
a-zahi2002 Jul 1, 2026
d80c48d
Merge pull request #157 from a-zahi2002/main-dev
a-zahi2002 Jul 1, 2026
6f233fb
Merge pull request #158 from a-zahi2002/main-dev
Oshenkarunathilaka Jul 1, 2026
986dfbc
Merge pull request #159 from a-zahi2002/main-dev
Hashini-Herath Jul 1, 2026
1f1f143
feat: implement complete frontend application architecture including …
a-zahi2002 Jul 2, 2026
b055e45
feat: implement role-based DashboardSidebar component with responsive…
a-zahi2002 Jul 2, 2026
de3e418
feat: implement role-based DashboardSidebar component and Tailwind CS…
a-zahi2002 Jul 2, 2026
8c5b18e
Merge pull request #161 from a-zahi2002/Zahi
a-zahi2002 Jul 2, 2026
78d5960
Merge pull request #162 from a-zahi2002/main-dev
a-zahi2002 Jul 2, 2026
7b1ae6f
feat: implement role-based navigation sidebar and add dedicated user …
a-zahi2002 Jul 2, 2026
1756205
Merge pull request #163 from a-zahi2002/main-dev
Oshenkarunathilaka Jul 5, 2026
8bbbd45
Merge pull request #164 from a-zahi2002/main
Oshenkarunathilaka Aug 7, 2026
ff0391d
feat: implement initial project structure with dashboard, auth protec…
Oshenkarunathilaka Aug 9, 2026
6bb62bf
Merge pull request #165 from a-zahi2002/Oshen
Oshenkarunathilaka Aug 9, 2026
afdeed6
add landing page layout with integrated theme toggle and notification…
Oshenkarunathilaka Aug 9, 2026
1e01bb5
Merge pull request #166 from a-zahi2002/Oshen
Oshenkarunathilaka Aug 9, 2026
62a2204
initialize landing page and layout architecture with core UI components
Oshenkarunathilaka Aug 9, 2026
a48b797
Merge pull request #167 from a-zahi2002/Oshen
Oshenkarunathilaka Aug 9, 2026
7254c79
implement user profile management and notification preference settin…
Oshenkarunathilaka Aug 9, 2026
0c8956f
Merge pull request #168 from a-zahi2002/Oshen
Oshenkarunathilaka Aug 9, 2026
36f715e
Merge pull request #169 from a-zahi2002/main-dev
a-zahi2002 Aug 9, 2026
2db1dbc
Merge branch 'Zahi' into main
a-zahi2002 Aug 11, 2026
aa4e2d1
Merge pull request #171 from a-zahi2002/main
a-zahi2002 Aug 11, 2026
382f377
update Maintenance section interface
Oshenkarunathilaka Aug 11, 2026
5197231
Merge pull request #172 from a-zahi2002/Oshen
Oshenkarunathilaka Aug 11, 2026
960f79c
Merge pull request #173 from a-zahi2002/main-dev
a-zahi2002 Aug 11, 2026
e49641c
edit home page
Oshenkarunathilaka Aug 12, 2026
a38c798
Merge pull request #175 from a-zahi2002/Oshen
Oshenkarunathilaka Aug 12, 2026
f1c3d2b
Update registration form
Oshenkarunathilaka Aug 12, 2026
6893164
Merge pull request #176 from a-zahi2002/Oshen
Oshenkarunathilaka Aug 12, 2026
6e6424e
Merge pull request #177 from a-zahi2002/main-dev
a-zahi2002 Aug 12, 2026
fe10619
Merge pull request #179 from a-zahi2002/main-dev
Hashini-Herath Aug 18, 2026
e171743
Merge pull request #180 from a-zahi2002/Hashini
Hashini-Herath Aug 18, 2026
c44c5e8
Merge pull request #183 from a-zahi2002/main
a-zahi2002 Aug 20, 2026
3978ec8
Add Supabase keep-alive workflow
a-zahi2002 Aug 25, 2026
c454d71
Merge pull request #184 from a-zahi2002/Zahi
a-zahi2002 Aug 25, 2026
9eb7c65
Merge pull request #185 from a-zahi2002/main
a-zahi2002 Aug 25, 2026
380e980
Merge pull request #186 from a-zahi2002/main-dev
a-zahi2002 Aug 25, 2026
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
73 changes: 73 additions & 0 deletions .github/workflows/keep-supabase-alive.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Keep Supabase Alive

on:
schedule:
# Runs every 3 days at 00:00 UTC to prevent 7-day inactivity pause
- cron: '0 0 */3 * *'
workflow_dispatch: # Allows manual trigger anytime from GitHub UI

jobs:
ping-supabase:
name: Ping Supabase Database
runs-on: ubuntu-latest

steps:
- name: Send Keep-Alive Ping to Supabase
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
APP_URL: ${{ secrets.APP_URL }}
run: |
echo "=========================================="
echo " Supabase Keep-Alive Health Ping "
echo "=========================================="

SUCCESS=0

# 1. Ping Supabase REST API directly if secrets exist
if [ -n "$SUPABASE_URL" ] && [ -n "$SUPABASE_ANON_KEY" ]; then
echo "Pinging Supabase REST API: $SUPABASE_URL/rest/v1/users?select=id&limit=1"

RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" \
-H "apikey: $SUPABASE_ANON_KEY" \
-H "Authorization: Bearer $SUPABASE_ANON_KEY" \
"$SUPABASE_URL/rest/v1/users?select=id&limit=1")

HTTP_CODE=$(echo "$RESPONSE" | grep "HTTP_CODE:" | cut -d':' -f2)
BODY=$(echo "$RESPONSE" | grep -v "HTTP_CODE:")

echo "Response Status: $HTTP_CODE"
echo "Response Body: $BODY"

if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 400 ]; then
echo "✅ Supabase REST API successfully pinged!"
SUCCESS=1
else
echo "❌ Direct Supabase REST ping failed with HTTP $HTTP_CODE"
fi
else
echo "⚠️ SUPABASE_URL or SUPABASE_ANON_KEY secret is not set in GitHub repository secrets."
fi

# 2. Ping deployed Next.js Keep-Alive endpoint if APP_URL is provided
if [ -n "$APP_URL" ]; then
echo "------------------------------------------"
echo "Pinging Next.js endpoint: $APP_URL/api/keep-alive"

APP_RESPONSE=$(curl -s -w "\nHTTP_CODE:%{http_code}" "$APP_URL/api/keep-alive")
APP_HTTP_CODE=$(echo "$APP_RESPONSE" | grep "HTTP_CODE:" | cut -d':' -f2)
APP_BODY=$(echo "$APP_RESPONSE" | grep -v "HTTP_CODE:")

echo "App Response Status: $APP_HTTP_CODE"
echo "App Response Body: $APP_BODY"

if [ "$APP_HTTP_CODE" -ge 200 ] && [ "$APP_HTTP_CODE" -lt 400 ]; then
echo "✅ Next.js keep-alive endpoint successfully pinged!"
SUCCESS=1
fi
fi

if [ $SUCCESS -eq 0 ] && [ -z "$SUPABASE_URL" ] && [ -z "$APP_URL" ]; then
echo "❌ ERROR: No secrets configured! Please add SUPABASE_URL & SUPABASE_ANON_KEY or APP_URL to GitHub Repository Secrets."
exit 1
fi
84 changes: 42 additions & 42 deletions app/admin/analytics/page.tsx

Large diffs are not rendered by default.

64 changes: 32 additions & 32 deletions app/admin/analytics/reports/page.tsx

Large diffs are not rendered by default.

172 changes: 136 additions & 36 deletions app/admin/users/page.tsx

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions app/api/keep-alive/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { NextResponse } from "next/server";
import { supabase } from "../../../lib/supabase";

export const dynamic = "force-dynamic";

export async function GET() {
try {
const startTime = Date.now();

// Lightweight query to keep Supabase database active and prevent 7-day inactivity pause
const { count, error } = await supabase
.from("users")
.select("id", { count: "exact", head: true })
.limit(1);

const durationMs = Date.now() - startTime;

if (error) {
console.error("Keep-alive Supabase query error:", error);
return NextResponse.json(
{
status: "error",
message: "Failed to ping Supabase database",
error: error.message,
timestamp: new Date().toISOString(),
},
{ status: 500 }
);
}

return NextResponse.json(
{
status: "ok",
message: "Supabase database keep-alive ping successful",
responseTimeMs: durationMs,
timestamp: new Date().toISOString(),
},
{ status: 200 }
);
} catch (err: any) {
console.error("Unexpected error in keep-alive endpoint:", err);
return NextResponse.json(
{
status: "error",
message: "Unexpected server error during keep-alive ping",
error: err?.message || String(err),
timestamp: new Date().toISOString(),
},
{ status: 500 }
);
}
}
98 changes: 55 additions & 43 deletions app/bookings/page.tsx

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions app/explore/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function ExploreResourcesPage() {
<button
key={cat}
onClick={() => setActiveCategory(cat)}
className={`px-4 py-2 rounded-full text-sm font-semibold whitespace-nowrap transition-all ${
className={`px-4 py-2 rounded-none text-sm font-semibold whitespace-nowrap transition-all ${
activeCategory === cat
? "bg-blue-600 text-white shadow-md shadow-blue-600/30"
: "bg-white text-slate-600 border border-slate-200 hover:border-blue-300 hover:bg-blue-50"
Expand Down Expand Up @@ -118,7 +118,7 @@ export default function ExploreResourcesPage() {
)}

{!loading && filteredResources.length === 0 && (
<div className="max-w-3xl mx-auto text-center py-20 bg-white rounded-3xl border border-slate-200 mt-8">
<div className="max-w-3xl mx-auto text-center py-20 bg-white rounded-none border border-slate-200 mt-8">
<p className="text-slate-500 text-lg font-medium">No resources found in this category.</p>
</div>
)}
Expand Down
38 changes: 19 additions & 19 deletions app/forgot-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import { sendPasswordResetEmail } from "firebase/auth";
import { auth } from "@/lib/firebase";

function Orb({ className }: { className: string }) {
return <div className={`absolute rounded-full blur-3xl opacity-20 animate-pulse ${className}`} />;
return <div className={`absolute rounded-none blur-3xl opacity-20 animate-pulse ${className}`} />;
}

/* step indicator shown on left panel */
function Step({ num, title, desc, active }: { num: string; title: string; desc: string; active?: boolean }) {
return (
<div className="flex items-start gap-4">
<div className={`w-9 h-9 rounded-full flex items-center justify-center shrink-0 border transition-all ${active ? "bg-blue-500/30 border-blue-400/60" : "bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10"}`}>
<div className={`w-9 h-9 rounded-none flex items-center justify-center shrink-0 border transition-all ${active ? "bg-blue-500/30 border-blue-400/60" : "bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10"}`}>
<span className={`text-xs font-black ${active ? "text-blue-300" : "text-slate-500"}`}>{num}</span>
</div>
<div className="pt-1">
Expand Down Expand Up @@ -85,7 +85,7 @@ export default function ForgotPasswordPage() {

{/* ══════════════ LEFT PANEL ══════════════ */}
<div className="hidden lg:flex lg:w-[48%] relative flex-col justify-between p-14 overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-[#0D1B3E] via-[#0a1530] to-[#080E1E]" />
<div className="absolute inset-0 bg-card from-[#0D1B3E] via-[#0a1530] to-[#080E1E]" />
<Orb className="w-80 h-80 bg-blue-600 -top-20 -right-10" />
<Orb className="w-64 h-64 bg-indigo-500 bottom-20 -left-10" />
<Orb className="w-40 h-40 bg-sky-400 top-1/2 left-1/2" />
Expand All @@ -109,7 +109,7 @@ export default function ForgotPasswordPage() {

{/* top badge */}
<div className="relative z-10">
<div className="inline-flex items-center gap-2 bg-slate-100 dark:bg-white/10 backdrop-blur border border-slate-300 dark:border-white/20 rounded-full px-4 py-2">
<div className="inline-flex items-center gap-2 bg-slate-100 dark:bg-white/10 border border-slate-300 dark:border-white/20 rounded-none px-4 py-2">
<ShieldCheck className="w-4 h-4 text-blue-300" />
<span className="text-white/80 text-xs font-semibold tracking-wide">Secure Account Recovery</span>
</div>
Expand All @@ -120,7 +120,7 @@ export default function ForgotPasswordPage() {
<div>
<h2 className="text-5xl font-black text-white leading-[1.1] tracking-tight mb-5">
Regain<br />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-300 to-sky-200">
<span className="text-transparent bg-clip-text bg-card ">
Your Access.
</span>
</h2>
Expand All @@ -140,7 +140,7 @@ export default function ForgotPasswordPage() {

{/* ══════════════ RIGHT PANEL ══════════════ */}
<div className="flex w-full lg:w-[52%] flex-col items-center justify-center p-8 sm:p-12 relative">
<div className="absolute inset-0 bg-gradient-to-b from-[#0D1428] to-[#080E1E]" />
<div className="absolute inset-0 bg-card from-[#0D1428] to-[#080E1E]" />
<Orb className="w-64 h-64 bg-blue-700 bottom-0 right-0 opacity-10" />

<div className="relative z-10 w-full max-w-sm">
Expand All @@ -156,18 +156,18 @@ export default function ForgotPasswordPage() {

{/* ── SUCCESS STATE ── */}
{success ? (
<div className="animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="animate-in fade-in slide-in- duration-500">
{/* icon */}
<div className="flex justify-center mb-8">
<div className="relative">
<div className="w-20 h-20 rounded-full bg-emerald-500/20 border border-emerald-500/30 flex items-center justify-center">
<div className="w-20 h-20 rounded-none bg-emerald-500/20 border border-emerald-500/30 flex items-center justify-center">
<Inbox className="w-9 h-9 text-emerald-400" />
</div>
<div className="absolute -top-1 -right-1 w-7 h-7 rounded-full bg-emerald-500 flex items-center justify-center shadow-lg shadow-emerald-500/40">
<div className="absolute -top-1 -right-1 w-7 h-7 rounded-none bg-emerald-500 flex items-center justify-center border-2 border-foreground shadow-[4px_4px_0_0_rgba(0,0,0,1)] dark:shadow-[4px_4px_0_0_rgba(255,255,255,0.2)] shadow-emerald-500/40">
<CheckCircle2 className="w-4 h-4 text-white" />
</div>
{/* pulse rings */}
<div className="absolute inset-0 rounded-full border border-emerald-500/30 animate-ping" />
<div className="absolute inset-0 rounded-none border border-emerald-500/30 animate-ping" />
</div>
</div>

Expand All @@ -179,7 +179,7 @@ export default function ForgotPasswordPage() {
</div>

{/* info card */}
<div className="bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-2xl p-5 space-y-3 mb-8">
<div className="bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-none p-5 space-y-3 mb-8">
{[
{ icon: <Mail className="w-4 h-4 text-blue-400" />, text: "Check your spam/junk folder if you don't see it" },
{ icon: <KeyRound className="w-4 h-4 text-blue-400" />, text: "The link expires in 1 hour for security" },
Expand All @@ -194,7 +194,7 @@ export default function ForgotPasswordPage() {

<button
onClick={() => setSuccess(null)}
className="w-full flex items-center justify-center gap-2 border border-slate-200 dark:border-white/10 bg-slate-50 dark:bg-white/5 hover:bg-slate-100 dark:bg-white/10 text-slate-300 font-bold py-3.5 rounded-2xl transition-all group"
className="w-full flex items-center justify-center gap-2 border border-slate-200 dark:border-white/10 bg-slate-50 dark:bg-white/5 hover:bg-slate-100 dark:bg-white/10 text-slate-300 font-bold py-3.5 rounded-none transition-all group"
>
<RotateCcw className="w-4 h-4 group-hover:rotate-180 transition-transform duration-500" />
Resend Email
Expand All @@ -205,8 +205,8 @@ export default function ForgotPasswordPage() {
/* ── FORM STATE ── */
<div className="animate-in fade-in duration-300">
<div className="mb-10">
<div className="inline-flex items-center gap-2 bg-blue-500/10 border border-blue-500/20 rounded-full px-3 py-1 mb-5">
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse" />
<div className="inline-flex items-center gap-2 bg-blue-500/10 border border-blue-500/20 rounded-none px-3 py-1 mb-5">
<div className="w-2 h-2 bg-blue-400 rounded-none animate-pulse" />
<span className="text-blue-300 text-xs font-semibold">Account Recovery</span>
</div>
<h1 className="text-3xl font-black text-white tracking-tight mb-2">Reset Password</h1>
Expand All @@ -232,14 +232,14 @@ export default function ForgotPasswordPage() {
onBlur={() => setFocusedField(false)}
placeholder="name@university.ac.lk"
disabled={loading}
className="block w-full pl-11 pr-4 py-3.5 bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-2xl text-sm font-semibold text-white placeholder-slate-600 focus:outline-none focus:ring-2 focus:ring-blue-500/40 focus:border-blue-500/60 focus:bg-white/8 transition-all disabled:opacity-40"
className="block w-full pl-11 pr-4 py-3.5 bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-none text-sm font-semibold text-white placeholder-slate-600 focus:outline-none focus:ring-2 focus:ring-blue-500/40 focus:border-blue-500/60 focus:bg-white/8 transition-all disabled:opacity-40"
/>
</div>
</div>

{/* Error */}
{error && (
<div className="flex items-center gap-3 bg-red-500/10 border border-red-500/20 p-4 rounded-2xl animate-in fade-in slide-in-from-top-2 duration-200">
<div className="flex items-center gap-3 bg-red-500/10 border border-red-500/20 p-4 rounded-none animate-in fade-in slide-in- duration-200">
<AlertCircle className="w-4 h-4 text-red-400 shrink-0" />
<p className="text-sm font-semibold text-red-400 leading-tight">{error}</p>
</div>
Expand All @@ -254,11 +254,11 @@ export default function ForgotPasswordPage() {
<button
type="submit"
disabled={loading}
className="w-full relative overflow-hidden bg-gradient-to-r from-blue-600 to-blue-500 hover:from-blue-500 hover:to-blue-400 text-white font-bold py-4 rounded-2xl transition-all duration-300 shadow-lg shadow-blue-500/20 hover:shadow-blue-500/40 hover:shadow-xl active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 group"
className="w-full relative overflow-hidden bg-card hover: hover: text-white font-bold py-4 rounded-none transition-all duration-300 border-2 border-foreground shadow-[4px_4px_0_0_rgba(0,0,0,1)] dark:shadow-[4px_4px_0_0_rgba(255,255,255,0.2)] shadow-blue-500/20 hover:shadow-blue-500/40 hover:border-2 border-foreground shadow-[4px_4px_0_0_rgba(0,0,0,1)] dark:shadow-[4px_4px_0_0_rgba(255,255,255,0.2)] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 group"
>
<div className="absolute inset-0 bg-gradient-to-r from-white/0 via-white/10 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700 skew-x-12" />
<div className="absolute inset-0 bg-card translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700 skew-x-12" />
{loading ? (
<div className="h-5 w-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<div className="h-5 w-5 border-2 border-white/30 border-t-white rounded-none animate-spin" />
) : (
<>
Send Reset Link
Expand Down
Loading
Loading