Skip to content

issue 112#125

Open
armaansharma-dev wants to merge 9 commits intoyb175:mainfrom
armaansharma-dev:onmyway
Open

issue 112#125
armaansharma-dev wants to merge 9 commits intoyb175:mainfrom
armaansharma-dev:onmyway

Conversation

@armaansharma-dev
Copy link
Copy Markdown
Collaborator

@armaansharma-dev armaansharma-dev commented Apr 11, 2026

#112

Summary by CodeRabbit

  • New Features

    • Added job bookmarking and "Saved Jobs" page to view bookmarked opportunities.
    • Introduced pagination for job listings.
    • Added toast notification system for user feedback.
  • Bug Fixes

    • Fixed resume upload response handling.
    • Corrected navigation behavior after sign-out.
  • UI/UX Improvements

    • Redesigned header with sign-in/sign-up flows and navigation menu.
    • Enhanced job cards with bookmark buttons and improved hover effects.
    • Implemented new layout components and semantic design tokens throughout the app.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive UI component library with design tokens, refactors routing with error handling and notification providers, adds bookmark functionality, implements resume checking and pagination, and updates multiple pages to use new shared layout primitives and toast-based notifications.

Changes

Cohort / File(s) Summary
Routing & App Architecture
frontend/src/App.tsx
Refactors routing structure by creating AppRoutes component, wrapping Router with ErrorBoundary, BookmarksProvider, ToastProvider; adds protected /saved route for SavedPage.
UI Component Library
frontend/src/components/ui/Button.tsx, frontend/src/components/ui/Card.tsx, frontend/src/components/ui/Badge.tsx, frontend/src/components/ui/Alert.tsx, frontend/src/components/ui/Input.tsx
Introduces new reusable UI components with TypeScript support, variant/size props, and forwardRef implementations; includes loading states, icons, and error handling.
UI Utilities & Providers
frontend/src/components/ui/PageLayout.tsx, frontend/src/components/ui/PageTransition.tsx, frontend/src/components/ui/Skeleton.tsx
Adds page layout primitives (Container, PageContainer, PageHeader, GridBackground), fade-in transitions, and skeleton loaders for consistent page structure.
Toast & Error Handling
frontend/src/components/ui/ToastProvider.tsx, frontend/src/components/ui/Toast.tsx, frontend/src/components/ui/ErrorBoundary.tsx
Implements toast notification system with auto-dismiss, context-based provider, and error boundary for catching render-time errors with retry capability.
Bookmarks System
frontend/src/components/ui/BookmarksProvider.tsx, frontend/src/components/ui/index.ts
Adds bookmark management via context and localStorage persistence; exports all UI components and hooks from barrel module.
Tour & Interactive Features
frontend/src/components/ui/WelcomeTour.tsx
Introduces multi-step guided tour component with localStorage-based completion tracking and dynamic tooltip positioning.
Job Listing Features
frontend/src/components/Jobs/JobCard.tsx, frontend/src/components/Jobs/JobList.tsx
Adds bookmark button to job cards with toggle functionality; implements pagination with Previous/Next/page buttons and page indicators.
Header & Navigation
frontend/src/components/Header.tsx
Restructures header with conditional auth flows: Sign In/Up buttons when signed out, avatar dropdown with navigation when signed in; adds centered nav bar for authenticated users.
Form & Landing Updates
frontend/src/components/Landing/CTAButton.tsx, frontend/src/components/Landing/Hero.tsx
Refactors CTA button with async resume checking and conditional navigation; updates Hero with design tokens and Container wrapper for consistent layout.
Page Implementations
frontend/src/pages/JobPage.tsx, frontend/src/pages/LandingPage.tsx, frontend/src/pages/ProfilePage.tsx, frontend/src/pages/SavedPage.tsx
Restructures pages with new layout primitives, toast notifications, and state management; JobPage adds resume checking and refresh; ProfilePage adds resume fetching and preferences UI; new SavedPage displays bookmarked jobs.
Resume Service & Config
frontend/src/services/resume.ts, frontend/src/components/Profile/ResumeUpdateSection.tsx, frontend/tailwind.config.js
Adds checkResumeExists() and getResume() service functions; updates ResumeUpdateSection with conditional button text; extends Tailwind config with semantic color tokens, typography scales, and animations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • Issue #71: The PR directly implements and extends the ProfilePage and ResumeUpdateSection components with resume fetching and preferences UI hooks as requested.
  • Issue #70: The PR adds resume service functions (checkResumeExists, getResume) and updates UI pages to check resume state, directly supporting the requested resume page components.

