Skip to content
anold-aPublic

About

A web application that helps in tracking which companies you applied to,what stage each application is at, whether you followed up and what your overall success rate is.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

JobPulse

Your job search journey, finally in rhythm.

JobPulse is a job application tracker. It gives you an instant pipeline, gentle reminders and zero lost replies. Log every application, move it through your hiring pipeline, see how your search is really going, and keep your mentor or career coach in the loop.

Built with Next.js (App Router), TypeScript, Tailwind CSS, Prisma and PostgreSQL.


Table of contents


Features

Area What you can do
Organize Save the company, role, location, posting link, source, applied date and notes for every application.
Status Move each application through a pipeline: applied, screening, interview, offer, rejected or withdrawn. Use the stage dropdown or the drag-and-drop board (keyboard accessible).
Timeline Every stage change is recorded as an event, so the detail page can always show what happened and when.
Follow up Set follow-up dates and interview dates. Overdue follow-ups are surfaced first.
Progress The dashboard counts applications by stage and works out your response rate and offer rate, and explains how they are calculated.
Search and filter Search by company and role, filter by stage, source and date range, and sort. All of it lives in the URL, so a refreshed or shared link shows the same view.
Notes Keep interviewer names, questions asked and salary ranges with the application they belong to.
Next steps See what is due today and optionally get a daily email with follow-ups and upcoming interviews.
Live updates Changes made in one tab or device appear in your other tabs and devices without a reload.
Custom workflows Define your own pipeline (for example Phone screen, Take-home, Panel) or start from a template for job search, internship or freelance.
Collaboration Invite a mentor or coach, choose which applications are shared, and give them Editor or Viewer access.
Export Download your applications as CSV.

Tech stack

Layer Choice
Framework Next.js (App Router) with server components
Language TypeScript (strict mode, path aliases for src folders)
Styling Tailwind CSS, mobile-first, with a shared type scale and a neutral palette plus a single link accent
Icons lucide-react
Database PostgreSQL (hosted in production, Docker locally)
ORM Prisma
Validation Zod
Passwords Argon2id
Email Resend
Real time Server-Sent Events over a pluggable event channel
Tests Service tests and Playwright end-to-end tests
CI GitHub Actions (lint, type-check, build)
Package manager pnpm

Architecture

Browser: server-rendered HTML plus small client components for interaction
   │
Next.js (App Router)
   ├─ Routes (src/app)                pages, layouts, route handlers. Thin: no business logic
   ├─ Server Actions                  form mutations, validated with Zod
   ├─ Services (src/server/services)  business rules and ownership checks
   ├─ Repositories (src/server/db)    Prisma queries only
   └─ Cross-cutting                   auth, validation, errors, env checks
   │
PostgreSQL (hosted)  ·  email provider  ·  event channel

Rules the codebase follows

  1. Routes stay thin. Pages, layouts and route handlers call into services. They hold no business logic.
  2. Server Actions validate first. Every mutation checks its input with a Zod schema before doing anything else.
  3. Services own the rules. Ownership checks, stage transition rules and permission checks live in src/server/services.
  4. Repositories only talk to the database. Prisma queries live in src/server/db and nothing else imports Prisma directly.
  5. One permission function. Every read or write asks one question: may this user do this action on this resource? That check lives in a single can() function.
  6. History is written with the change. Moving an application writes its StatusEvent in the same database transaction.

How a typical request flows

Adding an application:

  1. The user fills in the form and presses save.
  2. The Server Action validates the input with Zod.
  3. The service attaches the signed-in user's id and checks ownership.
  4. The repository inserts the row.
  5. The tagged cache is revalidated, the list refreshes without a full page reload, and a change event is published for the user's other tabs.

Project structure

src/
├─ app/
│  ├─ (marketing)/        public pages: home, resources, optimize, enterprise
│  ├─ (auth)/             sign up, login, password reset
│  ├─ (app)/              private pages, noindex, protected by the proxy
│  ├─ api/                route handlers (events stream, cron, CSV export)
│  ├─ robots.ts
│  ├─ sitemap.ts
│  └─ opengraph-image
├─ components/            UI components and primitives (Heading, Text, Button, LinkButton)
├─ server/
│  ├─ services/           business rules and ownership checks
│  ├─ db/                 repositories (Prisma queries only)
│  └─ realtime/           event channel adapters (in-memory, Postgres)
├─ types/                 shared types and navigation data
docs/

prisma/                   schema, migrations and seed script

The folder names above follow the architecture rules. Adjust them if your tree differs.


Getting started

Prerequisites

  • Node.js (current LTS)
  • pnpm
  • Docker (for the local PostgreSQL database)

Local setup

  1. Clone the repository and install dependencies:

    git clone https://github.com/<your-username>/job-pulse.git
    cd job-pulse
    pnpm install
  2. Create your environment file and fill in DATABASE_URL:

    cp .env.example .env
  3. Start the database (see Starting the local database).

  4. Run the migrations:

    pnpm db:migrate
  5. Optionally seed sample data:

    pnpm db:seed
  6. Start the dev server:

    pnpm dev

    Open http://localhost:3000.

In development, password reset links print to the terminal, so you do not need an email provider.

Scripts

Command What it does
pnpm dev Start the development server
pnpm build Create a production build
pnpm start Run the production build locally
pnpm lint Run the linter
pnpm db:migrate Create and apply migrations in development
pnpm db:deploy Apply existing migrations to a production database
pnpm db:seed Seed the database with sample data

Before you push, run pnpm build locally. It catches type errors and missing environment variables that pnpm dev hides.


Environment variables

Name Required Purpose
DATABASE_URL yes PostgreSQL connection string
DIRECT_URL production Non-pooled Postgres connection used for LISTEN. Without it, the in-memory channel only works on a single server instance
NEXT_PUBLIC_SITE_URL yes in production Public address, used for links, sitemap and social images
RESEND_API_KEY production only Sends password reset, reminder and invite emails. In development the link prints to the terminal
EMAIL_FROM no Sender address for emails
CRON_SECRET production Secret verified by the cron route that sends reminders

Environment variables are validated at startup, so a missing required value fails fast with a clear message instead of breaking at runtime. Never commit .env. Keep .env.example up to date when you add a variable.


Database

PostgreSQL with Prisma. Main models: User, Session, Application, StatusEvent, Interview, FollowUp, Workflow, WorkflowStage, Reminder, Team, Membership and Comment.

Indexes cover userId, appliedAt and the search fields.

Starting the local database

The local database runs in Docker as a container named jobpulse-db.

docker ps -a                 # check the container exists and see its state
docker start jobpulse-db     # start it
docker ps                    # confirm it is running

Migrations

  • Development: pnpm db:migrate creates and applies a migration after you change schema.prisma.
  • Production: pnpm db:deploy applies the migrations that already exist. Run it against the production database whenever a migration is added.

Authentication

Accounts use email and password.

  • Passwords are hashed with Argon2id.
  • Sessions are random tokens stored in an httpOnly cookie. Only the SHA-256 hash of a token is stored in the database, so a database leak does not expose usable sessions.
  • A proxy file (called middleware before Next.js 16) checks the session cookie on every private (app) route.
  • Sign-in and sign-up attempts are rate limited.So, will reset password
  • Password reset works through an emailed token.
  • Helpers getCurrentUser and requireUser give server code a safe way to read the signed-in user.
  • Sign-up and sign-in input is validated with Zod.

Live updates

Changes made in one tab or device show up in the user's other tabs and devices without a reload.

How it works in short:

  • Stage changes use optimistic updates, then the server confirms. If a save fails, the UI rolls back with a clear message.
  • A Server-Sent Events route streams a user's own changes to their other open tabs and devices.
  • Tabs in the same browser stay in sync with BroadcastChannel.
  • A connection status indicator shows whether live updates are connected.
  • A version column provides optimistic concurrency, so a stale update is rejected and the user is prompted to refresh.

Limits

  • A stream lasts at most 240 seconds, then the browser reconnects. Hobby functions are limited to 300 seconds.
  • Events are at-most-once. After any gap the browser refreshes once to catch up.
  • Each open tab holds one server function while connected. Hidden tabs release it after 60 seconds.

Checking it works

  1. Run the app and open it in two different browsers (or one normal and one private window), signed in as the same user.
  2. Add an application in one. It should appear in the other within a few seconds.
  3. Stop the dev server and start it again. The indicator goes amber, then green, and the page refreshes once.

Upgrade path

Swap the Postgres channel for a hosted pub/sub service by implementing EventChannel (publish, subscribe) in src/server/realtime/ and returning it from getChannel().


Collaboration

Invite a mentor or coach from Settings, Team, choose which applications are Shared, and give them Editor or Viewer access. See docs/collaboration.md for the roles, the privacy model and the limits.

Role Can do
Owner Everything, including managing members and revoking access
Editor View and edit shared applications, and comment
Viewer View shared applications

Also to include:

  • Invite by email with a token and an accept-invite flow
  • Comments and an activity feed on applications
  • Read-only share links with an expiry
  • An audit log of permission-sensitive actions
  • Access revocation and member removal
  • Shared pages are marked noindex

Reminders and email

  • Users set follow-up dates and interview dates, and overdue follow-ups are detected by the service layer.
  • A cron route handler finds due reminders and sends them through Resend. The route verifies CRON_SECRET before doing anything.
  • Notification preferences control which emails a user receives, including the optional daily summary.
  • Emails include an unsubscribe link.

SEO and accessibility

SEO

  • metadataBase, a title template and a default description
  • A per-page metadata helper with canonical URLs
  • Open Graph and Twitter images
  • robots.ts and sitemap.ts
  • WebApplication JSON-LD on the home page
  • Private (app) routes and shared pages are marked noindex

Accessibility

  • One h1 per page, proper landmarks, a skip link and visible focus styles
  • Accessible forms with field-level errors
  • Keyboard support for drag and drop on the pipeline board
  • Charts have text alternatives and a data table fallback

Mobile

  • Mobile-first Tailwind: base classes are for phones, and sm:, md: and lg: add changes for larger screens
  • The navbar switches to a menu button below 1024px, and mega menus open on tap as well as hover

Testing

  • Service tests cover validation, session guards, stage transitions, history writes, permissions (the full role matrix) and stats edge cases such as having no responses yet.
  • Playwright end-to-end tests cover add, edit and delete, and live updates across two tabs.
  • CI runs lint, type-check and build on every push.

Deploying

  1. Set the environment variables in your hosting dashboard.
  2. Run pnpm db:deploy against the production database.
  3. Deploy the app. On Vercel, every branch and pull request gets a preview deployment, so test there before merging to main.
  4. Use separate environment variables for Preview (a test database and test API keys) so testers cannot touch real data.
  5. Configure the scheduled job that calls the reminders cron route, with CRON_SECRET set.

Roadmap

Build one feature at a time. Finish and deploy each before starting the next.

  • 1. Foundation, typography and SEO: strict TypeScript, typed env validation, type scale, route groups, SEO metadata, app shell, CI
  • 2. Database and authentication: Prisma, users and sessions, sign-up and login, protected routes, rate limiting, password reset
  • 3. Application tracking: add, edit, delete and list applications, empty states, pending states and toasts
  • 4. Stage assignment and timeline: pipeline stages, status history, board with drag and drop, interviews and follow-ups
  • 5. Dashboard, search and success rate: stats, charts, URL-based search, filters and sorting, pagination, CSV export
  • 6. Real-time updates: optimistic UI, Server-Sent Events, tab sync, concurrency control
  • 7. Custom workflows and reminders: user-defined pipelines, templates, automation rules, reminder emails
  • 8. Collaboration: teams, roles, comments, share links, audit log

After the eight, in this order:

  1. Resources (guides and templates): the best SEO payoff, since public useful pages are what rank
  2. Optimize (resume tailoring, search analytics, market insights)
  3. Enterprise (university and agency dashboards, reusing the collaboration work)

Commit convention

Commits follow Conventional Commits:

type(scope): short description

Types in use: feat, fix, refactor, perf, style, chore, test, docs.

Examples:

feat(actions): add createApplication server action
fix(forms): keep typed values when validation fails
perf(cache): tag application queries and revalidate on mutation

Commits should be real units of work. If a step is too small for its own commit, merge it into the next one.


Known limitations

  • Email verification, two-factor sign-in and a list of active sessions are not built yet.
  • Live updates are at-most-once and limited by serverless connection time (see Live updates).
  • The in-memory event channel only works on a single server instance. Set DIRECT_URL in production.

About

A web application that helps in tracking which companies you applied to,what stage each application is at, whether you followed up and what your overall success rate is.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages