Hackathon Project
1st Place

RUNACOS (Web Design Contest)

Official web platform for the Redeemer's University Computer Science Association, centralizing academic resources, events, alumni networking, and facility reporting.

React
TypeScript
Vite
TailwindCSS
Express.js
Prisma
PostgreSQL
Supabase
React Query
Framer Motion
GSAP
Zod
Redis
Cloudinary
Node.js
Duration: 1 month
Completed: 5/26/2026
Team Size: 2

Team Members:

Daniel Fadehan
Taiwo Ayomide
Live Demo
RUNACOS (Web Design Contest)

Project Overview

RUNACOS is a full-stack web platform for the Computer Science Association at Redeemer's University, combining a public-facing site (news, events, alumni directory) with a backend fault-reporting system and a full admin management suite. Built with React + Vite on the frontend and Express + Prisma on the backend, with Supabase handling auth and storage, it provides students and faculty a centralized hub for academic resources and community engagement. The platform emphasizes a premium SaaS-like aesthetic — dark theme, gold accents, glassmorphic cards, and smooth animations — serving as a model for university digital infrastructure.

Key Features

  • News & Updates — publish and browse department announcements with rich formatting and categorization
  • Event Management — create, register, and track academic and social events with capacity management
  • Fault Reporting System (FRMS) — multi-step form for submitting facility issues with real-time status tracking and unique ticket IDs
  • Past Questions Repository — upload, approve, and download past exam papers filtered by level, semester, and session
  • Alumni Directory — showcase graduate career journeys, enable mentorship connections, and feature spotlights
  • Admin Dashboard — comprehensive CMS for news, events, FRMS reports, members, and newsletters with rich editing tools

Challenges & Solutions

  • Multi-step form state management across the FRMS wizard with conditional fields, file uploads, and cross-step Zod validation without prop drilling
  • Role-based access control spanning Supabase Auth JWTs, Prisma user roles, and per-route middleware with domain-restricted login (@run.edu.ng)
  • Query and cache invalidation strategy using React Query and Redis — preventing N+1 queries on admin dashboards while keeping data fresh across all consumers
  • File storage and approval workflow for past questions — dual-state storage (pending vs. approved), admin moderation queue, and preventing leak of unapproved files
  • Coordinating three independent codebases (frontend, admin, backend) with shared type contracts and consistent API versioning across Vercel and Node.js deployments

System Architecture

Architecture Overview

The RUNACOS (Redeemer's University Computer Science Association) platform is a three-tier web application serving as the official digital hub for CS students and staff.

System Components

ComponentRoleTechnology Stack
Frontend (Public)Student-facing website and portalReact 19, TypeScript, Vite, TailwindCSS, Framer Motion, GSAP
Admin DashboardStaff management interfaceReact 19, TypeScript, Vite, TailwindCSS, TipTap
Backend APIREST API & data layerExpress.js, Node.js, Prisma ORM, Zod validation
DatabasePersistent data storagePostgreSQL (Supabase)
AuthUser authenticationSupabase Auth (Google OAuth)
CachingRequest cachingUpstash Redis
MailingEmail notificationsNodemailer
StorageImage uploadsCloudinary
API DocsSwagger/OpenAPISwagger UI Express

High-Level Data Flow

Frontend SPA / Admin Panel
         │ HTTPS / Bearer token auth
         ▼
Express.js Backend (Port 3000)
├─ Auth Middleware (Supabase JWT)
├─ Admin Middleware (Role check)
├─ Validation Middleware (Zod)
├─ Cache Middleware (Redis)
└─ 11 Feature Modules
         │ Prisma queries
         ▼
PostgreSQL Database (Supabase)
├─ User accounts (Supabase Auth)
├─ Events & registrations
├─ News articles
├─ FRMS reports & comments
├─ Past questions
└─ Alumni profiles

Component Hierarchy

Frontend (Public)

App.tsx
└─ ThemeProvider > QueryClientProvider > BrowserRouter
   └─ AuthProvider
      ├─ [PUBLIC] Layout
      │  ├─ Navbar
      │  ├─ HomePage
      │  │  ├─ AboutSection / AboutAnimatedBeams
      │  │  ├─ EventCard / DepartmentCard
      │  │  └─ GalleryGlobe
      │  ├─ NewsPage > NewsFeaturedCarousel / NewsStoryCard
      │  ├─ EventsPage > EventsGrid > EventCard
      │  ├─ EventDetailPage > RegistrationProgress / SocialShareSheet
      │  ├─ FRMSPage > FRMSWizard (4-step)
      │  │  ├─ CategoryCard / PrioritySlider
      │  │  └─ TicketCard (preview)
      │  ├─ PastQuestionsPage > FilterSidebar / QuestionCard
      │  ├─ AlumniPage > AlumniCard > ExperienceTimeline / AlumniModal
      │  ├─ ExecutivesPage > ExecutiveCarousel
      │  └─ [Shared UI] GlassCard / BentoGrid / AnimatedCounter / ScrollReveal
      ├─ [AUTH] LoginPage / AuthCallbackPage
      ├─ [ONBOARDING] ProtectedRoute > OnboardingPage
      └─ [PORTAL] ProtectedRoute > PortalLayout
         ├─ PortalPage / PortalProfilePage
         └─ Events / FRMS / PastQuestions / News (portal variants)

Admin Dashboard

App.tsx
└─ AdminAuthProvider > NotificationProvider
   └─ AdminGuard
      └─ AdminLayout
         ├─ Sidebar / TopBar
         └─ Pages (Outlet)
            ├─ DashboardPage (stats + recent activity)
            ├─ UsersListPage > DataTable > UserEditModal
            ├─ EventsListPage > DataTable > EventFormModal
            ├─ NewsListPage > DataTable > NewsFormModal > TipTap Editor
            ├─ NewsletterPage > CampaignFormModal > TipTap Editor
            ├─ PastQuestionsListPage > DataTable (approval queue)
            ├─ FRMSListPage > DataTable > Status/Comment modal
            ├─ StaffListPage / ExecutivesListPage > DataTable > FormModal
            ├─ AlumniListPage > DataTable > AlumniFormModal
            └─ GalleryMomentsPage > DataTable > CloudinaryImageUploader

Backend Route Modules

Express App
├─ Global: helmet / cors / json / morgan / Redis cache
├─ /api/users        — profile sync, role management
├─ /api/events       — CRUD + registration + CSV export
├─ /api/pq           — past questions + approval queue
├─ /api/news         — articles + publish/draft workflow
├─ /api/frms         — ticket submission + status tracking
├─ /api/executives   — leadership directory
├─ /api/staff        — faculty directory
├─ /api/alumni       — alumni profiles + mentorship
├─ /api/contact      — form submissions inbox
├─ /api/newsletter   — subscribers + campaign send
├─ /api/gallery-moments — home page gallery
└─ /api/dashboard    — admin stats aggregation

Data Flow

Authentication Flow

1. User clicks "Sign in with Google"
2. supabase.auth.signInWithOAuth() → Google consent → token
3. Redirect to /auth/callback with session
4. AuthContext validates email domain (@run.edu.ng)
5. Stores access_token in localStorage
6. Fetches/syncs AppUser row via POST /api/users
7. If matricNumber/level missing → redirect to /onboarding
8. User fills onboarding form → PATCH /api/users/:id
9. Redirect to /portal or home

FRMS Report Submission

FRMSWizard (4 steps):
  Step 1: Category + Title + Description
  Step 2: Priority + Location + Evidence upload
  Step 3: Submitter info
  Step 4: Anonymous toggle + confirmation
       ↓
POST /api/frms → generate ticketId → Prisma FrmsReport
       ↓
Admin: FRMSListPage → status update → PATCH /api/frms/:ticketId

React Query Cache Strategy

SettingValue
staleTime5 minutes
gcTime10 minutes
retry1
refetchOnWindowFocusfalse

Cache is invalidated on every POST/PATCH/DELETE via Redis middleware.


Data Models

ModelKey Fields
Userid (Supabase UUID), email (@run.edu.ng), fullName, matricNumber, level, role, status
Eventid, slug, name, type, venue, eventDate, capacity, registrationEnabled, status
EventRegistrationid, eventId, userId, registrationStatus
NewsArticleid, title, content (HTML), imageURL, category, isPublished, isFeatured
PastQuestionid, courseCode, level, semester, academicSession, fileURLs, status, downloadCount
FrmsReportid, ticketId, category, priority, status, evidenceURL, submitterEmail
FrmsCommentid, reportId, userId, message, statusChange
AlumniProfileid, slug, displayName, graduationYear, jobTitle, company, experience (JSON)
NewsletterCampaignid, subject, bodyHTML, sendTo, status, sentAt

Build Toolchain

ToolPurpose
Vite 7.3Frontend bundler & dev server
TypeScript 5.9Type checking across all packages
TailwindCSS 4.2Utility CSS
Prisma 5.10ORM + database migrations
Express 4.18Backend framework
ESLint 9Linting
Jest 29Backend unit testing
Nodemon 3Dev server hot-reload

Key Architectural Decisions

Monorepo — Three Separate Packages

Frontend, admin, and backend are independent package.json workspaces with separate Vercel deployments. This keeps bundle sizes small and allows different DevOps requirements per package, at the cost of some type duplication.

Supabase Auth + Prisma Split

Google OAuth JWTs are issued and verified by Supabase; application state (roles, profiles) lives in Prisma-managed PostgreSQL. This decouples identity from app data but requires an extra Supabase API call per request to verify JWTs.

Feature Modules Pattern (Backend)

Each domain (events, news, frms, etc.) has its own routes / controller / service / repository / types / doc files. Self-contained modules make it easy to add or remove features; the trade-off is some cross-module duplication in error handling.

Redis Middleware Cache

All GET responses are cached in Upstash Redis; any POST/PATCH/DELETE clears the full cache. Simple and effective for a small team, but a blanket invalidation strategy — selective per-endpoint caching would scale better.

TipTap + Cloudinary in Admin

TipTap provides a headless rich-text editor for news articles and newsletters; Cloudinary handles image uploads and CDN delivery. Both integrate cleanly with React Hook Form and avoid managing media storage on the backend server.

FRMS Multi-Step Wizard

The fault-reporting form is broken into 4 steps to improve UX for a complex submission — avoiding a single long form reduces errors and increases completion rates.