Possibly related PRs

  • PR #100: This PR's frontend resume service functions (checkResumeExists/getResume) depend on the backend GET /resume endpoint and response parsing introduced there.
  • PR #111: Both PRs refactor App.tsx routing with the same provider wrapping pattern, introduce AppRoutes, add the protected /saved route, and implement SavedPage.
  • PR #109: Both PRs modify App.tsx routing setup, with PR #109 removing /resume route and this PR refactoring routes into AppRoutes and adding /saved.

Suggested reviewers

  • yb175

Poem

🐰 Hopping through components with glee,
Bookmarks and toasts and routing so free,
With design tokens gleaming so bright,
The UI's a burrow of pure delight!
~Rabbit DevOps 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'issue 112' is vague and non-descriptive. It references an issue number but does not convey what actual changes or features are implemented in the changeset. Revise the title to clearly describe the main change, such as 'Add saved jobs feature with bookmarks, UI components, and profile enhancements' or a more specific summary of the primary implementation.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/services/resume.ts (1)

8-13: ⚠️ Potential issue | 🟠 Major

Do not treat failed uploads as successful responses.
Line 13 returns parsed JSON without an res.ok check, so 4xx/5xx upload failures can flow through as success paths.

As per coding guidelines, "ONLY flag issues that fall into one of these HIGH-IMPACT categories: 3. Runtime crashes ... unhandled promise rejections in async handlers".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/services/resume.ts` around lines 8 - 13, The current upload flow
returns parsed JSON unconditionally after calling fetchWithAuth, which lets
4xx/5xx responses be treated as success; in the function that calls
fetchWithAuth (look for the method that posts to "/resume" where res is
awaited), check res.ok and, if false, read the error payload (res.text() or
res.json()) and throw a descriptive error including res.status and the body
before returning the success payload; replace the direct return of res.json()
with this conditional check so callers receive a rejected promise on failed
uploads.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/Landing/CTAButton.tsx`:
- Around line 24-45: The effect can leave stale resume state across
sign-out/sign-in and race with the async checkResume call; update the hook to
reset hasResume immediately on sign-out and cancel/ignore stale async results:
when isSignedIn becomes false call setHasResume(false) (and
setIsLoading(false)), and inside the effect that runs when isSignedIn/isLoaded
create a cancellation flag or requestId (e.g., local let cancelled = false or a
ref) before calling checkResumeExists(fetchRef.current), then only call
setHasResume(exists) and setIsLoading(false) if not cancelled and the current
sign-in session still applies; also return a cleanup that sets cancelled = true
to prevent stale updates from checkResumeExists from overwriting the new
session’s state.

In `@frontend/src/components/ui/ErrorBoundary.tsx`:
- Line 44: Replace the direct rendering of the exception text in the
ErrorBoundary fallback UI (the expression this.state.error?.message inside the
ErrorBoundary component) with a generic user-facing message, and ensure the
actual error is recorded via console.error or your telemetry/logger (e.g., call
console.error(this.state.error) or send to reportError) so internal details are
not exposed to users; update the renderFallback logic to show a fixed safe
string like "Something went wrong" while keeping the logging/reporting of
this.state.error intact.

In `@frontend/src/components/ui/WelcomeTour.tsx`:
- Around line 4-8: The TourStep interface declares position?: 'top' | 'bottom' |
'left' | 'right' but the WelcomeTour component's rendering logic (e.g., the
renderStep/getPlacement code path) currently treats every non-'top' value as
'bottom', so either narrow the TourStep.position type to only 'top' | 'bottom'
to reflect implemented behavior, or implement the missing 'left' and 'right'
placements in the WelcomeTour rendering (update the function that maps
step.position to tooltip/popover placement and the CSS/positioning logic for
'left' and 'right' branches) so the public type matches actual rendering; adjust
types and unit/UI tests accordingly.

In `@frontend/src/pages/JobPage.tsx`:
- Line 1: The resume-check effect in JobPage doesn't react to auth changes
because fetchWithAuth from useApi is recreated each render; memoize
fetchWithAuth inside useApi using useCallback (so it only changes when its true
dependencies change), then update the resume effect to depend on [fetchWithAuth]
(removing any ref-workaround) so hasResume updates on auth/token changes without
causing repeated API calls; target the fetchWithAuth export in useApi and the
resume effect that sets hasResume in JobPage.

---

Outside diff comments:
In `@frontend/src/services/resume.ts`:
- Around line 8-13: The current upload flow returns parsed JSON unconditionally
after calling fetchWithAuth, which lets 4xx/5xx responses be treated as success;
in the function that calls fetchWithAuth (look for the method that posts to
"/resume" where res is awaited), check res.ok and, if false, read the error
payload (res.text() or res.json()) and throw a descriptive error including
res.status and the body before returning the success payload; replace the direct
return of res.json() with this conditional check so callers receive a rejected
promise on failed uploads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b338c4bc-3064-4d12-b749-5540f9d74dcf

📥 Commits

Reviewing files that changed from the base of the PR and between 3f48962 and 477c8f3.

⛔ Files ignored due to path filters (1)
  • frontend/src/assets/logo1.svg is excluded by !**/*.svg
📒 Files selected for processing (27)
  • frontend/src/App.tsx
  • frontend/src/components/Header.tsx
  • frontend/src/components/Jobs/JobCard.tsx
  • frontend/src/components/Jobs/JobList.tsx
  • frontend/src/components/Landing/CTAButton.tsx
  • frontend/src/components/Landing/Hero.tsx
  • frontend/src/components/Profile/ResumeUpdateSection.tsx
  • frontend/src/components/ui/Alert.tsx
  • frontend/src/components/ui/Badge.tsx
  • frontend/src/components/ui/BookmarksProvider.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/Card.tsx
  • frontend/src/components/ui/ErrorBoundary.tsx
  • frontend/src/components/ui/Input.tsx
  • frontend/src/components/ui/PageLayout.tsx
  • frontend/src/components/ui/PageTransition.tsx
  • frontend/src/components/ui/Skeleton.tsx
  • frontend/src/components/ui/Toast.tsx
  • frontend/src/components/ui/ToastProvider.tsx
  • frontend/src/components/ui/WelcomeTour.tsx
  • frontend/src/components/ui/index.ts
  • frontend/src/pages/JobPage.tsx
  • frontend/src/pages/LandingPage.tsx
  • frontend/src/pages/ProfilePage.tsx
  • frontend/src/pages/SavedPage.tsx
  • frontend/src/services/resume.ts
  • frontend/tailwind.config.js

Comment on lines +24 to +45
useEffect(() => {
if (!isLoaded) return;

if (!isSignedIn) {
setIsLoading(false);
return;
}

const checkResume = async () => {
try {
const exists = await checkResumeExists(fetchRef.current);
setHasResume(exists);
} catch (err) {
console.error("Failed to check resume:", err);
setHasResume(false);
} finally {
setIsLoading(false);
}
};

checkResume();
}, [isSignedIn, isLoaded]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset and cancel auth-derived resume checks.

Without clearing hasResume and ignoring stale async results here, a sign-out/sign-in sequence can reuse the previous session's resume state and send the next user to the wrong page.

Suggested fix
  useEffect(() => {
    if (!isLoaded) return;
 
    if (!isSignedIn) {
+     setHasResume(null);
      setIsLoading(false);
      return;
    }
 
+   setHasResume(null);
+   setIsLoading(true);
+   let cancelled = false;
+
    const checkResume = async () => {
      try {
        const exists = await checkResumeExists(fetchRef.current);
-       setHasResume(exists);
+       if (!cancelled) setHasResume(exists);
      } catch (err) {
        console.error("Failed to check resume:", err);
-       setHasResume(false);
+       if (!cancelled) setHasResume(false);
      } finally {
-       setIsLoading(false);
+       if (!cancelled) setIsLoading(false);
      }
    };
 
-   checkResume();
+   void checkResume();
+
+   return () => {
+     cancelled = true;
+   };
  }, [isSignedIn, isLoaded]);
As per coding guidelines, ONLY flag issues that fall into one of these HIGH-IMPACT categories: Broken or incorrect React behavior (missing keys causing state corruption, infinite render loops, stale closures causing incorrect data, missing dependencies in useEffect that cause bugs).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/Landing/CTAButton.tsx` around lines 24 - 45, The
effect can leave stale resume state across sign-out/sign-in and race with the
async checkResume call; update the hook to reset hasResume immediately on
sign-out and cancel/ignore stale async results: when isSignedIn becomes false
call setHasResume(false) (and setIsLoading(false)), and inside the effect that
runs when isSignedIn/isLoaded create a cancellation flag or requestId (e.g.,
local let cancelled = false or a ref) before calling
checkResumeExists(fetchRef.current), then only call setHasResume(exists) and
setIsLoading(false) if not cancelled and the current sign-in session still
applies; also return a cleanup that sets cancelled = true to prevent stale
updates from checkResumeExists from overwriting the new session’s state.

Something went wrong
</h1>
<p className="text-text-secondary mb-6">
{this.state.error?.message || 'An unexpected error occurred'}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid rendering raw exception messages to users.
Line 44 exposes error.message directly in the fallback UI and can leak internal details, so render a generic message instead.

As per coding guidelines, "ONLY flag issues that fall into one of these HIGH-IMPACT categories: 1. Security vulnerabilities (hardcoded secrets, tokens, XSS risks via dangerouslySetInnerHTML, exposed credentials)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/ui/ErrorBoundary.tsx` at line 44, Replace the direct
rendering of the exception text in the ErrorBoundary fallback UI (the expression
this.state.error?.message inside the ErrorBoundary component) with a generic
user-facing message, and ensure the actual error is recorded via console.error
or your telemetry/logger (e.g., call console.error(this.state.error) or send to
reportError) so internal details are not exposed to users; update the
renderFallback logic to show a fixed safe string like "Something went wrong"
while keeping the logging/reporting of this.state.error intact.

Comment on lines +4 to +8
interface TourStep {
target: string;
title: string;
description: string;
position?: 'top' | 'bottom' | 'left' | 'right';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' frontend/src/components/ui/WelcomeTour.tsx
rg -n -C4 '<WelcomeTour\b|WelcomeTour\(' frontend/src

Repository: yb175/CVPilot

Length of output: 4855


🏁 Script executed:

#!/bin/bash
# Check for any imports of WelcomeTour
rg -n 'import.*WelcomeTour|from.*WelcomeTour' frontend/src
# Check if it's exported in barrel files
fd -e ts -e tsx | xargs grep -l 'WelcomeTour' 2>/dev/null | head -20

Repository: yb175/CVPilot

Length of output: 219


Narrow position or implement the missing placements.

Lines 4-8 allow 'left' and 'right', but line 98 sends every non-'top' value through the bottom branch. The public type promises behavior this component does not render.

Suggested fix
 interface TourStep {
   target: string;
   title: string;
   description: string;
-  position?: 'top' | 'bottom' | 'left' | 'right';
+  position?: 'top' | 'bottom';
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface TourStep {
target: string;
title: string;
description: string;
position?: 'top' | 'bottom' | 'left' | 'right';
interface TourStep {
target: string;
title: string;
description: string;
position?: 'top' | 'bottom';
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/ui/WelcomeTour.tsx` around lines 4 - 8, The TourStep
interface declares position?: 'top' | 'bottom' | 'left' | 'right' but the
WelcomeTour component's rendering logic (e.g., the renderStep/getPlacement code
path) currently treats every non-'top' value as 'bottom', so either narrow the
TourStep.position type to only 'top' | 'bottom' to reflect implemented behavior,
or implement the missing 'left' and 'right' placements in the WelcomeTour
rendering (update the function that maps step.position to tooltip/popover
placement and the CSS/positioning logic for 'left' and 'right' branches) so the
public type matches actual rendering; adjust types and unit/UI tests
accordingly.

@@ -1,97 +1,160 @@
import { useState } from "react";
import { useState, useEffect, useRef } from "react";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C4 'export function useApi|function useApi|const useApi|fetchWithAuth' frontend/src

Repository: yb175/CVPilot

Length of output: 9094


🏁 Script executed:

cat -n frontend/src/lib/fetcher.ts

Repository: yb175/CVPilot

Length of output: 1131


🏁 Script executed:

sed -n '15,50p' frontend/src/pages/JobPage.tsx

Repository: yb175/CVPilot

Length of output: 1235


🏁 Script executed:

sed -n '8,50p' frontend/src/components/Landing/CTAButton.tsx

Repository: yb175/CVPilot

Length of output: 1143


The resume check won't rerun if auth state changes mid-session, but the suggested fix would cause infinite API calls.

The resume effect has an empty dependency array while fetchWithAuth is unstable (recreated every render from useApi()). If the token refreshes or auth state changes, hasResume won't update. However, adding fetchWithAuth as a dependency would trigger the effect on every render (new function every render), flooding the API with duplicate requests.

The correct fix is to memoize fetchWithAuth in useApi() with useCallback first:

export const useApi = () => {
  const { getToken } = useAuth();

-  const fetchWithAuth = async (
+  const fetchWithAuth = useCallback(async (
    endpoint: string,
    options: RequestInit = {}
  ) => {
    // ... implementation
-  };
+  }, [getToken]);

  return { fetchWithAuth };
};

Then the effect can safely depend on [fetchWithAuth] without the ref workaround.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/JobPage.tsx` at line 1, The resume-check effect in JobPage
doesn't react to auth changes because fetchWithAuth from useApi is recreated
each render; memoize fetchWithAuth inside useApi using useCallback (so it only
changes when its true dependencies change), then update the resume effect to
depend on [fetchWithAuth] (removing any ref-workaround) so hasResume updates on
auth/token changes without causing repeated API calls; target the fetchWithAuth
export in useApi and the resume effect that sets hasResume in JobPage.

@armaansharma-dev
Copy link
Copy Markdown
Collaborator Author

@coderabbitai

You are acting as a senior backend + system design reviewer.

Your task is to analyze the current repository and convert the MVP goal into a well-structured execution plan.

🎯 Objective
We are building an MVP that integrates:

Backend APIs (existing or partially built)
Frontend consumption of those APIs
Minimal but functional end-to-end flow
Focus ONLY on MVP — avoid overengineering, scalability concerns, or unnecessary abstractions.

📌 What you need to do

  1. Analyze Codebase
    Understand folder structure
    Identify:
    existing services
    API endpoints
    database interactions
    missing layers (controllers, services, routes, etc.)
  2. Break into Sub-Issues (VERY IMPORTANT)
    Convert the entire MVP into well-defined sub-issues under one parent issue

Each sub-issue must include:

Title
Description
Exact files to modify/create
Expected outcome
Dependencies (if any)
Examples:

Setup API route for X
Implement service logic for Y
Connect frontend component to endpoint Z
Add validation layer
Handle error states
⚠️ Keep tasks small and executable (1–3 hour chunks)

  1. Identify Gaps
    Clearly mention:

Missing endpoints
Incomplete logic
Broken flows
Redundant or conflicting code
4. Provide Starter Code
Generate minimal working starter code for:

Backend:
Route
Controller
Service
Example response structure
Frontend:
API call (fetch/axios)
Basic integration (React or relevant framework)
State handling (minimal)
⚠️ Keep it clean and simple — no heavy architecture

  1. Integration Flow
    Explain the full flow:

Frontend → API → Service → DB → Response → UI

Keep it short but clear.

  1. MVP Constraints
    Strictly follow:

No auth complexity unless already present
No microservices
No advanced caching
No unnecessary abstractions
Prefer simple, readable code
7. Final Verdict
At the end, provide:

✅ Whether current structure supports MVP
⚠️ Risks / blockers
🧩 What MUST be completed before deployment
🧠 Context
We are following this approach:

Scraping / data fetching is deterministic
AI is used only for post-processing (not core fetching logic)
Reference:
:contentReference[oaicite:0]{index=0}

🚫 Avoid
Overengineering
Generic advice
Vague suggestions like “improve structure”
Large refactors unless absolutely required
✅ Output Format
Codebase Summary
Sub-Issues List (with details)
Identified Gaps
Starter Code (Backend + Frontend)
Integration Flow
Final Verdict
Be practical, direct, and execution-focused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me first explore the full repository structure before generating the plan.


🏁 Script executed:

#!/bin/bash
# Get top-level folder structure
tree -L 3 --dirsfirst -I 'node_modules|dist|build|.git|__pycache__|*.pyc' 2>/dev/null || find . -maxdepth 3 -type d | grep -v node_modules | grep -v .git | sort

echo "=== Backend entry points ==="
fd -e js -e ts -e py 'index|app|server|main' --exclude node_modules --exclude dist

echo "=== Route files ==="
fd -e js -e ts -e py 'route|router|routes' --exclude node_modules --exclude dist

echo "=== Controller files ==="
fd -e js -e ts -e py 'controller|Controller' --exclude node_modules --exclude dist

echo "=== Service files ==="
fd -e js -e ts -e py 'service|Service' --exclude node_modules --exclude dist

echo "=== DB / Model files ==="
fd -e js -e ts -e py 'model|Model|schema|Schema|db|database' --exclude node_modules --exclude dist

echo "=== Middleware ==="
fd -e js -e ts -e py 'middleware|auth' --exclude node_modules --exclude dist

Length of output: 2832


Good. Now let me read all the key files in one pass.


🏁 Script executed:

#!/bin/bash
echo "=== server.ts ==="
cat backend/server.ts

echo "=== auth.route.ts ==="
cat backend/route/auth.route.ts

echo "=== resumeRoute.ts ==="
cat backend/route/resumeRoute.ts

echo "=== preferences.route.ts ==="
cat backend/route/preferences.route.ts

echo "=== system.route.ts ==="
cat backend/route/system.route.ts

echo "=== resumeController.ts ==="
cat backend/controller/resumeController.ts

echo "=== preferences.controller.ts ==="
cat backend/controller/preferences.controller.ts

echo "=== system.controller.ts ==="
cat backend/controller/system.controller.ts

echo "=== prisma schema ==="
cat backend/prisma/schema.prisma

echo "=== auth.middleware.ts ==="
cat backend/middleware/auth.middleware.ts

echo "=== resumeService.ts ==="
cat backend/service/resumeService.ts

echo "=== preferences.service.ts ==="
cat backend/service/preferences.service.ts

echo "=== user.service.ts ==="
cat backend/service/user.service.ts

echo "=== parsingTrigger.ts ==="
cat backend/service/parsingTrigger.ts

echo "=== Frontend services dir ==="
fd . frontend/src/services --type f | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat {}'

echo "=== Frontend data dir ==="
fd . frontend/src/data --type f | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat {}'

echo "=== Frontend lib dir ==="
fd . frontend/src/lib --type f | sort | xargs -I{} sh -c 'echo "=== {} ===" && cat {}'

Length of output: 39684


@armaansharma-dev Here is the full analysis and MVP execution plan.


1. Codebase Summary

Layer Status Notes
Backend Routes ✅ Complete /resume, /preferences, /auth/me, /health
Backend Controllers ✅ Complete resumeController, preferencesController, systemController
Backend Services ✅ Complete Resume pipeline (Cloudinary → Gemini LLM → fallback → DB), preferences, user sync
Auth (Clerk) ✅ Complete protectedRoute middleware on all protected routes
DB Schema (Prisma) ⚠️ Partial User, UserPreferences, Resumeno Job model
Frontend Services ✅ Complete resume.ts, preferences.ts, useApi() fetcher
Frontend Pages ⚠️ Partial Profile/Resume/Prefs wired; JobsPage uses only MOCK_JOBS
Jobs API ❌ Missing No route, controller, service, or DB model for jobs
Job Matching ❌ Missing Scores/reasons are hardcoded mock strings
Bookmarks Backend ❌ Missing Only localStorage — no API persistence
Resume Parsing Poll ❌ Missing Frontend doesn't poll 202 until parsedData is ready

2. Sub-Issues List

🔴 P0 — Blockers (MVP cannot deploy without these)


SUB-1: Add Job model to Prisma schema

  • Files: backend/prisma/schema.prisma
  • Description: Add a Job model to store scraped/seeded job postings with fields for matching.
  • Expected outcome: jobs table in PostgreSQL; Prisma client regenerated.
  • Dependencies: None (first step)
  • Effort: ~30 min
model Job {
  id          String       `@id` `@default`(uuid())
  title       String
  company     String
  location    String
  locationType LocationType
  seniority   Seniority
  description String
  skills      String[]
  salary      String?
  postedAt    DateTime     `@default`(now())
  sourceUrl   String?

  @@index([seniority, locationType])
}

SUB-2: Seed jobs into DB (replace MOCK_JOBS)

  • Files: backend/scripts/seedJobs.ts (create)
  • Description: One-time seeder that writes the current MOCK_JOBS entries (and future scraped data) into the Job table.
  • Expected outcome: DB populated; scraper can write here going forward.
  • Dependencies: SUB-1
  • Effort: ~1 hr
// backend/scripts/seedJobs.ts
import prisma from "../lib/prisma.js";

const jobs = [/* paste MOCK_JOBS mapped to Job schema */];

