A modern, full-stack job application tracking system that transforms your job search chaos into organized success.
View Live Demo · GitHub Repository · Developer Portfolio
Job Tracker solves the universal frustration of managing job applications across dozens of tabs, spreadsheets, and sticky notes. Built for job seekers who want to focus on landing their dream role—not on administrative chaos.
- Visual Pipeline Management — See your entire job search at a glance with a beautiful Kanban board
- Zero Friction Workflow — Drag-and-drop interface with keyboard shortcuts for power users
- Interview Scheduling — Built-in calendar integration with Google Calendar sync and email reminders
- Job Discovery — Search and import jobs directly within your dashboard
- Privacy First — Your job search data stays yours with secure authentication
- Next.js 16 - React framework with App Router
- React 19 - UI library with concurrent features
- TypeScript 5 - Type safety
- Tailwind CSS 4 - Utility-first styling
- shadcn/ui - Accessible component library (Radix UI primitives)
- React Hook Form - Performant form validation
- Sonner - Toast notifications
- next-themes - Theme management
- dnd-kit - Modern drag-and-drop for React
- GSAP - Professional-grade animations
- Framer Motion - React animation library
- Lenis - Smooth scroll experience
- cmdk - Command palette interface
- date-fns - Date utility library
- react-markdown 10 - Markdown rendering
- MongoDB Atlas - Cloud database
- Mongoose 9 - MongoDB ODM
- better-auth - Authentication library
- Zod 4 - Schema validation
- Upstash - Rate limiting with Redis
- Resend - Email notifications
- Google Calendar API - Calendar sync
- Cheerio - HTML parsing
- Vercel AI SDK 6 - Streaming AI text generation
- ESLint 9 - Code linting with flat config
- Prettier - Code formatting with Tailwind plugin
- SWR - React data fetching
- Hybrid Caching - Server Components
use cache+ Client SWR - Path aliases (
@/*) for clean imports
This project utilizes a cutting-edge hybrid data fetching architecture leveraging Next.js 16's experimental cacheComponents and React 19 features:
- Server-Side: Critical data (like Kanban boards) is pre-fetched on the server using the
use cachedirective, ensuring instant initial HTML render. - Client-Side: Data is passed as
fallbackDatato SWR hooks, allowing for immediate hydration without layout shift or loading spinners. - Optimistic Updates: UI interactions (drag-and-drop) update instantly while validating in the background.
- Strict TypeScript: No
anytypes allowed. All MongoDB documents are serialized with generic utilities (serializeDoc<T>) to ensure type safety across the network boundary. - React 19 Compliance: All components are refactored for React 19 purity rules (no side effects in render) and proper
refusage. - Modular Design: Large features like the Calendar are broken down into sub-components (
CalendarHeader,DayCell) for maintainability. - Query Projections: All dashboard queries use strict
.select()projections to prevent over-fetching heavy text fields (descriptions, notes) — only the 16 fields needed for UI rendering are transferred.
- Drag-and-drop job cards between customizable status columns (Applied, Interview, Offer, Rejected, etc.)
- Smart priority automation — Automatically upgrades priority when moving to high-stakes columns (e.g., Interview → High)
- Priority tagging system — Mark urgent opportunities as High, Medium, or Low priority
- Full keyboard navigation — Power user shortcuts for every action (see Keyboard Shortcuts below)
- Command palette — Lightning-fast search and actions with
Cmd/Ctrl + K - Quick search — Press
/to instantly focus the search bar - Real-time optimistic updates — Changes reflect instantly with SWR caching
- Responsive design — Fully functional on desktop and mobile devices
- Context-Aware Generation — Uses your base profile and the job description to generate highly targeted resumes using the Vercel AI SDK
- Inline Editor — Preview and edit the generated markdown directly in the browser
- 1-Click PDF Export — Download print-ready, cleanly formatted resumes instantly
- Persistent Storage — Tailored resumes are saved directly to the job card in MongoDB
- Interview scheduling with date, time, and timezone support
- Custom time picker with 30-minute intervals (8 AM - 7 PM) for better UX
- Monthly calendar view of all upcoming interviews with at-a-glance details
- Interview conflict detection — See all scheduled interviews on the same day
- Google Calendar sync — Optional incremental OAuth for seamless two-way integration
- Automated email reminders via Resend:
- 24-hour interview reminders
- 7-day follow-up reminders after application
- Integrated Job Search — Find roles without leaving the app
- Smart Filtering — Filter by Junior, Intermediate, or Senior roles with optimized queries
- One-Click Import — Add jobs directly to your pipeline
- Location Presets — Quick toggles for major tech hubs (NZ/AU)
- Search State Persistence — Your active search, exact results, and UI filters are durably synced to MongoDB, auto-restoring your session across devices. Stale caches automatically trigger a background scrape.
The system transforms raw job postings into structured, actionable insights using a multi-tiered analysis engine.
The app uses Cheerio to parse LinkedIn job postings, employing multiple fallback selectors to ensure resilience against layout changes. It extracts:
- Title & Company: Basic role identification.
- Description: The primary source for deep seniority and tech-stack analysis.
- Search Metadata: Real-time extraction of required years (e.g., "1-3 years") and descriptive "snippets" directly from search result cards.
Note: Scraping relies on public/guest endpoints and replicates a standard browser behavior (User-Agent spoofing) to stay compliant with rate limits. It is designed for low-volume, personal use.
A tiered decision model combines explicit keywords with numeric experience ranges:
| Experience (Avg) | Example | Assigned Level |
|---|---|---|
| < 2 years | "0-2 years" | Junior |
| 2 – 4.9 years | "2-7 years" | Intermediate |
| 5 – 9.9 years | "5+ years" | Senior |
| 10+ years | "10+ years" | Lead |
Keyword Overrides: Explicit titles like "Principal", "Intern", or "Senior" take precedence over numeric ranges to ensure accuracy.
A weighted scoring function (classifyRoleType) identifies the development track by scanning for exact-word tech keywords with strict word boundaries:
- Frontend: React, Vue, HTML, CSS, JavaScript, TypeScript, Web Developer.
- Backend: Node, Python, Java, C#, .NET, SQL, Database, API, Server.
- Fullstack: High scores across both categories.
The Junior filter is tuned specifically for the tech industry to catch "hidden" junior roles while suppressing senior noise:
- Auto-Include: Roles with "Junior", "Grad", or "Intern" in the title.
- Conditional Include: Generic titles (e.g., "Software Engineer") only appear if they mention Frontend Tech (React, JS, etc.) or Low Years in the card snippet.
- Auto-Exclude: Roles with obvious senior keywords (Lead, Principal, etc.).
Results are surfaced as professional, color-coded badges and auto-populated notes in the tracker:
Senior (5-10 years • Frontend) ✓
Confidence Indicators:
✓High: Exact keyword or clear numeric match.~Medium: Inferred from partial signals.?Low: Best guess based on weak evidence.
- Google OAuth — Sign in with your Google account
- Email/Password — Traditional authentication option
- Protected routes — Server-side session validation
- Rate limiting — API protection with Upstash Redis
- Crisp monochrome theme — Professional, high-contrast aesthetic with Zinc color palette
- Dark/Light theme — Full theme support that follows system preference
- Smooth animations — GSAP and Framer Motion powered interactions
- Accessible components — Built on Radix UI primitives with shadcn/ui
- Rock-solid layout stability — Custom scroll locking implementation prevents layout shifts
- Smooth scrolling — Lenis scroll experience
Boost your productivity with comprehensive keyboard shortcuts:
Cmd/Ctrl + K— Open command palette/— Focus search bar?— Show keyboard shortcuts helpShift + T— Toggle dark/light themeESC— Close dialogs / Clear searchShift + K— Switch to Kanban viewShift + L— Switch to List viewShift + C— Switch to Calendar viewShift + J— Switch to Job Search viewC— Create new jobCtrl + Enter— Trigger search (in Search tab)
- Quick view switching without leaving the keyboard
- Seamless search while browsing jobs
- Instant dialog dismissal
Live Application: job-tracker-app-cyan.vercel.app
Modern landing page with clean design and clear call-to-action
Kanban board with drag-and-drop job cards and priority indicators
Monthly calendar showing scheduled interviews with company details
- Node.js 18.17.0 or higher
- npm 9.x or higher (or pnpm/yarn)
- MongoDB Atlas account (free tier available)
- Google Cloud Console project for OAuth (setup guide)
-
Clone the repository
git clone https://github.com/sambai-dev/job-tracker-app.git cd job-tracker-app -
Install dependencies
npm install
-
Configure environment variables
cp .env.example .env.local
-
Edit
.env.localwith your credentials:# MongoDB Connection MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/<database>?retryWrites=true&w=majority MONGO_DB_NAME=job-tracker # Authentication (required) BETTER_AUTH_SECRET=<generate with: openssl rand -base64 32> BETTER_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_BETTER_AUTH_URL=http://localhost:3000 # Google OAuth (required) GOOGLE_CLIENT_ID=<from Google Cloud Console> GOOGLE_CLIENT_SECRET=<from Google Cloud Console> GOOGLE_CALENDAR_ENABLED=true # Rate Limiting (optional for development) UPSTASH_REDIS_REST_URL=<from Upstash Console> UPSTASH_REDIS_REST_TOKEN=<from Upstash Console> # Email Notifications (optional) RESEND_API_KEY=re_... FROM_EMAIL=reminders@your-domain.com # Cron Jobs (optional) CRON_SECRET=<generate with: openssl rand -hex 32>
-
Start the development server
npm run dev
-
Open your browser at http://localhost:3000
job-tracker-app/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # API routes
│ │ │ ├── auth/ # Authentication endpoints
│ │ │ ├── boards/ # Board CRUD operations
│ │ │ ├── calendar/ # Calendar & Google sync
│ │ │ ├── columns/ # Column management
│ │ │ ├── cron/ # Scheduled jobs (reminders)
│ │ │ ├── jobs/ # Job application CRUD
│ │ │ ├── job-search/ # Job search API
│ │ │ └── parse-job/ # Job URL parsing
│ │ ├── dashboard/ # Protected dashboard route
│ │ ├── sign-in/ # Authentication pages
│ │ ├── sign-up/
│ │ ├── privacy-policy/
│ │ └── terms-of-use/
│ │
│ ├── components/ # Shared UI components
│ │ ├── ui/ # shadcn/ui primitives (28 components)
│ │ ├── layout/ # Navbar, footer, wrappers
│ │ ├── dashboard/ # Dashboard-specific components
│ │ └── motion/ # Animation components
│ │
│ ├── features/ # Feature modules
│ │ ├── dashboard/ # Kanban board & job management
│ │ │ ├── components/ # JobCard, KanbanColumn, Calendar/
│ │ │ ├── data/ # Server-side data fetching (use cache)
│ │ │ ├── hooks/ # useBoards, useJobs
│ │ │ └── actions/ # Server actions
│ │ └── landing/ # Marketing landing page
│ │ └── components/ # Hero, Features, FAQ, etc.
│ │ └── data/ # Landing page static data
│ │
│ ├── lib/ # Core infrastructure
│ │ ├── auth/ # better-auth configuration
│ │ ├── db/ # MongoDB connection
│ │ ├── models/ # Mongoose schemas
│ │ ├── schemas/ # Zod validation schemas
│ │ ├── services/ # Business logic services
│ │ ├── email/ # Email templates & sending
│ │ └── utils/ # Shared utilities
│ │
│ ├── hooks/ # Global custom hooks
│ └── types/ # TypeScript definitions
│
├── public/ # Static assets
├── .env.example # Environment template
├── next.config.ts # Next.js configuration
└── package.json
┌─────────────────────────────────────────────────────────────┐
│ Frontend │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Landing │ │ Dashboard │ │ Auth Pages │ │
│ │ (GSAP) │ │ (Kanban) │ │ (better-auth) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ API Layer (Next.js) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────────┐ │
│ │ /auth │ │ /jobs │ │ /boards │ │ /calendar │ │
│ │ │ │ /search │ │ │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Data Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ MongoDB │ │ Upstash │ │ Resend │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
We prioritize security best practices, particularly regarding data isolation in a multi-tenant environment.
The application implements strict ownership verification to prevent Insecure Direct Object Reference (IDOR) vulnerabilities. Critical operations, such as column updates, verified that:
- The user is authenticated.
- The user owns the parent board that the column belongs to.
Implementation Example:
// src/app/api/columns/[columnId]/route.ts
// 1. Verify Authentication
if (!session?.user)
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
// 2. Lookup Resource & Parent
const column = await Column.findById(columnId);
const board = await Board.findById(column.boardId);
// 3. Strict Ownership Check
// Prevents User A from modifying User B's columns by guessing IDs
if (!board || board.userId !== session.user.id) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}We utilize a structured logging system that prevents sensitive internal state (like database connection details) from leaking into production logs, while maintaining observability.
To optimize build times and prevent connection errors during static generation, the application uses a lazy initialization pattern for the database and auth provider. This ensures MongoDB connections are only established when a request is actually processed at runtime, preventing "ECONNREFUSED" errors during Vercel builds.
Kanban reordering uses a fractional midpoint algorithm instead of the common O(N) approach of shifting all adjacent items:
// Calculate a float exactly between the two neighbors
const prevOrder = filteredTargetJobs[newOrder - 1].order;
const nextOrder = filteredTargetJobs[newOrder].order;
calculatedOrder = (prevOrder + nextOrder) / 2;
// Single document update — O(1) writes, no transactions needed
job.order = calculatedOrder;
await job.save();This means every drag-and-drop updates exactly one document regardless of column size, eliminating write amplification and the need for multi-document transactions.
We utilize SWR with a custom exponential backoff strategy for network resilience:
- 4xx Errors: Fail fast (e.g., don't retry Unauthorized requests)
- 5xx Errors: Retry with delays of 1s, 2s, 4s to allow server recovery without overwhelming it.
Unlike hosted solutions (Auth0/Clerk) that silo user data, Better Auth gives us full ownership of the user database while maintaining strict security standards.
- Type Safety: End-to-end TypeScript support matches our strict schema requirements.
- Plugins: Modular architecture allows adding features like "Two Factor" or "Admin" without bloat.
- Privacy: User data never leaves our controlled MongoDB instance.
Traditional Redis requires persistent TCP connections, which are problematic in serverless environments like Vercel. Upstash provides a REST-based Redis compatible with Edge runtimes.
- Stateless: Perfect for Next.js API rate limiting middleware.
- Scalable: Per-request pricing fits the traffic pattern of a job tracker (bursty usage).
- Zero Latency Cold Starts: HTTP-based protocol avoids connection pool handshake overhead.
We chose dnd-kit over older libraries (react-beautiful-dnd) for its modern, modular architecture.
- Accessibility: First-class support for keyboard interactions and screen readers.
- Extensibility: Hook-based API allows custom sensors and modifiers (needed for our column/card nested drag logic).
- Performance: Minimized re-renders through virtual DOM abstractions.
We use Zod for runtime schema validation to bridge the gap between static TypeScript types and dynamic API inputs.
- Single Source of Truth: We infer TypeScript types directly from Zod schemas, ensuring API validation logic and type definitions never drift out of sync.
- Developer Experience: "Fail-fast" error messages make backend debugging significantly easier.
{
_id: ObjectId,
userId: string, // Owner reference
name: string, // Board name
columns: ObjectId[], // Ordered column references
isDefault: boolean, // Default board flag
createdAt: Date,
updatedAt: Date
}{
_id: ObjectId,
boardId: ObjectId, // Parent board reference
name: string, // e.g., "Applied", "Interview", "Offer"
order: number, // Display order
createdAt: Date,
updatedAt: Date
}{
_id: ObjectId,
userId: string, // Owner reference
columnId: ObjectId, // Parent column reference
company: string,
position: string,
location?: string,
salary?: string,
url?: string,
notes?: string,
priority: "high" | "medium" | "low",
order: number, // Position within column
interviewDate?: Date,
interviewTime?: string,
interviewTimezone?: string,
reminderSent?: boolean,
followUpSent?: boolean,
createdAt: Date,
updatedAt: Date
}| Method | Endpoint | Description |
|---|---|---|
| GET/POST | /api/auth/* |
Authentication (better-auth) |
| GET | /api/boards |
List user's boards |
| POST | /api/boards |
Create new board |
| GET | /api/jobs |
List jobs (with column filter) |
| POST | /api/jobs |
Create job application |
| PATCH | /api/jobs/[id] |
Update job application |
| DELETE | /api/jobs/[id] |
Delete job application |
| GET | /api/columns |
List columns for a board |
| POST | /api/columns |
Create new column |
| GET | /api/calendar/events |
Get calendar events |
| POST | /api/calendar/sync |
Sync with Google Calendar |
| POST | /api/parse-job |
Parse job details from URL |
| POST | /api/cron/reminders |
Trigger email reminders |
| Script | Command | Description |
|---|---|---|
| Development | npm run dev |
Start dev server at localhost:3000 |
| Build | npm run build |
Create production build |
| Start | npm run start |
Run production server |
| Lint | npm run lint |
Run ESLint checks |
| Lint Fix | npm run lint:fix |
Auto-fix ESLint issues |
| Format | npm run format |
Format with Prettier |
| Format Check | npm run format:check |
Check code formatting |
| Test | npm run test |
Run Vitest in watch mode |
| Test CI | npm run test:ci |
Run tests with coverage report |
This project uses:
- ESLint 9 with flat config for modern linting
- Prettier with Tailwind CSS plugin for consistent formatting
- TypeScript strict mode for type safety
- Zod for runtime validation of API inputs
- Vitest with v8 coverage for unit and integration testing (96%+ model coverage)
// Instead of relative imports:
import { Button } from "../../../components/ui/button";
// Use path aliases:
import { Button } from "@/components/ui/button";-
Push to GitHub
git add . git commit -m "Initial commit" git push origin main
-
Import to Vercel
- Visit vercel.com/new
- Import your GitHub repository
- Vercel auto-detects Next.js settings
-
Configure Environment Variables In Vercel dashboard, go to Settings > Environment Variables and add:
MONGODB_URI=MONGO_DB_NAME=job-trackerBETTER_AUTH_SECRET=BETTER_AUTH_URL=https://your-domain.vercel.appNEXT_PUBLIC_BETTER_AUTH_URL=https://your-domain.vercel.appGOOGLE_CLIENT_ID=GOOGLE_CLIENT_SECRET=UPSTASH_REDIS_REST_URL=UPSTASH_REDIS_REST_TOKEN=RESEND_API_KEY=FROM_EMAIL=CRON_SECRET=
-
Update Google OAuth In Google Cloud Console, add your Vercel domain to authorized redirect URIs:
https://your-domain.vercel.app/api/auth/callback/google -
Deploy Vercel automatically deploys on every push to
main.
For automated email reminders, configure a cron job to call:
POST https://your-domain.vercel.app/api/cron/reminders
Header: Authorization: Bearer <CRON_SECRET>
Vercel Cron or external services like cron-job.org work well.
Contributions are welcome for bug fixes and improvements. Please follow these guidelines:
-
Getting Started
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Make your changes
- Run linting and formatting:
npm run lint && npm run format - Commit with conventional commits:
git commit -m "feat: add new feature" - Push and open a Pull Request
-
Code Standards
- Follow existing code patterns and conventions
- Write TypeScript with proper types (avoid
any) - Use Zod for API input validation
- Keep components small and focused
- Add comments for complex logic
-
Pull Request Process
- Ensure your code passes linting and type checks
- Update documentation if needed
- Provide a clear description of changes
- Reference any related issues
- Vercel AI SDK Integration: Added a powerful streaming AI generator to tailor resumes specifically to scraped job descriptions.
- Data Context Viewer: AI strictly uses facts from your base
UserProfile.resumeTextto prevent hallucinations and enforce factual accuracy. - Inline Editing & Markdown: Generated resumes are editable directly in the UI with a markdown toggle powered by
@tailwindcss/typography. - PDF Export: Added seamless 1-click PDF exporting and clipboard copying for generated resumes.
- Save-on-Complete: Tailored resumes are non-blockingly saved directly to their respective
JobApplicationdocuments.
- MongoDB Auto-Search: Refactored the "Find Roles" tab to durably sync job search results and UI filters (Remote, Experience, Age) to the
UserProfileschema. - Hydration Engine: The
useJobSearchhook natively bridges MongoDB state down to the UI dropdowns, instantly restoring search contexts across devices. - Stale Cache Auto-Trigger: Automatically fires off a fresh background scrape if the local database cache is older than 6 hours.
- Fixed Unbounded Array Vulnerability: Removed the
jobApplicationsarray from theColumnschema to prevent document bloat and potential MongoDB 16MB document limit crashes at scale. - O(1) Drag-and-Drop: Replaced O(N) transactional
$incshifting with fractional midpoint ordering — each reorder now updates exactly one document. - Query Projections: Added strict
.select()projections to all board-fetching queries, excluding heavy text fields (description,notes) from dashboard payloads. - Removed Unnecessary Transactions: Simplified single-document operations (job delete, column move) by removing costly transaction wrappers.
Core Architecture
- Dual-Filtering System: Separated "Raw" job data vs "Inferred" seniority data with confidence scores.
- Strict Schema: Updated
Jobschema for strict seniority levels (Junior, Intermediate, Senior, Lead) and structured years (min/max). - Modular Refactor: Extracted
JobSearchPanelinto 6 sub-components (Bar,Filters,Stats,Results,Card,EmptyState) and centralized regex patterns.
UI/UX Overhaul
- Smart Components: Added
MismatchBadgefor data discrepancies and "Smart Empty States" with context-aware suggestions. - Visual Polish:
- Increased truncation limits (300px) and added location deduplication.
- Refined seniority badge colors with optimized dark-mode contrast.
- Unified search bar into a single elevated unit.
- Swapped "View" (Primary) and "Import" (Secondary) button hierarchy.
- Horizontal Scrolling Layout: Replaced rigid grid with a flexible horizontal scroll workflow (standard Kanban UX).
- Responsive Columns: Fixed layout issues where column headers (e.g., "Rejected") were cut off. Implemented smart truncation with tooltips and minimum column widths (280px).
- Mobile Experience: Improved touch responsiveness and layout stability.
- Logo Carousel Optimization: Replaced glitched GSAP ticker with a robust, fade-in enabled implementation that handles resizing gracefully.
- Scraping Engine: Refined job parser execution to safely use public endpoints, avoiding potential rate limits for personal use.
- Added comprehensive keyboard shortcut system for power users
/key now focuses search bar instantly (no need to click)Shift + K/L/Cfor quick view switching (Kanban/List/Calendar)ESCkey dismisses dialogs and clears searchCshortcut for creating new jobs- Improved command palette (
Cmd/Ctrl + K) with better search
- Custom time picker with 30-minute intervals (8 AM - 7 PM)
- Interview conflict detection on calendar view
- At-a-glance interview details showing company name and time on calendar cells
- Enhanced interview scheduler UX with better timezone support
- Reduced re-renders in dashboard components
- Optimized board data fetching with proper SWR configuration
- Improved Kanban column rendering performance
- Faster search with optimized filtering logic
- Enhanced search bar styling and visibility
- Better visual feedback for active keyboard shortcuts
- Improved dialog transitions and animations
- More consistent button hover states
- Better empty state messaging
- Stale Application Indicator — Visual badge for applications inactive 14+ days
- Upcoming This Week Widget — Quick glance at imminent interviews without opening calendar
- Quick Notes Inline Edit — Jot down notes without opening full edit dialog
- Tags/Labels System — Categorize jobs beyond status (Remote, FAANG, Referral)
- Quick Stats Dashboard — Response rates, weekly/monthly application counts
- Job Status Auto-Archive — Automatically archive old rejected applications
- Google Calendar sync requires manual OAuth flow per user
- Email reminders require external cron service on free Vercel tier
- Mobile drag-and-drop has limited touch support
This project is source-available, not open source.
You MAY:
- View and study the source code for learning purposes
- Fork the repository for educational experimentation
- Use small code snippets (<50 lines) with attribution in non-commercial projects
You MAY NOT:
- Use this code for any commercial purpose without written permission
- Copy, reproduce, or redistribute substantial portions of the codebase
- Create derivative commercial products based on this code
- Remove or modify copyright notices and attributions
- Sell or sublicense this code or derivatives
Sam Bai — Full-Stack Software Engineer
- Portfolio: sambai.dev
- GitHub: github.com/sambai-dev
- LinkedIn: linkedin.com/in/sam-bai1
Built with Next.js 16, React 19, and modern web technologies