Skip to content
PacoaldevPublic

About

Track your job applications with style and precision. Watch your career opportunities come to life.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Repository files navigation

APPLYDASH

Track your job applications with style and precision. Organize your pipeline, measure your progress, and never miss a follow-up.

🚀 Live App: https://applydash.vercel.app

🖼️ Screenshots

Landing page

Portada

Dashboard — Table view

Panel

Detail menu

Detail Menu

Dashboard — Kanban view

Kanban

Matching view

✨ Features

Dashboard

  • Statistics panel — Total applications, response rate, interviews, offers, rejections, and overdue follow-ups
  • Weekly activity chart — Collapsible area chart showing applications per week with peak and average metrics
  • Quick filters — All, This week, Interviewing, No response 14+ days, Follow-up due, Offers
  • Hide rejected toggle — Button next to filters to hide/show rejected applications; shows count of hidden rows
  • Table view — Editable AG Grid with sorting, filtering, and pagination
  • Multi-row editing — Edit multiple rows freely and save all changes at once with a single Update button; dirty rows highlighted with a yellow left border
  • Detail panel — Double-click any row to open a full slide-in side panel with all fields editable in a comfortable form
  • Kanban view — Visual pipeline by status with drag-and-drop and inline status changes
  • Status badges — Color-coded raised-button badges with progressive scale (blue → amber → orange → green → red)
  • Days column — Calculated "Days" column showing elapsed days since application; color-coded (yellow > 7d, orange > 14d, red > 21d)
  • Clickable links — Open job postings directly from the grid
  • Tags — Comma-separated labels per job (e.g. Frontend, Remote EU)
  • Follow-up reminders — nextFollowUpDate with visual alerts when due
  • Status history — Automatic log of every status change per application
  • CSV export & import — Export respects active filters; import auto-detects ;/, separator, Spanish column headers, and UTF-8/Windows-1252 encoding
  • Delete confirmation — Two-step confirmation before deleting a row to prevent accidents
  • Keyboard shortcuts — N new job, S save/update, Escape cancel
  • Matching History Panel — Compare any application in real-time with your historical evaluation profiles stored locally.
  • Client-Side IndexedDB Sync — Local storage of all matching history records, allowing instant client-side matching searches.
  • Strict URL & ID Matching — Robust extraction of unique Job IDs (such as LinkedIn's /view/ID, InfoJobs' query IDs, and Indeed's jk hashes) to prevent broad domain matching errors.
  • Score-Based Recommendation Engine — Automatic positive recommendation rules for applications scoring 4.0/5.0 or above.

App experience

  • Light / dark / system theme — Toggle in the navbar (persists in localStorage)
  • English & Spanish UI — Language switcher (EN / ES) across landing and dashboard
  • Toast notifications — Real-time feedback via Sonner on save, update, delete, and import
  • Responsive design — Works on desktop and mobile; filters and view toggle stack cleanly on small screens; table scroll optimized for touch

Authentication & data

  • User auth — Supabase (register, login, password reset)
  • Admin panel — Separate JWT-based admin login at /admin
  • Secure storage — PostgreSQL via Prisma; jobs scoped per user

Browser extension

Capture job postings from LinkedIn, Indeed, InfoJobs, Michael Page, Tecnoempleo, Infoempleo, Joppy, and any other job site directly into your dashboard — with a single click, no copy-paste.

  • On-demand capture, not crawling — A Manifest V3 content script (extension/content.js) is injected into the active tab but does nothing until you open the popup. It never runs in the background, so there is no automatic crawling of job boards.
  • JSON-LD first, DOM second — Extraction prefers schema.org JobPosting structured data (<script type="application/ld+json">, including nested @graph), the most stable source across portals, then enriches it with portal-specific DOM selectors.
  • Per-portal scrapers — Dedicated extractors for LinkedIn, Indeed, InfoJobs, Michael Page, Tecnoempleo, Infoempleo and Joppy, plus a generic fallback for any other site.
  • Robust LinkedIn extractor — LinkedIn gets the richest fallback chain: job title/company from unified top-card selectors, then document.title parsing ("Position | Company"), then og:title; plus workplace type (Remote/Hybrid/Office), location, salary, and the recruiter name and profile URL from the hiring-team/poster section.
  • Salary normalization — Detected salaries are normalized to a consistent format (e.g. 35.000€ - 40.000€, $50,000 - $70,000), including k suffix handling and EUR/USD/GBP detection.
  • Auto-retry — The popup retries scraping up to 3 times (500 ms / 1.5 s / 3 s) so single-page-app portals have time to render; any missing field can be filled in manually before saving.

Extension Captures

Extension popup

🚀 Tech Stack

Layer Technology
Framework Next.js 15 (latest patched), React 19
Styling Tailwind CSS v4, shadcn/ui, Radix UI
Data grid AG Grid Community v34
Charts Recharts 2
ORM Prisma 6
Database PostgreSQL (Supabase)
Auth Supabase Auth + JWT (admin)
Validation Zod + react-hook-form
Notifications Sonner
Deployment Vercel

🏗️ Quick Start

Prerequisites

  • Node.js 18+
  • PostgreSQL database (Supabase recommended)
  • Supabase project for authentication

Installation

  1. Clone the repository

    git clone https://github.com/Pacoaldev/APPLYDASH.git
    cd APPLYDASH
  2. Install dependencies

    npm install
  3. Set up environment variables

    Copy .env.example to .env.local and fill in the values:

    cp .env.example .env.local
    # Supabase
    NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
    NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
    
    # Site URL
    NEXT_PUBLIC_SITE_URL=http://localhost:3000
    
    # Database — Transaction pooler (port 6543) for serverless / production
    DATABASE_URL=postgresql://postgres.xxxx:[PASSWORD]@aws-0-eu-west-2.pooler.supabase.com:6543/postgres
    
    # Direct connection (port 5432) — used by Prisma migrations
    DIRECT_URL=postgresql://postgres:[PASSWORD]@db.xxxx.supabase.co:5432/postgres
    
    # JWT Secret (Supabase → Settings → API → JWT Settings)
    JWT_SECRET=your_jwt_secret

    ⚠️ Never commit .env.local to version control.

  4. Initialize the database

    Generate the Prisma client:

    npx prisma generate

    Bootstrap tables (recommended for new setups):

    curl -X POST http://localhost:3000/api/migrate

    Or, if you use versioned Prisma migrations:

    npx prisma migrate dev
  5. Start the development server

    npm run dev

    Visit http://localhost:3000.

📊 Data model

Job fields

Field Description
company Company name
position Role / job title
type Remote, Office, or Hybrid
status Pipeline stage (Applied, Interview, Offer, Rejected, …)
appliedDate Date you applied
platform Where you found the job (LinkedIn, Indeed, …)
applicationLink URL to the posting
location Job location
salary Salary info (free text)
notes Personal notes
tags Array of labels
nextFollowUpDate Reminder date for follow-up — auto-set to appliedDate + 7 days when not specified

Status history

Every create and status change writes a row to job_status_history (oldStatus, newStatus, changedAt). View it from the dashboard via the History button on a selected row.

🧩 Browser extension

A Chrome/Edge (Manifest V3) extension that captures the job posting on your current tab and saves it to your ApplyDash dashboard. Source lives in extension/ (manifest.json, content.js, popup.html, popup.js, icons/).

How it works

  1. Content script (content.js) — Injected into every tab at document_idle (matches: <all_urls>), but it is completely passive: it only reacts to a { action: "scrape" } message from the popup via chrome.runtime.onMessage. There is no background worker and no autonomous crawling.
  2. Extraction (scrapeJobPage) — Runs when the popup asks for it, in two layers:
    • JSON-LD (extractJsonLd) — Parses application/ld+json scripts for a JobPosting node (recursing into @graph), reading title, hiringOrganization, location, employment type and baseSalary.
    • Portal-specific DOM selectors — The hostname is matched and a dedicated scraper runs: scrapeLinkedIn, scrapeIndeed, scrapeInfoJobs, scrapeMichaelPage, scrapeTecnoempleo, scrapeInfoempleo, scrapeJoppy, or scrapeGeneric as fallback.
    • The LinkedIn path (scrapeLinkedIn) additionally falls back to document.title → og:title, normalizes workplace type/location/salary, and pulls the recruiter name + LinkedIn URL from the hiring-team section.
  3. Popup (popup.js) — On open, it scrapes the active tab with up to 3 retries (500 ms / 1.5 s / 3 s) to let SPAs render, pre-fills the form, and lets you review/edit before saving.
  4. Save — Sends a POST /api/jobs request with credentials: include (session cookies) to your ApplyDash instance. applicationLink is taken from the current tab URL, appliedDate is set to today, and nextFollowUpDate defaults to appliedDate + 7 days.

Install (developer mode)

  1. Open chrome://extensions → enable Developer mode
  2. Load unpacked → select the extension/ folder
  3. Click Reload (↺) on the extension after any icon or code update
  4. Log in to ApplyDash in the same browser
  5. Open a job posting (LinkedIn, Indeed, InfoJobs, etc.) → click the extension icon → review the auto-detected fields → Save to ApplyDash

The extension pre-fills ApplyDash URL with the production URL (https://applydash.vercel.app) automatically — no manual configuration needed. For local development, change it to http://localhost:3000.

ℹ️ Note: This is a capture-on-demand tool, not an unattended scraper. It reads the page you already have open, only when you click the extension, and never logs in to job portals on your behalf.

Regenerating extension icons

The extension icons are generated from public/applydashlogo.svg. If you update the logo, regenerate them with:

node scripts/generate-icons.mjs

Then reload the extension in chrome://extensions.

☁️ Deploying to Vercel

Configured for Vercel — no custom build settings required.

Environment variables

Add these in Vercel → Settings → Environment Variables:

Variable Description
NEXT_PUBLIC_SUPABASE_URL Supabase project URL
NEXT_PUBLIC_SUPABASE_ANON_KEY Supabase anon key
NEXT_PUBLIC_SITE_URL Deployment URL (e.g. https://applydash.vercel.app)
DATABASE_URL Transaction pooler URL (port 6543)
DIRECT_URL Direct connection URL (port 5432)
JWT_SECRET JWT secret from Supabase

⚠️ Serverless functions must use the transaction pooler for DATABASE_URL, not the direct connection.

Supabase auth URLs

In Supabase → Authentication → URL Configuration:

  • Site URL: https://applydash.vercel.app
  • Redirect URLs: https://applydash.vercel.app/**

Post-deploy migration

After the first deploy, apply schema updates:

curl -X POST https://applydash.vercel.app/api/migrate

Or:

npm run migrate:deploy

🛠️ Available scripts

Command Description
npm run dev Start dev server (Turbopack)
npm run build Production build
npm run start Start production server
npm run lint Run ESLint
npm run check-env Validate required env vars
npm run build:strict Check env + build
npm run migrate:deploy Run Prisma migrations (production)
npm run migrate:prod Production migration script
npx prisma generate Generate Prisma client
npx prisma studio Open database browser

📁 Project structure

APPLYDASH/
├── app/
│   ├── (auth)/           # Login, register, password reset
│   ├── (admin)/          # Admin auth & dashboard
│   ├── api/
│   │   ├── jobs/         # Extension & external job creation
│   │   └── migrate/      # Database bootstrap / schema updates
│   └── dashboard/        # Main user dashboard (server page + actions)
├── components/
│   ├── job-dashboard.tsx    # Stats, filters, view toggle, detail panel
│   ├── jobGrid.tsx          # AG Grid table with multi-row editing
│   ├── job-kanban.tsx       # Kanban pipeline
│   ├── activity-chart.tsx   # Weekly activity area chart (Recharts)
│   ├── job-detail-panel.tsx # Slide-in detail/edit panel
│   ├── dashboard-stats.tsx
│   ├── quick-filters.tsx
│   ├── status-history-panel.tsx
│   ├── theme-provider.tsx
│   ├── locale-provider.tsx
│   └── ui/                  # shadcn primitives
├── extension/            # Chrome extension (job capture)
├── lib/
│   ├── jobService.ts     # Job queries
│   ├── job-utils.ts      # Filters, stats, status colors
│   └── i18n/             # EN / ES translations
├── prisma/
│   └── schema.prisma     # User, Job, JobStatusHistory, Admin
├── data/
│   └── cellContents.ts   # Autocomplete lists (company, position, status)
├── types/
│   └── job.ts            # TypeScript interfaces
├── utils/supabase/       # Supabase clients & middleware
└── validation/           # Zod schemas

🎨 Theming & i18n

Preference Storage key Options
Theme applydash-theme light, dark, system
Language applydash-locale en, es

Theme uses CSS variables in app/globals.css (shadcn .dark class). Language strings live in lib/i18n/translations.ts.

🤝 Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Commit your changes: git commit -m 'Add amazing feature'
  4. Push to the branch: git push origin feature/amazing-feature
  5. Open a Pull Request

📄 License

MIT License — see LICENSE.

🙋‍♂️ Support


📝 Changelog

August 2026 — UI Layout, Responsiveness & Features

  • Matching History panel — Added MatchingHistoryPanel to analyze and visualize job matching.
  • Client-Side IndexedDB Sync — Cached matching records in IndexedDB. Allowed instant local searches and query fallbacks.
  • Sparkles panel shortcut — Integrated direct matching trigger in JobDetailPanel.
  • Forced mobile table layout — Disabled Kanban toggle below 768px in JobDashboard.
  • Horizontal filter scrolling — Enabled scrollbar-less overflow-x scrolling in QuickFilters.
  • In Progress status — Implemented status tracking with localization support.
  • Grid column resizing — Adjusted AG Grid column constraints. Increased the tracking (Seguimiento) column width (minWidth to 185, maxWidth to 240) to prevent the 5th icon (Archivar) from being cut off. Reduced the notes (Notas) column flex factor from 2 to 1 to prevent it from dynamically taking up too much horizontal space.
  • Tracking cell order — Reordered jobGrid actions for better usability.
  • Admin utilities and endpoints — Implemented test management API endpoints. Standardized middleware and authentication routes.
  • Simplified footer branding — Cleaned branding logo and tagline from footer.

Late July 2026 — Usability & Functionality Enhancements

  • Keyboard shortcuts modal — Clickable keyboard helper modal added to the navbar (EN/ES) displaying commands (N, S, Escape).
  • Recruiter fields — Added recruiterName, recruiterEmail, and recruiterLinkedin to the Job model and form inside the slide-in detail panel.
  • Subtasks checklist — Fully interactive task checklist within the job detail panel, persisted directly inside a tasks JSON field in the database.
  • Closed status stats sync — Jobs with "Closed" ("Oferta cerrada") status now count towards the "Rejected" ("Rechazadas") stat block on the dashboard header.
  • Robust LinkedIn scraper — Scraper now falls back to browser tab title parsing for title/company, extracts recruiter name and LinkedIn URL from the hiring team section ("Anunciante del empleo"), and uses regex fallbacks for modal type (Remote/Office/Hybrid).

July 2026 — UX/UI improvements

  • Weekly activity chart — Collapsible area chart (Recharts) showing applications per week with peak and average; lives between stats and filters
  • Job detail panel — Double-click any row to open a full slide-in side panel with all fields editable; saves directly to the database
  • Multi-row editing — Edit multiple rows before saving; each dirty row gets a yellow left border; single "Update (n)" button saves all at once
  • Delete confirmation — Two-step confirm before deleting a row to prevent accidental deletions
  • Hide rejected toggle — Button next to filters shows count of hidden rejected rows; state persists across reloads via localStorage
  • Days elapsed column — New "Días/Days" column showing days since application; color-coded yellow > 7d, orange > 14d, red > 21d
  • Custom favicon — favicon.png and apple-touch-icon.png generated from the ApplyDash logo SVG
  • Keyboard shortcuts — N = new job, S = save/update, Escape = cancel/close
  • Export respects active filter — CSV export now downloads only the rows currently visible in the grid
  • Quick notes in extension — notes field added to the browser extension popup
  • Extension scraper rewritten — Uses JSON-LD structured data first (most stable), then portal-specific DOM selectors with auto-retry; supports LinkedIn, Indeed, InfoJobs, Michael Page, Tecnoempleo, Infoempleo, Joppy, and generic fallback
  • Salary normalization — Extension normalizes salary to 35.000€ - 40.000€ or $50,000 - $70,000 format including k suffix support

July 2026 — Data integrity & UX fixes

  • Job ordering — jobs now sort by createdAt (insertion timestamp) so new entries always appear at the bottom with the next sequential number; createdAt column added to the jobs table via Supabase SQL migration
  • Grid height persisted — table height is now restored from localStorage on page reload using useEffect, avoiding the SSR hydration mismatch that was resetting the size every time
  • CSV import rewritten — parser now handles semicolon (;) and comma (,) separators automatically; strips the leading index column (#); maps Spanish column headers (Empresa, Puesto, Fecha aplicación, Ubicación, etc.); normalizes DD/MM/YYYY dates to YYYY-MM-DD
  • CSV encoding fixed — import detects UTF-8 BOM, pure UTF-8, and Windows-1252 automatically so €, á, é, ñ and other special characters import correctly from files generated by Excel or LibreOffice on Windows
  • CSV export with BOM — exported files now include a UTF-8 BOM (\uFEFF) so Excel and LibreOffice open them with correct encoding without prompting
  • Type field i18n — job type (Remote/Office/Hybrid) is now stored canonically in English and displayed translated, matching the same pattern used for status; fixed mismatched typeEs array order
  • Type → Location auto-fill — selecting Remote/Remoto automatically sets the Location field to match if it was empty or undisclosed
  • canonicalType / displayType — new utility functions added to lib/job-utils.ts mirroring canonicalStatus / displayStatus

July 2026 — Performance & quality improvements (Lighthouse)

  • Logo optimized — SVG (1127 KiB) converted to WebP (15 KiB) for navbar and footer; 98% size reduction
  • LCP improved — removed animation delay on the landing page subtitle so the browser paints it immediately
  • CLS fixed — main element now has a stable min-height to prevent layout shift on hydration
  • Font display — added display: "swap" to Geist Sans and Geist Mono to prevent invisible text during load
  • Security headers — added Content-Security-Policy and Cross-Origin-Opener-Policy headers in next.config.ts
  • Accessibility — footer links now always show underline (not only on hover) to meet WCAG contrast requirements
  • Status badges — redesigned with raised button effect and progressive color scale (blue → amber → orange → green → red)
  • Row order preserved — editing or saving a job no longer reorders the table
  • Footer i18n — footer now respects the active locale (EN/ES)
  • Extension status fix — resolved duplicate id="status" bug that prevented the status field from being sent correctly

July 2026 — Vercel migration

  • Migrated from DigitalOcean (Docker) to Vercel — removed output: standalone and custom build scripts; Vercel now auto-detects Next.js

  • Next.js upgraded to latest patched version (CVE-2025-66478 fix)

  • Supabase connection pooler — DATABASE_URL now uses the Transaction pooler (port 6543) required for Vercel serverless; DIRECT_URL added for Prisma migrations

  • Email redirect fix — signUp now passes emailRedirectTo using NEXT_PUBLIC_SITE_URL so confirmation emails link to the live domain instead of localhost

  • nextFollowUpDate auto-calculation — new jobs get appliedDate + 7 days automatically in both the dashboard form and the browser extension API

  • Extension icons fixed — regenerated as properly centered square PNGs from the SVG logo using scripts/generate-icons.mjs

  • Extension URL pre-filled — production URL hardcoded as default so users don't need to type it manually

  • Mobile layout — filter tabs and view toggle (Tabla/Kanban) now stack on separate rows; table container uses touch-action: pan-y to prevent accidental column drags on touch devices

  • Job ordering — jobs now sort by createdAt (insertion timestamp) so new entries always appear at the bottom with the next sequential number; createdAt column added to the jobs table via Supabase SQL migration

  • Grid height persisted — table height is now restored from localStorage on page reload using useEffect, avoiding the SSR hydration mismatch that was resetting the size every time

  • CSV import rewritten — parser now handles semicolon (;) and comma (,) separators automatically; strips the leading index column (#); maps Spanish column headers (Empresa, Puesto, Fecha aplicación, Ubicación, etc.); normalizes DD/MM/YYYY dates to YYYY-MM-DD

  • CSV encoding fixed — import detects UTF-8 BOM, pure UTF-8, and Windows-1252 automatically so €, á, é, ñ and other special characters import correctly from files generated by Excel or LibreOffice on Windows

  • CSV export with BOM — exported files now include a UTF-8 BOM (\uFEFF) so Excel and LibreOffice open them with correct encoding without prompting

  • Type field i18n — job type (Remote/Office/Hybrid) is now stored canonically in English and displayed translated, matching the same pattern used for status; fixed mismatched typeEs array order

  • Type → Location auto-fill — selecting Remote/Remoto automatically sets the Location field to match if it was empty or undisclosed

  • canonicalType / displayType — new utility functions added to lib/job-utils.ts mirroring canonicalStatus / displayStatus

July 2026 — Performance & quality improvements (Lighthouse)

  • Logo optimized — SVG (1127 KiB) converted to WebP (15 KiB) for navbar and footer; 98% size reduction
  • LCP improved — removed animation delay on the landing page subtitle so the browser paints it immediately
  • CLS fixed — main element now has a stable min-height to prevent layout shift on hydration
  • Font display — added display: "swap" to Geist Sans and Geist Mono to prevent invisible text during load
  • Security headers — added Content-Security-Policy and Cross-Origin-Opener-Policy headers in next.config.ts
  • Accessibility — footer links now always show underline (not only on hover) to meet WCAG contrast requirements
  • Status badges — redesigned with raised button effect and progressive color scale (blue → amber → orange → green → red)
  • Row order preserved — editing or saving a job no longer reorders the table
  • Footer i18n — footer now respects the active locale (EN/ES)
  • Extension status fix — resolved duplicate id="status" bug that prevented the status field from being sent correctly

July 2026 — Vercel migration

  • Migrated from DigitalOcean (Docker) to Vercel — removed output: standalone and custom build scripts; Vercel now auto-detects Next.js
  • Next.js upgraded to latest patched version (CVE-2025-66478 fix)
  • Supabase connection pooler — DATABASE_URL now uses the Transaction pooler (port 6543) required for Vercel serverless; DIRECT_URL added for Prisma migrations
  • Email redirect fix — signUp now passes emailRedirectTo using NEXT_PUBLIC_SITE_URL so confirmation emails link to the live domain instead of localhost
  • nextFollowUpDate auto-calculation — new jobs get appliedDate + 7 days automatically in both the dashboard form and the browser extension API
  • Extension icons fixed — regenerated as properly centered square PNGs from the SVG logo using scripts/generate-icons.mjs
  • Extension URL pre-filled — production URL hardcoded as default so users don't need to type it manually
  • Mobile layout — filter tabs and view toggle (Tabla/Kanban) now stack on separate rows; table container uses touch-action: pan-y to prevent accidental column drags on touch devices

Made with ❤️ for job seekers everywhere

About

Track your job applications with style and precision. Watch your career opportunities come to life.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages