Skip to content

yeetcode-xyz/yeetcode-website

Repository files navigation

YeetCode Website

Next.js 15 frontend for YeetCode — a competitive LeetCode platform with duels, XP, streaks, and group leaderboards.

Tech Stack

  • Next.js 15 — App Router, React Server Components
  • TypeScript — Full type safety
  • Tailwind CSS — Neobrutalist design system
  • Resend — Transactional email (via API backend)

Local Setup

Prerequisites

  • Node.js 18+
  • The YeetCode API running locally at http://localhost:6969

Install

git clone https://github.com/yeetcode-xyz/yeetcode-website.git
cd yeetcode-website

npm install

cp .env.example .env.local
# Fill in .env.local (see Environment Variables below)

Run

npm run dev

App is at http://localhost:3000.


Environment Variables

Variable Required Description
YEETCODE_API_URL Yes URL of the YeetCode API (e.g. http://localhost:6969)
YEETCODE_API_KEY Yes API key matching YETCODE_API_KEY on the backend

Project Structure

yeetcode-website/
│
├── app/
│   ├── layout.tsx                    # Root layout (fonts, metadata)
│   ├── page.tsx                      # Landing page
│   │
│   ├── (app)/                        # Auth-required pages
│   │   ├── layout.tsx                # Wraps with AppProvider + ToastProvider
│   │   ├── dashboard/page.tsx        # Main dashboard
│   │   └── group/page.tsx            # Group management page
│   │
│   ├── (auth)/                       # Auth pages (no session required)
│   │   ├── login/page.tsx            # Email input → OTP send
│   │   ├── verify/page.tsx           # OTP verification
│   │   └── onboarding/page.tsx       # Username + university setup (new users)
│   │
│   ├── duel-invite/[token]/page.tsx  # Public duel invite landing page (no auth)
│   │
│   ├── components/
│   │   ├── ui/
│   │   │   ├── toast.tsx             # Toast + sendBrowserNotification
│   │   │   └── searchable-dropdown.tsx
│   │   └── dashboard/
│   │       ├── DuelsSection.tsx      # Full duel UI (challenge, accept, solve, history)
│   │       ├── TodaysChallenge.tsx   # Daily problem card
│   │       ├── UserStats.tsx         # XP, streak, solve counts
│   │       ├── FriendsLeaderboard.tsx
│   │       ├── ActiveBounties.tsx
│   │       ├── QuickActions.tsx
│   │       └── LeaderboardHeader.tsx
│   │
│   └── api/                          # Next.js API routes (thin proxies to backend)
│       ├── auth/
│       │   ├── send-otp/             # POST → /store-verification-code
│       │   ├── login/                # POST → /verify-code (sets session cookie)
│       │   ├── me/                   # GET  → /user (reads session cookie)
│       │   └── logout/               # POST → clears session cookie
│       ├── user/
│       │   ├── create/               # POST → /create-user
│       │   ├── [username]/           # GET  → /user/{username}
│       │   └── display-name/         # POST → /update-display-name
│       ├── duels/
│       │   ├── [username]/           # GET  → /duels/{username}
│       │   ├── create/               # POST → /create-duel
│       │   ├── accept/               # POST → /accept-duel
│       │   ├── reject/               # POST → /reject-duel
│       │   ├── start/                # POST → /start-duel
│       │   ├── verify/               # POST → /verify-duel-solve
│       │   ├── submit/               # POST → /record-duel-submission
│       │   ├── detail/[duelId]/      # GET  → /duel/{duelId}
│       │   ├── create-open/          # POST → /create-open-challenge
│       │   ├── accept-open/          # POST → /accept-open-challenge
│       │   ├── open-challenges/[username]/ # GET → /open-challenges/{username}
│       │   ├── create-invite/        # POST → /create-duel-invite
│       │   ├── invite/[token]/       # GET  → /duel-invite/{token}
│       │   └── accept-invite/        # POST → /accept-duel-invite
│       ├── group/
│       │   ├── create/               # POST → /create-group
│       │   ├── join/                 # POST → /join-group
│       │   ├── leave/                # POST → /leave-group
│       │   └── [groupId]/stats/      # GET  → /group-stats/{groupId}
│       ├── leaderboard/              # GET  → /leaderboard
│       ├── daily/
│       │   ├── [username]/           # GET  → /daily-problem/{username}
│       │   └── complete/             # POST → /complete-daily-problem
│       └── bounties/[username]/      # GET  → /bounties/{username}
│
└── lib/
    ├── api.ts                        # Typed fetch helpers + shared interfaces (Duel, User, etc.)
    └── contexts/
        └── app-context.tsx           # Global state: user, duels, daily, groups

State Management

All global state lives in AppProvider (lib/contexts/app-context.tsx):

  • user — current user (XP, streak, group, university, etc.)
  • duels — user's duel list (PENDING, ACTIVE, COMPLETED)
  • dailyData — today's challenge + completion status
  • isDuelsLoading — controls skeleton vs. content in DuelsSection
  • refreshDuels(silent?) — re-fetches duels; pass silent=true to skip the loading skeleton (used by the 3s background poll in DuelsSection)

Auth Flow

  1. User enters email → /api/auth/send-otp → backend sends OTP via Resend
  2. User enters OTP → /api/auth/login → backend verifies, returns username
  3. Next.js route sets an HTTP-only session cookie with the username
  4. /api/auth/me reads that cookie and returns the current user
  5. New users (no username yet) are redirected to /onboarding to pick a username and university

Duel Flow (Frontend)

DuelsSection manages the full duel lifecycle across tabs:

Challenge tab → create-duel / create-open-challenge / create-duel-invite
     ↓
Pending tab  → accept-duel / reject-duel (shows expiry countdown)
     ↓
Active tab   → start-duel → verify-duel-solve (user clicks) or auto-poll every 3s
     ↓
History tab  → completed duels with Rematch button

Notifications: A browser notification fires when a new PENDING challenge arrives. A toast + browser notification fires when a duel completes.

Invite link: "Invite someone new" generates a shareable yeetcode.xyz/duel-invite/{token} link and optionally emails it. The recipient sees the invite landing page and can accept after logging in or creating an account.


API Routes (Proxy Pattern)

All app/api/ routes are thin proxies. They read the session cookie for username, forward the request to the backend with X-API-Key, and return the JSON response.

// Example: app/api/duels/accept/route.ts
export async function POST(req: Request) {
  const session = (await cookies()).get("session")?.value
  const body = await req.json()
  const res = await fetch(`${process.env.YEETCODE_API_URL}/accept-duel`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": process.env.YEETCODE_API_KEY!,
    },
    body: JSON.stringify({ ...body, username: session }),
  })
  return NextResponse.json(await res.json())
}

Rank System

Rank XP Range
Script Kiddie 0–499
Debugger 500–1,499
Stack Overflower 1,500–3,499
Algorithm Apprentice 3,500–6,499
Loop Guru 6,500–11,999
Recursion Wizard 12,000–19,999
Regex Sorcerer 20,000–34,999
Master Yeeter 35,000–49,999
0xDEADBEEF 50,000+

Each rank has three subdivisions (I, II, III).


Deploying with Coolify

  1. Point Coolify at this repo (main branch)
  2. Set build command: npm run build
  3. Set start command: npm start
  4. Set env vars: YEETCODE_API_URL, YEETCODE_API_KEY
  5. Deploy — no persistent storage needed (all data lives in the API)

Contributing

Adding an API route

Create a file under app/api/ following the proxy pattern above. Keep routes thin — no business logic, just forward to the backend.

Adding a dashboard component

  1. Create the component in app/components/dashboard/
  2. Import and render it in app/(app)/dashboard/page.tsx
  3. Use useAppContext() for global state (user, duels, etc.)

Workflow

  1. Fork → create a branch (git checkout -b feature/your-feature)
  2. Run locally: npm run dev
  3. Lint before pushing: npm run lint
  4. PR against main

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors