Skip to main content

Frontend Bug Fixes — February 2026

Audit of 6 reported frontend bugs, their root causes, and applied fixes.


Summary

BugSeverityStatusDescription
BUG 1CRITICALNo action neededMissing /jobseeker/connections/companies route
BUG 2MEDIUMFixedHardcoded CVType in talentRowBuilder.ts
BUG 3MEDIUMFixedHardcoded CVType in candidateRowBuilder.ts
BUG 4MEDIUMFixeduseEffect cleanup missing on early returns in authGuard.tsx
BUG 5LOWNo action neededuseEffect without dependency array in WebSocketContext.tsx
BUG 6LOWFixednpm run lint broken — next lint removed in Next.js 16

BUG 1 — Missing /jobseeker/connections/companies Route

Status: No action needed (not a real bug)

Report: TypeScript emits TS2307: Cannot find module for app/jobseeker/connections/companies/page.js from the Next.js route validator.

Investigation:

  • The /jobseeker/connections/ directory contains only collaboratingpartner/, contacts/, and _mockData.ts
  • Navigation config (lib/config/navigation.ts) does not define a "Companies" submenu item for job seekers
  • Route config (lib/config/routes.ts) has no /jobseeker/connections/companies entry
  • No code anywhere references this path

Conclusion: This route was never intended for job seekers. The company role has /company/connections/companies, but job seekers use "Collaborating Partners" instead. The TS2307 warning from the Next.js type validator is harmless noise — Next.js handles missing routes gracefully at runtime.


BUG 2 — Hardcoded CVType in talentRowBuilder.ts

Status: Fixed

File: lib/utils/candidates/talentRowBuilder.ts

Root cause: Line 233 hardcoded CVType: "MANUAL ENTRY" for every talent row, ignoring the source field that the backend already returns via the GET_COMPANY_TALENTS GraphQL query.

The source field supports these values (defined as TalentSource in lib/hooks/talents/useCompanyTalents.ts):

MANUAL_ENTRY | TALENT_POOL_IMPORT | CV_UPLOAD | APPLICATION_DIRECT |
REFERRAL | RECRUITMENT_AGENCY | LINKEDIN_IMPORT | BULK_IMPORT |
API_INTEGRATION | INTERNAL_TRANSFER

Changes:

  1. Added import for formatEnumLabel from @utils/formatting/formatEnumLabel
  2. Replaced hardcoded value (line 233):
    - CVType: "MANUAL ENTRY", // per requirement
    + CVType: formatEnumLabel(t.source ?? "MANUAL_ENTRY"),
  3. Updated type comment (line 22):
    - CVType: string // show "MANUAL ENTRY" for talents
    + CVType: string // talent source label (e.g. "Manual Entry", "Invited", "Shared")

How it works: formatEnumLabel converts enum-style strings like "CV_UPLOAD" into readable labels like "Cv Upload". When source is null/undefined, it falls back to "MANUAL_ENTRY" which renders as "Manual Entry".

No GraphQL changes needed — the GET_COMPANY_TALENTS query already fetches source, and the GqlTalent interface already includes it.


BUG 3 — Hardcoded CVType in candidateRowBuilder.ts

Status: Fixed

File: lib/utils/candidates/candidateRowBuilder.ts

Root cause: The buildRows() function (line 228) hardcoded CVType: "MANUAL ENTRY" identically to BUG 2. Note: the buildCandidateRows() and buildCandidateRowsWithTalents() functions in the same file were already correct — they used c.source || "N/A".

Changes:

  1. Added import for formatEnumLabel from @utils/formatting/formatEnumLabel
  2. Replaced hardcoded value in buildRows() (line 228):
    - CVType: "MANUAL ENTRY", // per requirement
    + CVType: formatEnumLabel(t.source ?? "MANUAL_ENTRY"),
  3. Updated type comment (line 22):
    - CVType: string // show "MANUAL ENTRY" for talents
    + CVType: string // talent source label (e.g. "Manual Entry", "Invited", "Shared")
  4. Added source to GqlTalent interface in lib/hooks/useCompanyCandidates.ts (line 90):
      export interface GqlTalent {
    id: string
    userId: string
    + source?: string | null
    user?: GqlUser | null
    activeCV?: GqlActiveCV | null
    }

Why the type change was needed: The GqlTalent interface in useCompanyCandidates.ts (used by buildRows) was missing the source field, even though the GET_COMPANY_TALENTS GraphQL query already fetches it. The GqlTalent in useCompanyTalents.ts already had it.


BUG 4 — useEffect Cleanup Missing on Early Returns in authGuard.tsx

Status: Fixed

File: components/auth/authGuard.tsx

Root cause: The useEffect had multiple early return statements (for loading, not logged in, onboarding redirects, etc.) that exited the effect without returning a cleanup function. The cleanup that clears redirectTimer was only returned on the "happy path" (line 130). This meant:

  1. Effect sets a 100ms redirect timer (line 120-122) when a role mismatch is detected
  2. Component unmounts before the timer fires
  3. No cleanup runs because the previous effect execution returned void (early exit on line 123)
  4. The stale timer fires and calls router.replace("/auth/unauthorized") on an unmounted component

Note: Re-renders were safe because the effect already clears pending timers at the top (lines 27-30). Only the unmount case was broken.

Fix: Wrapped all guard logic in a local runGuard() function, so the cleanup is always returned regardless of which code path executes:

useEffect(() => {
setHasAccess(false)
setAccessChecked(false)

if (redirectTimer.current) {
clearTimeout(redirectTimer.current)
redirectTimer.current = null
}

const runGuard = () => {
if (loading) return
if (!isLoggedIn) { router.replace("/auth/signin"); return }
// ... all other guard logic with early returns ...
}

runGuard()

// Cleanup ALWAYS runs on unmount now
return () => {
if (redirectTimer.current) {
clearTimeout(redirectTimer.current)
redirectTimer.current = null
}
}
}, [loading, user, isLoggedIn, isCompany, requiredRole, router, pathname])

BUG 5 — useEffect Without Dependency Array in WebSocketContext.tsx

Status: No action needed (intentional pattern)

File: contexts/WebSocketContext.tsx (lines 169-173)

useEffect(() => {
autoConnectRef.current = autoConnect
connectRef.current = connect
disconnectRef.current = disconnect
}) // no dependency array

Analysis: This is a standard React "ref-sync" pattern. The effect runs on every render to keep refs in sync with the latest values. This allows the auto-connect effect (lines 175-202) to use stable refs in its dependency array while still accessing the latest connect/disconnect callbacks.

Running on every render is intentional — ref assignments (ref.current = value) are trivially cheap and cause no re-renders. Adding a dependency array would risk stale refs.


BUG 6 — npm run lint Broken

Status: Fixed

File: package.json

Root cause: The lint script used next lint, but Next.js 16 removed the lint subcommand. When next lint runs, Next.js interprets lint as a [directory] argument, producing:

Invalid project directory provided, no such directory: .../aiqlick-frontend/lint

The available Next.js 16 commands are: build, dev, start, info, telemetry, typegen, upgrade, experimental-test, internal — no lint.

Fix:

- "lint": "next lint"
+ "lint": "eslint ."

This calls ESLint directly using the project's eslint.config.mjs flat config, which already extends next/core-web-vitals and next/typescript.


Files Changed

FileChange
lib/utils/candidates/talentRowBuilder.tsAdded formatEnumLabel import; replaced hardcoded CVType; updated comment
lib/utils/candidates/candidateRowBuilder.tsAdded formatEnumLabel import; replaced hardcoded CVType in buildRows(); updated comment
lib/hooks/useCompanyCandidates.tsAdded source?: string | null to GqlTalent interface
components/auth/authGuard.tsxRestructured useEffect so cleanup function is always returned
package.jsonChanged lint script from next lint to eslint .