From 8f4b41da532cbc936b69dc048b9dc48dd27bd856 Mon Sep 17 00:00:00 2001 From: Jeston Singh Date: Tue, 15 Sep 2026 22:52:18 +0530 Subject: [PATCH 1/6] chore: migrate to pnpm, strict ESLint/Prettier, lefthook, commitlint (PR 0.1) (#1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: migrate to pnpm, strict ESLint/Prettier, lefthook, commitlint PR 0.1 of docs/MILESTONES.md. Toolchain - pnpm 10 via corepack (packageManager, engines, pnpm-lock.yaml); npm lockfile removed. .npmrc hoists eslint/prettier plugins for eslint-config-next; onlyBuiltDependencies allow-list for postinstall scripts (SECURITY §2.12). - ESLint flat config: next/core-web-vitals + typescript-eslint strict/stylistic type-checked, no-console, no-explicit-any, no-non-null-assertion, import/order, and the ARCHITECTURE §1 layer rules as no-restricted-imports overrides. Legacy allowances are scoped and annotated with the PR that deletes them. - Prettier (+tailwind plugin), commitlint (conventional + "security" type), lefthook hooks calling local binaries; gitleaks/vitest guarded by scripts until installed. .gitattributes normalises LF (R19). - package.json scripts per AGENTS.md §5; .nvmrc; .vscode settings. - tailwind.config.js removed (Tailwind v4 uses @theme in CSS). Legacy code brought to lint-clean (356 → 0) - All console.* removed, including the session-cookie log in getCurrentUser (closes R1 early; P1.1 adds the structured logger). - Fixed B1 (await cookies() in profile update), B4 (duplicate keys), B12 (stateful /g regex), B13 (signIn return), B16 (single AgentProps). - Typed JSON boundaries instead of any; ?? instead of ||; void on fire-and-forget promises; onKeyDown instead of deprecated onKeyPress. - /api/vapi/generate: minimal shape validation and no error leakage (interim until P1.4 adds zod + session auth; R2 still open). - ci.yml: replaced invalid YAML flow mappings containing ${{ }}. Verified: pnpm lint, pnpm typecheck, next build all clean; hooks reject non-conventional messages. Co-Authored-By: Claude Opus 5 * docs: update risk register, changelog, and contributing for PR 0.1 * ci: adopt milestone → dev → main promotion flow with pipeline gates - CI runs on every PR and on pushes to main, dev, and milestone/**. - New "Promotion guard" workflow: PRs into main must come from dev; PRs into dev must come from milestone/* or hotfix/*. Intended as a required status check alongside CI on both protected branches. - PROCESS.md documents the branching model; CONTRIBUTING.md, AGENTS.md, and docs/MILESTONES.md updated so work branches are cut from the current milestone branch, never from main. Co-Authored-By: Claude Opus 5 * chore: point issue template link at renamed repository * chore: add branch protection script for main and dev * ci: let pnpm/action-setup read the version from packageManager --------- Co-authored-by: Claude Opus 5 --- .gitattributes | 16 + .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/workflows/ci.yml | 32 +- .github/workflows/promote-guard.yml | 28 + .npmrc | 7 + .nvmrc | 1 + .prettierignore | 11 + .vscode/extensions.json | 9 + .vscode/settings.json | 17 + AGENTS.md | 2 +- CHANGELOG.md | 11 + CONTRIBUTING.md | 8 +- PROCESS.md | 25 +- app/(auth)/layout.tsx | 23 +- app/(auth)/sign-in/page.tsx | 2 +- app/(root)/interview/[id]/feedback/page.tsx | 98 +- app/(root)/interview/[id]/page.tsx | 30 +- app/(root)/interview/page.tsx | 13 +- app/(root)/layout.tsx | 110 +- app/(root)/page.tsx | 81 +- app/(root)/profile/page.tsx | 16 +- app/api/auth/signout/route.ts | 27 +- app/api/daily-login/check/route.ts | 29 +- app/api/daily-login/streak/route.ts | 29 +- app/api/news/route.ts | 44 +- app/api/profile/update/route.ts | 31 +- app/api/vapi/generate/route.ts | 86 +- app/globals.css | 86 +- app/layout.tsx | 14 +- commitlint.config.mjs | 29 + components.json | 2 +- components/AIChatbot.tsx | 111 +- components/Agent.tsx | 257 +- components/AllInterviewsSection.tsx | 31 +- components/AnalyticsChart.tsx | 76 +- components/AuthForm.tsx | 183 +- components/DailyLoginStar.tsx | 87 +- components/DisplayTechIcons.tsx | 115 +- components/DynamicCareerQuote.tsx | 8 +- components/FormField.tsx | 14 +- components/HighPayingJobsSection.tsx | 56 +- components/HomeAnalyticsSection.tsx | 12 +- components/InterviewCard.tsx | 60 +- components/LiveDateTime.tsx | 14 +- components/NewsSection.tsx | 59 +- components/PaginationControls.tsx | 27 +- components/ProfileClient.tsx | 221 +- components/SettingsMenu.tsx | 77 +- components/ui/button.tsx | 37 +- components/ui/form.tsx | 106 +- components/ui/input.tsx | 16 +- components/ui/label.tsx | 19 +- components/ui/sonner.tsx | 14 +- constants/index.ts | 2 +- docs/MILESTONES.md | 2 +- docs/RISKS.md | 8 +- eslint.config.mjs | 215 +- firebase/admin.ts | 10 +- firebase/client.ts | 8 +- lefthook.yml | 21 +- lib/actions/auth.action.ts | 133 +- lib/actions/general.action.ts | 19 +- lib/utils.ts | 5 +- lib/vapi.sdk.ts | 7 +- next.config.ts | 6 +- package-lock.json | 9454 ------------------- package.json | 34 +- pnpm-lock.yaml | 7899 ++++++++++++++++ prettier.config.mjs | 14 + scripts/github/protect-branches.sh | 36 + scripts/hooks/gitleaks.sh | 7 + scripts/hooks/vitest.sh | 6 + tailwind.config.js | 16 - types/index.d.ts | 13 +- types/vapi.d.ts | 5 +- 75 files changed, 9789 insertions(+), 10650 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/promote-guard.yml create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 commitlint.config.mjs delete mode 100644 package-lock.json create mode 100644 pnpm-lock.yaml create mode 100644 prettier.config.mjs create mode 100644 scripts/github/protect-branches.sh create mode 100644 scripts/hooks/gitleaks.sh create mode 100644 scripts/hooks/vitest.sh delete mode 100644 tailwind.config.js diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6e80775 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,16 @@ +# Normalise line endings (RISKS R19). Text files are LF in the repo and in working trees. +* text=auto eol=lf + +# Lockfiles and generated files +pnpm-lock.yaml -diff + +# Binary assets +*.png binary +*.jpg binary +*.jpeg binary +*.ico binary +*.woff binary +*.woff2 binary +*.webp binary +*.mp3 binary +*.wav binary diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ce4ea79..9b6bbbc 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: Security vulnerability - url: https://github.com/Jeston10/JustPrep_Ai_interview/blob/main/SECURITY.md + url: https://github.com/Jeston10/JustPrep/blob/main/SECURITY.md about: Please report security issues privately as described in SECURITY.md. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ebb3dc..856409c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,15 @@ # Until then this workflow is expected to fail on the legacy codebase; do not weaken it to make it pass. name: CI +# Branching model (PROCESS.md §Branching): PR branch → milestone/* → dev → main. +# The pipeline runs on every PR (any base) and on pushes to the three integration tiers. on: pull_request: push: - branches: [main] + branches: + - main + - dev + - "milestone/**" concurrency: group: ci-${{ github.ref }} @@ -16,7 +21,7 @@ permissions: env: NODE_VERSION: 22 - PNPM_VERSION: 10 + # pnpm version comes from package.json#packageManager (pnpm/action-setup reads it). jobs: static: @@ -24,11 +29,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: { fetch-depth: 0 } + with: + fetch-depth: 0 - uses: pnpm/action-setup@v4 - with: { version: ${{ env.PNPM_VERSION }} } - uses: actions/setup-node@v4 - with: { node-version: ${{ env.NODE_VERSION }}, cache: pnpm } + with: + node-version: ${{ env.NODE_VERSION }} + cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm lint - run: pnpm typecheck @@ -45,9 +52,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - with: { version: ${{ env.PNPM_VERSION }} } - uses: actions/setup-node@v4 - with: { node-version: ${{ env.NODE_VERSION }}, cache: pnpm } + with: + node-version: ${{ env.NODE_VERSION }} + cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm test -- --coverage - name: Integration (Firestore emulator) @@ -62,9 +70,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - with: { version: ${{ env.PNPM_VERSION }} } - uses: actions/setup-node@v4 - with: { node-version: ${{ env.NODE_VERSION }}, cache: pnpm } + with: + node-version: ${{ env.NODE_VERSION }} + cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm audit --audit-level=high - run: pnpm build @@ -80,9 +89,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 - with: { version: ${{ env.PNPM_VERSION }} } - uses: actions/setup-node@v4 - with: { node-version: ${{ env.NODE_VERSION }}, cache: pnpm } + with: + node-version: ${{ env.NODE_VERSION }} + cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm dlx playwright install --with-deps chromium webkit - run: pnpm dlx firebase-tools emulators:exec --only auth,firestore "pnpm test:e2e" diff --git a/.github/workflows/promote-guard.yml b/.github/workflows/promote-guard.yml new file mode 100644 index 0000000..ecabc24 --- /dev/null +++ b/.github/workflows/promote-guard.yml @@ -0,0 +1,28 @@ +# Enforces the promotion path: only `dev` may open a PR into `main`, and only +# `milestone/*` or hotfix branches may open a PR into `dev`. Pair with branch +# protection (required checks: CI + this job) so the rule cannot be bypassed. +name: Promotion guard + +on: + pull_request: + branches: [main, dev] + +permissions: + contents: read + +jobs: + guard: + name: Check source branch + runs-on: ubuntu-latest + steps: + - name: main accepts only dev + if: github.base_ref == 'main' && github.head_ref != 'dev' + run: | + echo "::error::PRs into main must come from dev (got '${{ github.head_ref }}')." + exit 1 + - name: dev accepts milestone/* and hotfix/* + if: github.base_ref == 'dev' && !startsWith(github.head_ref, 'milestone/') && !startsWith(github.head_ref, 'hotfix/') + run: | + echo "::error::PRs into dev must come from milestone/* or hotfix/* (got '${{ github.head_ref }}')." + exit 1 + - run: echo "Promotion path OK" diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..bb52773 --- /dev/null +++ b/.npmrc @@ -0,0 +1,7 @@ +# pnpm settings +# eslint-config-next references its plugins by name; pnpm 10 stopped hoisting them by default. +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=*prettier* +# Fail on peer-dependency conflicts instead of silently resolving them. +strict-peer-dependencies=false +auto-install-peers=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e2b3c2e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +# Prettier ignore — generated, vendored, and binary paths +node_modules +.next +out +build +coverage +playwright-report +test-results +pnpm-lock.yaml +public +*.md diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..974ee23 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "vitest.explorer", + "ms-playwright.playwright" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cd174ae --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, + "eslint.useFlatConfig": true, + "eslint.validate": ["javascript", "typescript", "typescriptreact"], + "files.eol": "\n", + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.preferences.importModuleSpecifier": "non-relative", + "tailwindCSS.experimental.classRegex": [ + ["cn\(([^)]*)\)", "[\"'`]([^\"'`]*)[\"'`]"], + ["cva\(([^)]*)\)", "[\"'`]([^\"'`]*)[\"'`]"] + ], + "search.exclude": { "**/node_modules": true, "**/.next": true, "pnpm-lock.yaml": true } +} diff --git a/AGENTS.md b/AGENTS.md index a1aa737..26b1a05 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,7 +36,7 @@ docs/ Handbook: architecture, standards, security, design, decisions ( ## 4. How to work - **Before starting a task**: read the relevant `docs/` page and the ADRs it references. Check `docs/MILESTONES.md` for where the task sits and what it depends on, and **read the `docs/RISKS.md` rows tagged for that phase** (MILESTONES §13). A PR that closes a risk row says so (`Closes R7`) and updates the row's status. -- **Branching**: `main` is protected. Branch as `type/short-description` (`feat/text-interview-mode`, `fix/profile-cookie-await`). One concern per branch. +- **Branching**: `main` and `dev` are protected. Cut work branches from the current `milestone/p-` branch as `type/short-description` (`feat/text-interview-mode`, `fix/profile-cookie-await`) and PR back into it. Promotion is `milestone/* → dev → main`, each by PR with green CI (`PROCESS.md`). One concern per branch. - **Commits**: Conventional Commits (`feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`, `perf:`, `security:`). Imperative mood, ≤ 72 chars subject, body explains *why*. - **Pull requests**: use the template. Include screenshots for UI at 375 px and 1440 px minimum. Link the plan item or issue. Self-review the diff before requesting review. - **Definition of done** (all required): diff --git a/CHANGELOG.md b/CHANGELOG.md index 757072f..eb2e890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,19 @@ All notable changes to this project are documented here. Format follows [Keep a - Engineering handbook: `AGENTS.md`, `CONTRIBUTING.md`, `PROCESS.md`, `SECURITY.md`, and `docs/` (guardrails, architecture, tech stack, security, design system, component library, responsive rules, coding standards, quality, implementation plan, milestones execution plan with legacy → target migration map, risk register/watch-list, ADRs 0001–0010). - GitHub templates (issues, PR), `CODEOWNERS`, CI workflow, `lefthook.yml`, `.env.example`. +- Toolchain (PR 0.1): pnpm 10, strict type-aware ESLint with layer rules, Prettier, lefthook hooks, commitlint, `.gitattributes`, `.nvmrc`, VS Code settings. + ### Changed - README rewritten to describe the product, stack, and how to work in the repo. +- Legacy code brought to lint-clean under the strict config (356 violations → 0). + +### Fixed +- Profile update always returned 500 (`cookies()` not awaited). +- Duplicate object keys in `DisplayTechIcons`; stateful `/g` regex in `InterviewCard`; `signIn` returned nothing on success; conflicting `AgentProps` definitions. + +### Security +- Removed the server log that printed the raw session cookie and decoded claims (RISKS R1). +- `/api/vapi/generate` no longer echoes internal errors to the client and rejects malformed bodies (full auth + zod lands in P1.4). ## [0.1.0] — 2025-09 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ed7084..ac75866 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,9 +13,13 @@ pnpm dev `lefthook install` runs automatically on `pnpm install` and enables the git hooks. +Optional but recommended: install the [gitleaks](https://github.com/gitleaks/gitleaks) binary (`winget install gitleaks` / `scoop install gitleaks` / `brew install gitleaks`) so secrets are caught at commit time; CI runs it on every push either way. + +If a globally installed older `pnpm` shadows the pinned version, run commands through `corepack pnpm …` or upgrade the global install. + ## Branches and commits -- Branch from `main`: `feat/…`, `fix/…`, `refactor/…`, `docs/…`, `chore/…`, `security/…`. +- Branch from the **current milestone branch** (`milestone/p-`, see `docs/MILESTONES.md`), never from `main`: `feat/…`, `fix/…`, `refactor/…`, `docs/…`, `chore/…`, `security/…`. PRs target the milestone branch. Promotion is milestone → `dev` → `main` (see `PROCESS.md`). - Conventional Commits. Examples: - `feat(attempt): stream interviewer turns in text mode` - `fix(auth): await cookies() in profile update` @@ -30,7 +34,7 @@ pnpm dev 4. Server changes: tick the checklist in `docs/SECURITY.md` §3. 5. Architecture, vendor, data-model, or token changes: include an ADR in `docs/decisions/`. 6. Update docs in the same PR. Add a `CHANGELOG.md` entry under *Unreleased*. -7. Squash-merge once approved. +7. Squash-merge into the milestone branch once approved. Milestone → `dev` and `dev` → `main` promotions are merge commits so history per phase stays traceable. ## Code review standards diff --git a/PROCESS.md b/PROCESS.md index fbe1bda..3a7bf13 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -2,11 +2,34 @@ How work flows through this repository, modelled on GitLab's engineering process at small scale. +## Branching and promotion + +``` +chore/… feat/… fix/… security/… one PR each + │ PR (CI green, template complete) + ▼ +milestone/p- one per phase in docs/MILESTONES.md; created from dev + │ PR when the phase exit criteria are met (CI green on the milestone branch) + ▼ +dev integration; deployed to the preview environment; soak-tested + │ PR only from dev (promotion guard) with CI green; squash or merge commit, tagged + ▼ +main production; protected; never receives direct pushes +``` + +Rules: +- `main` and `dev` are protected: PRs only, required checks (`CI` jobs + `Promotion guard`), no force-push, linear history on `main`. +- Milestone branches are `milestone/p0-toolchain`, `milestone/p1-security`, … (names in `docs/MILESTONES.md`). They are deleted after their PR into `dev` merges; the next milestone branch is cut from the updated `dev`. +- Work branches are cut from the current milestone branch and merged back into it by PR. They never target `dev` or `main`. +- `hotfix/*` may target `dev` directly (and is then promoted to `main` through `dev`); it is the only exception. +- Every push to `dev`, `milestone/**`, and `main` runs the full pipeline; PRs run it against the merge result. +- A phase is "achieved" when its milestone branch has merged into `dev`, CI is green on `dev`, and the manual soak (release checklist in `docs/QUALITY.md` §5) passes; promotion to `main` then tags the release. + ## Cadence - **Milestones** = phases in `docs/IMPLEMENTATION_PLAN.md`. Each has a GitHub milestone and a tracking issue with the exit criteria as a checklist. - **Iterations** are weekly. Monday: pick issues for the week from the active phase; Friday: release if the exit criteria for the phase are met, otherwise tag a pre-release. -- **Releases**: SemVer tags (`v0.x.y` until Phase 5 completes, then `v1.0.0`). `CHANGELOG.md` is updated per release from Conventional Commits. +- **Releases**: SemVer tags (`v0.x.y` until Phase 5 completes, then `v1.0.0`) applied on `main` when `dev` is promoted. `CHANGELOG.md` is updated per release from Conventional Commits. ## Issue lifecycle diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx index f0d350d..0066e9a 100644 --- a/app/(auth)/layout.tsx +++ b/app/(auth)/layout.tsx @@ -1,16 +1,13 @@ -import {ReactNode} from 'react' -import { isAuthenticated } from '@/lib/actions/auth.action' -import { redirect } from 'next/navigation'; +import { redirect } from "next/navigation"; +import { type ReactNode } from "react"; -const Authlayout = async ({children}:{children:ReactNode}) => { +import { isAuthenticated } from "@/lib/actions/auth.action"; + +const Authlayout = async ({ children }: { children: ReactNode }) => { const isUserAuthenticated = await isAuthenticated(); - - if(isUserAuthenticated) redirect ('/'); - return ( -
{children} -
- ) -} -export default Authlayout + if (isUserAuthenticated) redirect("/"); + return
{children}
; +}; + +export default Authlayout; diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx index a3a13ef..98cca39 100644 --- a/app/(auth)/sign-in/page.tsx +++ b/app/(auth)/sign-in/page.tsx @@ -4,4 +4,4 @@ const Page = () => { return ; }; -export default Page; \ No newline at end of file +export default Page; diff --git a/app/(root)/interview/[id]/feedback/page.tsx b/app/(root)/interview/[id]/feedback/page.tsx index 6bd78ff..0a6359e 100644 --- a/app/(root)/interview/[id]/feedback/page.tsx +++ b/app/(root)/interview/[id]/feedback/page.tsx @@ -1,46 +1,42 @@ import dayjs from "dayjs"; -import Link from "next/link"; import Image from "next/image"; +import Link from "next/link"; import { redirect } from "next/navigation"; -import { - getFeedbackByInterviewId, - getInterviewById, -} from "@/lib/actions/general.action"; import { Button } from "@/components/ui/button"; + import { getCurrentUser } from "@/lib/actions/auth.action"; +import { getFeedbackByInterviewId, getInterviewById } from "@/lib/actions/general.action"; const Feedback = async ({ params }: RouteParams) => { const { id } = await params; const user = await getCurrentUser(); + if (!user) redirect("/sign-in"); const interview = await getInterviewById(id); if (!interview) redirect("/"); const feedback = await getFeedbackByInterviewId({ interviewId: id, - userId: user?.id!, + userId: user.id, }); return (

- Feedback on the Interview -{" "} - {interview.role} Interview + Feedback on the Interview - {interview.role} Interview

-
+
{/* Overall Impression */} -
+
star

Overall Impression:{" "} - - {feedback?.totalScore} - + {feedback?.totalScore} /100

@@ -49,9 +45,7 @@ const Feedback = async ({ params }: RouteParams) => {
calendar

- {feedback?.createdAt - ? dayjs(feedback.createdAt).format("MMM D, YYYY h:mm A") - : "N/A"} + {feedback?.createdAt ? dayjs(feedback.createdAt).format("MMM D, YYYY h:mm A") : "N/A"}

@@ -64,7 +58,7 @@ const Feedback = async ({ params }: RouteParams) => { {/* Interview Breakdown */}

Breakdown of the Interview:

- {feedback?.categoryScores?.map((category, index) => ( + {feedback?.categoryScores.map((category, index) => (

{index + 1}. {category.name} ({category.score}/100) @@ -77,7 +71,7 @@ const Feedback = async ({ params }: RouteParams) => {

Strengths

    - {feedback?.strengths?.map((strength, index) => ( + {feedback?.strengths.map((strength, index) => (
  • {strength}
  • ))}
@@ -86,30 +80,65 @@ const Feedback = async ({ params }: RouteParams) => {

Areas for Improvement

    - {feedback?.areasForImprovement?.map((area, index) => ( + {feedback?.areasForImprovement.map((area, index) => (
  • {area}
  • ))}
{/* Links to Practice Section */} -
-

Links to Practice

-
    + @@ -117,20 +146,13 @@ const Feedback = async ({ params }: RouteParams) => {
    @@ -138,4 +160,4 @@ const Feedback = async ({ params }: RouteParams) => { ); }; -export default Feedback; \ No newline at end of file +export default Feedback; diff --git a/app/(root)/interview/[id]/page.tsx b/app/(root)/interview/[id]/page.tsx index b487f6c..5cf5fe6 100644 --- a/app/(root)/interview/[id]/page.tsx +++ b/app/(root)/interview/[id]/page.tsx @@ -2,39 +2,37 @@ import Image from "next/image"; import { redirect } from "next/navigation"; import Agent from "@/components/Agent"; -import { getRandomInterviewCover } from "@/lib/utils"; +import DisplayTechIcons from "@/components/DisplayTechIcons"; -import { - getFeedbackByInterviewId, - getInterviewById, -} from "@/lib/actions/general.action"; import { getCurrentUser } from "@/lib/actions/auth.action"; -import DisplayTechIcons from "@/components/DisplayTechIcons"; +import { getFeedbackByInterviewId, getInterviewById } from "@/lib/actions/general.action"; +import { getRandomInterviewCover } from "@/lib/utils"; const InterviewDetails = async ({ params }: RouteParams) => { const { id } = await params; const user = await getCurrentUser(); + if (!user) redirect("/sign-in"); const interview = await getInterviewById(id); if (!interview) redirect("/"); const feedback = await getFeedbackByInterviewId({ interviewId: id, - userId: user?.id!, + userId: user.id, }); return ( <> -
    -
    -
    +
    +
    +
    cover-image

    {interview.role} Interview

    @@ -42,14 +40,12 @@ const InterviewDetails = async ({ params }: RouteParams) => {
    -

    - {interview.type} -

    +

    {interview.type}

    { ); }; -export default InterviewDetails; \ No newline at end of file +export default InterviewDetails; diff --git a/app/(root)/interview/page.tsx b/app/(root)/interview/page.tsx index b5d4ffb..1abf260 100644 --- a/app/(root)/interview/page.tsx +++ b/app/(root)/interview/page.tsx @@ -1,21 +1,20 @@ +import { redirect } from "next/navigation"; + import Agent from "@/components/Agent"; + import { getCurrentUser } from "@/lib/actions/auth.action"; const Page = async () => { const user = await getCurrentUser(); + if (!user) redirect("/sign-in"); return ( <>

    Interview generation

    - + ); }; -export default Page; \ No newline at end of file +export default Page; diff --git a/app/(root)/layout.tsx b/app/(root)/layout.tsx index 64570b8..53c7567 100644 --- a/app/(root)/layout.tsx +++ b/app/(root)/layout.tsx @@ -1,29 +1,36 @@ -import { ReactNode } from 'react' -import Image from 'next/image' -import Link from 'next/link' -import { isAuthenticated, getCurrentUser, hasLoggedInToday, getUserLoginStreak } from '@/lib/actions/auth.action' -import { redirect } from 'next/navigation'; -import { headers } from 'next/headers'; -import SettingsMenu from '@/components/SettingsMenu'; -import DailyLoginStar from '@/components/DailyLoginStar'; -import LiveDateTime from '@/components/LiveDateTime'; -import { FiHome, FiUser } from 'react-icons/fi'; -import AIChatbot from '@/components/AIChatbot'; -import DynamicCareerQuote from '@/components/DynamicCareerQuote'; -import { FaLinkedin, FaInstagram, FaGithub } from 'react-icons/fa'; +import { headers } from "next/headers"; +import Image from "next/image"; +import Link from "next/link"; +import { redirect } from "next/navigation"; +import { type ReactNode } from "react"; +import { FaLinkedin, FaInstagram, FaGithub } from "react-icons/fa"; +import { FiHome, FiUser } from "react-icons/fi"; -const PUBLIC_ROUTES = ['/sign-in', '/sign-up']; +import AIChatbot from "@/components/AIChatbot"; +import DailyLoginStar from "@/components/DailyLoginStar"; +import DynamicCareerQuote from "@/components/DynamicCareerQuote"; +import LiveDateTime from "@/components/LiveDateTime"; +import SettingsMenu from "@/components/SettingsMenu"; -const Rootlayout = async ({children}: {children:ReactNode}) => { - const headersList =await headers(); - const currentPath = headersList.get("x-next-url") || "/"; +import { + isAuthenticated, + getCurrentUser, + hasLoggedInToday, + getUserLoginStreak, +} from "@/lib/actions/auth.action"; + +const PUBLIC_ROUTES = ["/sign-in", "/sign-up"]; + +const Rootlayout = async ({ children }: { children: ReactNode }) => { + const headersList = await headers(); + const currentPath = headersList.get("x-next-url") ?? "/"; // Skip auth check for public pages if (!PUBLIC_ROUTES.includes(currentPath)) { const isUserAuthenticated = await isAuthenticated(); - if(!isUserAuthenticated) { - redirect('/sign-in'); + if (!isUserAuthenticated) { + redirect("/sign-in"); } } @@ -31,46 +38,46 @@ const Rootlayout = async ({children}: {children:ReactNode}) => { let user = null; let initialHasLoggedInToday = false; let initialStreak = 0; - + if (!PUBLIC_ROUTES.includes(currentPath)) { try { user = await getCurrentUser(); if (user?.id) { [initialHasLoggedInToday, initialStreak] = await Promise.all([ hasLoggedInToday(user.id), - getUserLoginStreak(user.id) + getUserLoginStreak(user.id), ]); } - } catch (error) { - console.error("Error getting user data for daily login star:", error); + } catch { + // Streak widget is non-essential; render without it. } } // Restore to simple layout return (
    -