Problem
The club's web platform was outdated in both design and function. The public site lacked structure and mobile support, and officers ran recruitment on fragmented tools (spreadsheets, manual email threads, and external schedulers) to manage hundreds of applications and interviews, with no single place to track applicant status or history.
What we found
An audit of the site and admin dashboard, plus feedback from members and officers, surfaced four problems:
- Poor responsiveness. The site broke on mobile and across screen sizes.
- Slow pages. Key application and admin pages took 15 to 50 seconds to load.
- High drop-off. Users were confused by the layout and missing information.
- Support by Discord. People skipped the site and asked for help in Discord instead.
Architecture

We built on the T3 stack (Next.js 14 App Router, TypeScript, Tailwind CSS, tRPC, and Prisma) for end-to-end type safety without hand-written API schemas. NextAuth with an Auth0 provider handled sessions and role-based routing, and the Google Calendar and Drive APIs automated scheduling and document handling. The team worked in feature branches with semantic commits and structured PRs.
Applications and review
Applicants submit a multi-step React Hook Form application that ends with a validated PDF resume upload, stored in Google Drive and linked to their Prisma record.
<Input
type="file"
accept=".pdf"
onChange={(e) => {
const resume = e.target.files?.[0]
if (resume && resume.type === "application/pdf" && resume.size < 20 * 1024 * 1024) {
setResumeFile(resume)
}
}}
/>
Officers review everything in one ApplicantDetailsModal: answers, an embedded resume preview, internal notes, and tRPC-powered status updates written straight to the database. No spreadsheets, no tab switching.

Asymmetric scheduling
The core scheduling problem: applicants need a 30-minute interview, but interviewers need 45 minutes: the interview plus a 15-minute buffer. The buffer is enforced server-side, so no officer can be overbooked by mistake.
const interviewDuration = 30 * 60 * 1000 // 30 min
const interviewerWindow = interviewDuration + 15 * 60 * 1000 // 45 min
await db.interview.create({
data: {
applicantId,
interviewerId,
startTime,
endTime: new Date(startTime.getTime() + interviewDuration),
location,
teamId,
},
})
Officer availability maps each 15-minute block to the officers free in it, which drives a live calendar of coverage and load. Confirmed interviews sync to a shared Google Calendar, and changes made there flow back into the platform through webhooks.

Communication
Confirmations and rejections go out automatically through tRPC routes and Nodemailer, each templated with the applicant's slot, interviewer, and location, with no copy-paste and no missed or duplicated messages.
Timeline
- Fall 2024: planning and public site. A year-long roadmap with Figma mockups and schema design, then the public site in Next.js and Tailwind, then a week of testing with officers.
- Spring 2025: admin platform and launch. Admin architecture, three months building the dashboard, scheduling, email, and Google integrations, and a production launch for officer applications and design challenges.
Results
- The redesign is fully responsive, and high-volume views load in under 3 seconds, down from 15–50.
- Clearer structure and in-page guidance cut both drop-off and Discord support requests.
- The auto-scheduler cut manual scheduling effort by 90% and brought overbookings to zero.
Most of the time savings came from backend automation and admin tooling rather than the visual refresh, and end-to-end types caught edge cases early enough to keep a five-person team moving quickly.