async function seed() {
  await prisma.job.createMany({ data: jobs, skipDuplicates: true });
  console.log("Seeded", jobs.length, "jobs");
}
seed().finally(() => prisma.$disconnect());

SUB-3: Create GET /jobs — Job matching endpoint

  • Files to create: backend/route/jobs.route.ts, backend/controller/jobs.controller.ts, backend/service/jobs.service.ts
  • File to modify: backend/server.ts
  • Description: Authenticated endpoint that reads user's parsedData (resume skills) + UserPreferences (seniority, locationType) and returns filtered/scored jobs from DB.
  • Expected outcome: GET /jobs returns [{ job, score, reason }] ranked by skill overlap.
  • Dependencies: SUB-1, SUB-2
  • Effort: ~2–3 hrs

Starter code — backend/service/jobs.service.ts:

import prisma from "../lib/prisma.js";
import { getResumeByUserId } from "./resumeService.js";
import { getPreferences } from "./preferences.service.js";

export async function getMatchedJobs(clerkId: string) {
  const [resume, prefs] = await Promise.all([
    getResumeByUserId(clerkId),
    getPreferences(clerkId),
  ]);

  // Build filter from preferences
  const where: any = {};
  if (prefs?.seniority) where.seniority = prefs.seniority;
  if (prefs?.locationPreferences?.length)
    where.locationType = { in: prefs.locationPreferences };

  const jobs = await prisma.job.findMany({ where, take: 50 });

  // Extract user skills from parsedData
  const userSkills: string[] = (resume?.parsedData as any)?.skills ?? [];

  // Score each job by skill overlap
  return jobs
    .map((job) => {
      const matchedSkills = job.skills.filter((s) =>
        userSkills.some((u) => u.toLowerCase() === s.toLowerCase())
      );
      const score = Math.round((matchedSkills.length / Math.max(job.skills.length, 1)) * 100);
      return {
        ...job,
        score: String(score),
        reason: matchedSkills.length
          ? `Your skills (${matchedSkills.slice(0, 3).join(", ")}) match this role.`
          : "Explore this role to expand your skillset.",
      };
    })
    .sort((a, b) => Number(b.score) - Number(a.score));
}

Starter code — backend/controller/jobs.controller.ts:

import { Request, Response } from "express";
import { getMatchedJobs } from "../service/jobs.service.js";

interface AuthRequest extends Request {
  auth: { userId: string } | null;
}

export const getJobsHandler = async (req: AuthRequest, res: Response) => {
  try {
    const userId = req.auth?.userId;
    if (!userId) return res.status(401).json({ error: "Unauthorized" });
    const jobs = await getMatchedJobs(userId);
    return res.status(200).json({ jobs });
  } catch (err) {
    console.error("Jobs fetch error:", err);
    return res.status(500).json({ error: "Internal server error" });
  }
};

Starter code — backend/route/jobs.route.ts:

import { Router } from "express";
import { protectedRoute } from "../middleware/auth.middleware.js";
import { getJobsHandler } from "../controller/jobs.controller.js";

const router = Router();
router.get("/", protectedRoute, getJobsHandler);
export default router;

In backend/server.ts, add:

import jobsRouter from "./route/jobs.route.js";
// ...
app.use("/jobs", jobsRouter);

SUB-4: Connect JobsPage to real /jobs API

  • Files: frontend/src/services/jobs.ts (create), frontend/src/pages/JobPage.tsx
  • Description: Replace MOCK_JOBS with a real API call; handle loading and empty states.
  • Expected outcome: JobsPage shows real matched jobs from backend.
  • Dependencies: SUB-3
  • Effort: ~1–2 hrs

Starter code — frontend/src/services/jobs.ts:

import { Job } from "../data/MockJobs";

export const fetchMatchedJobs = async (
  fetchWithAuth: (url: string, options?: RequestInit) => Promise<Response>
): Promise<Job[]> => {
  const res = await fetchWithAuth("/jobs");
  if (!res.ok) throw new Error("Failed to fetch jobs");
  const data = await res.json();
  return data.jobs ?? [];
};

In frontend/src/pages/JobPage.tsx, replace the MOCK_JOBS block:

const { fetchWithAuth } = useApi();
const [jobs, setJobs] = useState<Job[]>([]);
const [isLoadingJobs, setIsLoadingJobs] = useState(false);

useEffect(() => {
  if (!hasResume) return;
  setIsLoadingJobs(true);
  fetchMatchedJobs(fetchWithAuth)
    .then(setJobs)
    .catch(() => addToast({ message: "Failed to load jobs", variant: "error" }))
    .finally(() => setIsLoadingJobs(false));
}, [hasResume]);

🟡 P1 — Important (needed for complete UX, not hard blockers)


SUB-5: Fix Prisma datasource — missing url field

  • File: backend/prisma/schema.prisma
  • Description: The datasource db block is missing url = env("DATABASE_URL"). This will fail in any environment without it.
  • Expected outcome: Migrations and client generation work correctly.
  • Effort: 5 min
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

SUB-6: Fix syncClerkUser called with empty email in resumeController

  • File: backend/controller/resumeController.ts
  • Description: await syncClerkUser(userId, "") creates users with placeholder emails. Either fetch Clerk profile data here or remove the sync from the resume upload (it's already done in preferences).
  • Expected outcome: Users are not created with temp-{clerkId}@placeholder.com emails.
  • Effort: ~1 hr
// Option A: remove from resumeController (sync happens at preferences creation)
// Delete: await syncClerkUser(userId, "")

// Option B: fetch Clerk user profile before syncing
import { clerkClient } from "@clerk/express";
const clerkUser = await clerkClient.users.getUser(userId);
await syncClerkUser(userId, clerkUser.emailAddresses[0]?.emailAddress ?? "", clerkUser.firstName ?? "");

SUB-7: Add originalFileName to Resume model + store on upload

  • Files: backend/prisma/schema.prisma, backend/controller/resumeController.ts, backend/service/resumeService.ts
  • Description: Frontend's ProfilePage tries to display originalFileName from resume but this field is not in the DB schema.
  • Expected outcome: Resume records store the original file name; ProfilePage displays it correctly.
  • Effort: ~1 hr
model Resume {
  // ...existing fields...
  originalFileName String?
}

SUB-8: Poll for resume parsing completion (202 → 200)

  • Files: frontend/src/pages/JobPage.tsx, frontend/src/pages/ProfilePage.tsx
  • Description: When GET /resume returns 202 (parsing in progress), the frontend should poll every 3–4s until it gets 200 (parsedData ready), then load jobs.
  • Expected outcome: User sees a "Parsing your resume…" state that resolves automatically.
  • Effort: ~1 hr
// frontend/src/services/resume.ts — add polling helper
export const waitForResumeParsed = async (
  fetchWithAuth: (url: string, options?: RequestInit) => Promise<Response>,
  maxAttempts = 10,
  intervalMs = 3000
): Promise<boolean> => {
  for (let i = 0; i < maxAttempts; i++) {
    const res = await fetchWithAuth("/resume");
    if (res.status === 200) return true;
    if (res.status !== 202) return false;
    await new Promise((r) => setTimeout(r, intervalMs));
  }
  return false;
};

SUB-9: Persist bookmarks to backend

  • Files: backend/route/bookmarks.route.ts (create), backend/controller/bookmarks.controller.ts (create), backend/prisma/schema.prisma
  • Description: BookmarksProvider currently only uses localStorage. Add a Bookmark model and POST /bookmarks/:jobId / DELETE /bookmarks/:jobId / GET /bookmarks endpoints.
  • Expected outcome: Bookmarks survive across devices/sessions.
  • Effort: ~2 hrs
model Bookmark {
  id        String   `@id` `@default`(uuid())
  userId    String
  jobId     String
  createdAt DateTime `@default`(now())

  @@unique([userId, jobId])
  @@index([userId])
}

3. Identified Gaps

# Gap Severity Location
1 No /jobs API — entire jobs page is mocked 🔴 Critical Backend missing entirely
2 No Job model in Prisma schema 🔴 Critical schema.prisma
3 Job matching logic doesn't exist 🔴 Critical No jobs.service.ts
4 datasource db missing url = env("DATABASE_URL") 🔴 Critical schema.prisma
5 syncClerkUser(userId, "") creates placeholder email users 🟡 High resumeController.ts Line 26
6 originalFileName used in frontend but not in DB schema 🟡 High schema.prisma, ProfilePage.tsx
7 Resume 202 (parsing) not polled — user stuck 🟡 High JobPage.tsx, ProfilePage.tsx
8 Bookmarks only in localStorage — lost on new device/session 🟡 Medium BookmarksProvider.tsx
9 Refresh Jobs button in JobPage just fakes a delay with setTimeout — doesn't re-fetch 🟡 Medium JobPage.tsx
10 No scraping/ingestion pipeline connected to DB — jobs must be manually seeded 🟡 Medium Backend

4. Integration Flow

User uploads resume
        │
        ▼
POST /resume ──► Cloudinary (store file)
        │
        ▼
parsingTrigger() [fire-and-forget]
  ├── Extract text (PDF/DOCX)
  ├── Gemini LLM → ParsedResume JSON
  ├── Fallback rule-based parser
  └── DB: Resume.parsedData = { skills, experience, ... }
        │
        ▼  (user sets prefs meanwhile)
POST /preferences ──► DB: UserPreferences { seniority, locationPreferences }
        │
        ▼
Frontend polls GET /resume (202 → 200)
        │
        ▼  (parsedData ready)
GET /jobs [NEW] ──► jobs.service.ts
  ├── Read Resume.parsedData.skills
  ├── Read UserPreferences.seniority + locationPreferences
  ├── Query Job table with filters
  └── Score by skill overlap → sorted list
        │
        ▼
Frontend JobsPage renders real matched jobs

5. Final Verdict

✅ What already supports MVP

  • Full resume upload + async AI parsing pipeline is production-grade
  • Preferences CRUD is complete end-to-end
  • Auth (Clerk) is solid and consistent
  • Frontend service layer (fetchWithAuth) is correctly abstracted
  • UI is complete and polished

⚠️ Risks / Blockers

  1. /jobs API is the single largest blocker — without it, the core value prop (job matching) doesn't exist
  2. datasource db missing url — may cause silent failures in CI/CD migrations
  3. Job scoring is trivial string matching — acceptable for MVP but set expectations accordingly
  4. Resume parsing is async (202) but the frontend doesn't wait → user may see "no jobs" immediately after upload

🧩 Must complete before deployment

Priority Task Sub-Issue
1 Fix datasource db URL SUB-5
2 Add Job model + migration SUB-1
3 Seed jobs into DB SUB-2
4 Create GET /jobs endpoint SUB-3
5 Connect JobsPage to /jobs SUB-4
6 Fix syncClerkUser empty email SUB-6
7 Add resume parsing poll (202→200) SUB-8

SUB-7 (originalFileName) and SUB-9 (bookmarks backend) can be post-MVP.


Would you like me to open these as individual GitHub issues linked to a parent tracking issue, or generate the full implementation diffs for any specific sub-issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant