Part 2 — Using Claude Code CLI to add a public-facing hackathon website to the admin dashboard from Part 1. Participant signup, event browsing, project submissions — all inside the same project.
In Part 1, we used it to build Hackathon HQ — a hackathon management platform with six interconnected collections: events, teams, participants, submissions, judges, and scores. Five minutes from zero to a deployed admin dashboard.
But participants don't care about your admin panel. They want a website. A place to browse upcoming events, sign up, join a team, and submit their project.
The good news: the admin panel already has a complete REST API with auto-generated OpenAPI documentation. We're going to point Claude Code at that spec and let it build public-facing pages — right inside the same project.
The react-admin-dashboard template for Codehooks.io lets you spin up a complete admin panel for any domain — CRM, project management, inventory, ticketing, you name it. Describe your data to an AI, paste the JSON datamodel, and you get a production app with authentication, role-based access, dynamic forms, file uploads, related collections, and auto-generated REST API docs. One JSON file drives the entire stack: React frontend, serverless backend, NoSQL database.
What We're Adding
Public pages alongside the existing admin dashboard — same project, same backend, same deploy:
- Public landing page — hero section, grid of open events (read-only, via API key)
- Event detail page — event info, teams, schedule
- Participant signup & login — external users create accounts and sign in
- Submission form — authenticated participants submit their projects
Admins keep using the admin panel at /admin. Participants get a clean public site at /. One Codehooks project serves both.
┌──────────────────────────────────────┐
│ Hackathon HQ (one Codehooks project)│
│ │
│ /admin/* → Admin dashboard (Part 1)│
│ / → Public site (Part 2) │
│ │
│ Public reads use x-apikey │
│ Participant writes use JWT (login) │
└──────────────────────────────────────┘
Screenshot of the finished website:

Prerequisites
- The Hackathon HQ project from Part 1 (deployed and running)
- At least one event created in the admin panel with status other than "Planning" (e.g. "Registration Open") — otherwise the public site will have nothing to show
-
Claude Code CLI installed (
npm i -g @anthropic-ai/claude-code) - Node.js 18+
Step 1: Create an API Key for Public Read Access
Note: Throughout this article I use
hackathonhqas my project name. Replace it with whatever you named your project in Part 1.
The public landing page needs to fetch events without requiring users to log in. Codehooks has built-in API key support. Create a token:
cd hackathonhq
coho add-token --readonly --description "Public website read access"
This key lets the public site fetch events and event details. Authenticated actions (signup, login, submitting projects) still go through the JWT auth system.
Step 2: Feed the OpenAPI Spec to Claude Code
Start Claude Code inside the hackathonhq project:
claude
First, let Claude Code understand the existing API:
> Read the OpenAPI spec at https://hackathonhq.api.codehooks.io/dev/openapi.json
and summarize the available API endpoints. I want to add public-facing pages
to this project for hackathon participants.
Claude Code fetches the spec and comes back with a full understanding of your API:
-
Events:
GET /api/eventswith query filters and sorting -
Teams:
GET /api/teamswith lookup references to events and participants -
Submissions:
POST /api/submissionswith status tracking -
Auth:
POST /auth/login,POST /auth/logout,GET /auth/me - Query format:
qfor MongoDB-style filters,hfor sort/limit/offset
It didn't hallucinate an API. It read the real one. Every field name, every enum value, every relationship — straight from your running backend.
Step 3: Add a Registration Endpoint
The admin panel from Part 1 only has login — admins create user accounts manually. For the public site, participants need to register themselves. Ask Claude Code to add it:
> Add a POST /auth/register endpoint to backend/index.js that lets new users
sign up with username, email, and password. It should create a user with
role "user", hash the password using the existing hashPassword function,
check for duplicate usernames, and return the user info with a JWT cookie
(same as the login endpoint). Make sure /auth/register is covered by the
existing public auth bypass so it doesn't require authentication.
Claude Code reads the existing auth code in backend/index.js, sees the hashPassword function, the findUserByUsername helper, and the JWT cookie pattern from the login endpoint — then generates a registration endpoint that follows the exact same conventions.
Important: After adding this, deploy the backend so the endpoint is live:
npm run deploy
Step 4: Restructure the Routes
The admin dashboard currently lives at /. We need to move it and make room for public pages:
> I want to add public pages to this React app for hackathon participants.
Move the existing admin dashboard routes under /admin/* (keep the existing
Layout with sidebar). Add new public routes at the root:
/ → public landing page
/events/:id → public event detail
/signup → participant registration
/login → participant login
/submit → submission form (authenticated only)
Create a PublicLayout component with a top navbar (no sidebar).
Update App.jsx with both route groups.
Claude Code reads the existing App.jsx, Layout.jsx, and routing structure, then restructures everything. The admin dashboard keeps working exactly as before — just under /admin now. Public pages get their own clean layout.
Important: Make sure all admin routes are fully updated — including collection detail views. Links like
/:collection/:idneed to become/admin/:collection/:id, otherwise clicking into a record from the admin panel will land you on the public site. Tell Claude Code explicitly:
> Double-check that all internal navigation links in the admin dashboard
(sidebar, list rows, breadcrumbs, detail views, "New" buttons) use the
/admin prefix. A detail view at /:collection/:id should be
/admin/:collection/:id. Test by clicking through from the admin sidebar
to a collection list, then into a record detail.
After this change, the admin panel login moves to
/admin/login. Bookmark it — you'll need it to manage events and review submissions.
Step 5: Build the Landing Page
> Create the public landing page at frontend/src/pages/public/LandingPage.jsx.
Hero section with "Hackathon HQ" title and tagline. Below it, a responsive
grid of event cards fetched from GET /api/events. Filter out events where
status is "Planning" — show all other events (Registration Open, In Progress,
Judging, Completed). Use an x-apikey header for authentication (the key is
stored in an environment config). Each card shows name, dates, location,
status badge, and a "View Details" link. Use shadcn Card components.
Make it look great.
Claude Code generates the page. Notice what it gets right because it read the spec:
// Public reads use the API key — no login required
const res = await fetch('/api/events?' + new URLSearchParams({
q: JSON.stringify({
status: { $ne: 'Planning' }
}),
h: JSON.stringify({ $sort: { startDate: -1 } })
}), {
headers: { 'x-apikey': API_KEY }
});
No guessing. No placeholder endpoints. The actual API format from your OpenAPI spec. The x-apikey header gives read access without requiring login. The filter excludes "Planning" events so only published events appear — everything from "Registration Open" through "Completed".
Tip: If the landing page shows no events, check your admin panel — make sure at least one event has a status other than "Planning".
Step 6: Event Detail Page
> Create an event detail page at frontend/src/pages/public/EventPage.jsx.
Route: /events/:id. Fetch the event from GET /api/events/:id using the
x-apikey header. Display name, description, dates, location, prizes
(render as markdown), and banner image. Below the details, fetch teams
for this event using GET /api/teams with query {"event._id": "<eventId>"}.
Show teams as cards with team name, track, and member count.
Add a "Sign up to participate" call-to-action.
Claude Code understands the lookup relationship format (event._id) because it saw the x-lookup definitions in the spec. It builds the page with the correct query structure, markdown rendering for the prizes field, and responsive team cards.
Step 7: Participant Signup and Login
Now we need authenticated actions. Participants sign up and log in through the auth system:
> Create a signup page at frontend/src/pages/public/SignupPage.jsx and a
login page at frontend/src/pages/public/LoginPage.jsx. Signup calls
POST /auth/register with username, email, and password. Login calls
POST /auth/login. Both use credentials: 'include' for the httpOnly
JWT cookie. On success, redirect to the landing page. Create an
AuthContext that checks GET /auth/me on mount and provides user state.
Update the public navbar: show Login/Sign Up buttons when logged out,
show username + Logout when logged in.
One prompt. Claude Code creates:
-
AuthContext.jsx— wraps the app, checks session on load -
SignupPage.jsx— registration form with validation -
LoginPage.jsx— login form matching the app's style - Updated
PublicNavbar.jsx— auth-aware navigation
Participants sign up and log in at /signup and /login. Admins use /admin/login to access the admin panel. Same auth system, different entry points.
Step 8: Submission Form
> Create a submission page at frontend/src/pages/public/SubmitPage.jsx.
Only accessible when logged in — redirect to /login otherwise. The form
posts to POST /api/submissions with: title, description, event (dropdown
fetched from /api/events), team (dropdown filtered by selected event),
track (enum from the spec), demoUrl, repoUrl, videoUrl. Use the JWT
cookie for authentication (credentials: 'include'). Reference the
Submission schema from the OpenAPI spec for field types.
Claude Code reads the Submission component schema from the spec and knows every field, every enum value (Draft, Submitted, Under Review, Finalist, Winner, Honorable Mention), and every URL format. The event dropdown filters teams dynamically — pick an event, the team dropdown updates.
A participant submits their project here. An admin sees it immediately in the admin dashboard at /admin. Same data, two interfaces.
The Iterative Part
AI coding is not one prompt and done. Here's the back-and-forth that made it real:
> The event cards need a badge showing the event status. Use the shadcn
Badge component with different colors for each status.
> Add a countdown timer to events that are "Registration Open" — show
days until startDate.
> The submit page should show a success message after submission with
a link back to the event page.
Each time, Claude Code reads the existing files, understands the context, and makes targeted changes. It doesn't start over. It builds on what's already there.
Step 9: Test and Deploy
Test locally:
cd frontend
npm run dev
Open http://localhost:5173. Here's your test checklist:
- Landing page — events with status other than "Planning" should appear. No login needed.
- Event detail — click an event card, see details and teams.
-
Sign up — create a participant account at
/signup. -
Submit — log in, go to
/submit, submit a project. -
Admin panel — visit
/admin/login, log in asadmin/admin, verify the submission appears.
When it looks right, deploy — same command as always:
cd ..
npm run deploy
One project. One deploy. Both the admin dashboard and the public site go live together.
Project: hackathonhq-jvel Space: dev
Deployed Codehook successfully! 🙌
Check your logs with: coho logs --follow
What We Built
One project, two interfaces:
| Admin Dashboard (Part 1) | Public Website (Part 2) | |
|---|---|---|
| Routes | /admin/* |
/ |
| Login URL | /admin/login |
/login |
| Purpose | Manage hackathon data | Participant-facing site |
| Read access | JWT (admin/user roles) | API key (no login needed) |
| Write access | JWT (admin role) | JWT (participant login) |
| Built with | JSON datamodel | Claude Code + OpenAPI spec |
| Time | 5 minutes | ~30 minutes |
Visitor browses events ──→ x-apikey ──→ Read-only access
Participant submits ──→ JWT login ──→ Write access
Admin manages data ──→ JWT login ──→ Full admin access
Key Takeaways
OpenAPI specs are AI multipliers. Your backend already generates API docs. Point an AI coding tool at them and it writes frontend code that actually calls the right endpoints with the right parameters. The spec is more accurate than anything you'd type in a prompt.
Same project, two audiences. The admin panel and the public site live in the same codebase, share the same backend, and deploy together. API keys handle public reads. JWT handles authenticated writes. Clean separation, zero duplication.
Claude Code is iterative. It's not a magic "build my app" button. It's a conversation. Describe, generate, review, refine. Each round it reads what it already built and adds to it. The OpenAPI spec keeps it grounded in your real API.
Get Started
The admin dashboard template is open source:
npm i -g codehooks @anthropic-ai/claude-code
coho create hackathonhq --template react-admin-dashboard
cd hackathonhq
claude
Part 1 gives you the admin panel. Part 2 — you, Claude Code, and an OpenAPI spec. Same project, new audience.
Missed Part 1?
This article builds on the admin backend from Part 1. If you haven't read it yet, start there — in five minutes you'll have a deployed hackathon management platform with a full REST API, and you'll be ready to follow along here.
Read Part 1: I Asked AI to Design a Hackathon App. 5 Minutes Later It Was in Production.
What would you add next? A public leaderboard? Real-time score updates? A sponsor portal? Drop your ideas in the comments.





Top comments (0)