Skip to content

Refactor admin#14

Open
adil-khursheed wants to merge 4 commits intomainfrom
refactor-admin
Open

Refactor admin#14
adil-khursheed wants to merge 4 commits intomainfrom
refactor-admin

Conversation

@adil-khursheed
Copy link

@adil-khursheed adil-khursheed commented Mar 10, 2026

Summary by CodeRabbit

  • New Features

    • Add: institute creation UI, dashboard, batches page, students list, and institute selector in header.
    • Add: many reusable UI components (avatar, badge, combobox, input-group, tabs, tooltip, scroll area, spinner, skeleton, switch, separator, field, button-group).
  • Improvements

    • Improved authentication/login flows and cookie handling.
    • Better form validation, data fetching/caching, and query hydration.
    • Major styling/theme overhaul and refined visual polish.
  • Removals

    • Removed legacy institute page and middleware; replaced routing logic with new proxy/layouts.

…thentication, and data management for teachers, institutes, and students.
…pages, institute creation, and student enrollment features.
@beetle-ai
Copy link

beetle-ai bot commented Mar 10, 2026

Summary by Beetle

This PR represents a comprehensive refactoring and modernization of the admin web application, introducing significant architectural improvements, UI/UX enhancements, and new features for institute management. The changes span across three major commits that progressively build upon each other to create a robust admin dashboard system.

📁 File Changes Summary (Consolidated across all commits):

File Status Changes Description
.prettierignore
.prettierrc
eslint.config.mjs
Added +46/-0 Code Quality Setup: Introduced Prettier configuration with Tailwind CSS plugin and custom import ordering, plus ESLint configuration for Next.js with TypeScript support
package.json
package-lock.json
Modified +1088/-511 Dependency Upgrades: Major updates including React 19, Next.js 16, Tailwind CSS v4, Radix UI components, TanStack Query, React Hook Form, Zod validation, and Base UI components
postcss.config.mjs
tailwind.config.ts
Modified/Removed +1/-155 Styling Migration: Migrated from Tailwind CSS v3 to v4 with new PostCSS configuration using @tailwindcss/postcss
src/app/globals.css Modified +224/-106 Design System Overhaul: Implemented comprehensive CSS custom properties for colors, shadows, spacing, and typography with dark mode support
src/actions/cookie_actions.ts
src/actions/user_actions.ts
src/actions/institute_actions.ts
src/actions/batch_actions.ts
src/actions/student_action.ts
src/actions/mentor_actions.ts
Modified +127/-71 Server Actions Refactoring: Updated cookie handling to use default admin token name, improved error handling, added caching with React cache API, and enhanced type safety
src/app/api/auth/login/route.ts
src/app/api/auth/logout/route.ts
src/app/api/auth/verify/route.ts
src/app/api/google/auth/route.ts
Modified +57/-36 Authentication Enhancement: Standardized cookie naming to leadlly.in_admin_token, improved error handling with AxiosError types, and added proper logout flow
src/app/(auth)/login/page.tsx
src/app/(auth)/_components/GoogleLoginButton.tsx
Modified +48/-35 Login UI Modernization: Redesigned login page with Card components, Avatar for branding, and improved Google OAuth flow with better error handling
src/app/(protected)/layout.tsx
src/app/(protected)/(root)/layout.tsx
src/app/provider.tsx
src/app/QueryProvider.tsx
Added +119/-0 Protected Route Architecture: Implemented layered layout system with authentication checks, React Query provider setup, and Redux store integration
src/app/(protected)/create-institute/page.tsx
src/app/(protected)/create-institute/_components/create-institute-form.tsx
Modified/Added +577/-324 Institute Creation Feature: Complete form implementation with React Hook Form, Zod validation, multi-step fields for institute details, subjects, standards, and address information
src/app/(protected)/(root)/(dashboard)/page.tsx
src/app/(protected)/(root)/institute/[instituteId]/page.tsx
Modified +75/-121 Dashboard Refactoring: Simplified dashboard with institute overview, student/teacher management, and dynamic routing based on active institute
src/app/(protected)/(root)/_components/main-header.tsx
src/app/(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx
src/app/(protected)/(root)/institute/[instituteId]/_components/add-students-dialog.tsx
Added +278/-0 Institute Management UI: Header with institute switcher dropdown, detailed institute overview with contact information, and student enrollment dialog
src/components/ui/button.tsx
src/components/ui/input.tsx
src/components/ui/checkbox.tsx
src/components/ui/dropdown-menu.tsx
Modified +58/-43 Component Library Updates: Enhanced button variants with new sizes and states, improved input styling, better checkbox alignment, and dropdown menu scrolling
src/components/ui/combobox.tsx
src/components/ui/field.tsx
src/components/ui/input-group.tsx
src/components/ui/avatar.tsx
src/components/ui/badge.tsx
src/components/ui/tabs.tsx
src/components/ui/tooltip.tsx
src/components/ui/separator.tsx
src/components/ui/scroll-area.tsx
src/components/ui/switch.tsx
src/components/ui/skeleton.tsx
src/components/ui/spinner.tsx
src/components/ui/button-group.tsx
Added +1,000+/-0 New UI Components: Comprehensive set of reusable components built on Base UI and Radix UI primitives for forms, navigation, and data display
src/helpers/schema/createInstituteSchema.ts
src/helpers/types/index.ts
Added/Modified +77/-2 Type Safety & Validation: Zod schema for institute creation form validation and enhanced TypeScript interfaces for Admin and Institute entities
src/redux/slices/userSlice/index.ts
src/redux/slices/instituteSlice/index.ts
src/app/StoreProvider.tsx
Modified +19/-12 State Management Improvements: Added proper TypeScript typing with PayloadAction, updated user/institute state to use new IAdmin/IInstitute interfaces
src/middleware.ts
src/proxy.ts
Removed/Added +48/-28 Routing Logic Enhancement: Replaced middleware with proxy for better authentication flow, automatic redirection based on institute membership, and improved path matching

Total Changes: 73 files changed, +3,500 additions, -1,800 deletions

🗺️ Walkthrough:

sequenceDiagram
participant User
participant Login
participant Auth
participant Protected
participant Institute
participant Dashboard
User->>Login: Access Application
Login->>Auth: Google OAuth / Email Login
Auth->>Auth: Set leadlly.in_admin_token
alt Has Institutes
Auth->>Protected: Redirect to /institute/:id
Protected->>Institute: Load Active Institute
Institute->>Dashboard: Display Overview
else No Institutes
Auth->>Protected: Redirect to /create-institute
Protected->>Institute: Show Creation Form
Institute->>Institute: Validate with Zod Schema
Institute->>Dashboard: Create & Redirect
end
Dashboard->>Dashboard: Fetch Institute Data (TanStack Query)
Dashboard->>Dashboard: Display Students/Teachers
Note over Dashboard: User can switch institutes via header dropdown
Dashboard->>Institute: Switch Institute
Institute->>Dashboard: Update Active Institute
Loading

🎯 Key Changes:

1. Major Framework & Dependency Upgrades

  • React 19 & Next.js 16: Upgraded to latest versions with improved performance and new features
  • Tailwind CSS v4: Complete migration to new architecture with CSS-first configuration
  • TanStack Query: Introduced for server state management with prefetching and caching
  • React Hook Form + Zod: Implemented type-safe form validation and management

2. Authentication & Authorization Enhancements

  • Standardized authentication token naming (leadlly.in_admin_token)
  • Improved OAuth flow with better error handling and user feedback
  • Enhanced routing logic with automatic redirection based on institute membership
  • Added server-side authentication checks in protected layouts

3. Institute Management System

  • Multi-Institute Support: Users can manage multiple institutes with easy switching
  • Comprehensive Creation Form: Detailed institute setup with subjects, standards, and contact information
  • Dynamic Routing: Institute-specific pages with proper data fetching and caching
  • Student Enrollment: Dialog-based interface for adding students via email

4. UI/UX Improvements

  • Design System: Implemented comprehensive CSS custom properties for consistent theming
  • Component Library: Added 13+ new reusable UI components (Combobox, Tabs, Tooltip, etc.)
  • Responsive Design: Enhanced mobile-first approach with better breakpoint handling
  • Dark Mode Support: Full dark mode implementation with theme variables

5. Code Quality & Developer Experience

  • TypeScript Strictness: Enhanced type safety across all modules
  • Code Formatting: Prettier configuration with automatic import sorting
  • Error Boundaries: Added error handling for better user experience
  • Suspense Boundaries: Implemented loading states for async components

6. Performance Optimizations

  • React Cache API: Utilized for server-side data caching
  • Query Prefetching: Implemented with TanStack Query for faster page loads
  • Code Splitting: Improved with dynamic imports and lazy loading
  • Optimized Re-renders: Better state management with Redux and React Query
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only.Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

Follow us: Beetle · X · LinkedIn

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Introduces React Query integration and QueryProvider, replaces middleware with a proxy-based auth flow, renames the auth cookie, removes Tailwind config in favor of token-driven CSS, expands the UI component library heavily, adds/create institute flows and pages, tightens types, and adjusts build/lint/format configs.

Changes

Cohort / File(s) Summary
Formatting & Tooling
/.prettierignore, .prettierrc, eslint.config.mjs, postcss.config.mjs, tsconfig.json
Added Prettier ignore and config (plugins + import ordering), new ESLint config module, switched PostCSS plugin key, and adjusted TS config (jsx → react-jsx, path formatting).
Dependencies & Manifest
package.json
Large dependency update: Next/React versions bumped, added React Query, Zod, Base UI, query/dev tooling and import-sort plugin; added overrides for @types/react packages; changed lint script.
CSS & Styling System
src/app/globals.css, tailwind.config.ts
Replaced Tailwind config (removed) with a token-driven globals.css including theme variables, utilities, and dark variant; tailwind.config.ts deleted.
Authentication & Cookie Handling
src/app/api/auth/*, src/app/api/google/auth/route.ts, src/app/api/auth/logout/route.ts, src/app/api/auth/verify/route.ts, src/actions/cookie_actions.ts
Renamed cookie to leadlly.in_admin_token across auth routes; logout now proxies to external admin logout and clears cookie; getCookie defaulted to the new name.
Routing / Middleware
src/middleware.ts (removed), src/proxy.ts
Removed legacy middleware; added proxy middleware implementing auth-aware redirects, user lookup via getUser, and route matching config.
React Query / Data Layer
src/app/QueryProvider.tsx, src/app/(protected)/layout.tsx, src/app/(protected)/root/layout.tsx, src/app/layout.tsx
Added QueryProvider with SSR-aware QueryClient, prefetching/hydration in protected layouts, and MainHeader hydration; root layout updated for font and body classes.
Action/Client APIs
src/actions/*.ts, src/apiClient/apiClient.ts
Standardized token retrieval to getCookie(), replaced revalidateTag with updateTag, added getActiveInstitute and other endpoint path updates, improved error handling and response shapes; cosmetic quote/style changes in apiClient.
Auth Pages & Flows
src/app/(auth)/_components/GoogleLoginButton.tsx, src/app/(auth)/login/page.tsx, src/app/(auth)/create-institute/page.tsx
Refactored Google login to Axios + admin flag, loading state, and revised navigation; login UI refactored to Card/Avatar; removed legacy auth create-institute page.
Protected Layout & Provider
src/app/(protected)/layout.tsx, src/app/provider.tsx, src/app/StoreProvider.tsx
Added ProtectedLayout that enforces auth (redirects when missing token), introduced async Provider fetching getUser and passing typed user to StoreProvider; StoreProvider typing hardened to IAdmin.
Institute Management Pages
src/app/(protected)/(root)/institute/[instituteId]/page.tsx, src/app/(protected)/(root)/institute/[instituteId]/batches/page.tsx, src/app/(protected)/(root)/institute/[instituteId]/students/page.tsx, src/app/(root)/institute/[instituteId]/page.tsx
Added new protected dashboard, batches page (create batch dialog + forms), server-side students page; removed older root institute page.
Header & Institute Components
src/app/(protected)/(root)/_components/main-header.tsx, src/app/(protected)/(root)/institute/.../InstituteOverview.tsx, StudentsOverview.tsx, add-students-dialog.tsx
Added MainHeader with institute dropdown and CreateInstituteForm dialog; refactored InstituteOverview to accept instituteId and fetch data; StudentsOverview prop shape flattened; added AddStudentsDialog component.
UI Component Library (many files)
src/components/ui/* (avatar, badge, button, button-group, combobox, field, input-group, scroll-area, separator, skeleton, sonner, spinner, switch, tabs, tooltip, etc.)
Large addition / overhaul of UI primitives: ~17 new/updated components (Radix + Base UI wrappers, CVA variants, accessibility hooks). Significant new public exports across many files.
Forms, Schemas & Types
src/helpers/schema/createInstituteSchema.ts, src/helpers/types/index.ts, src/redux/slices/*
Added CreateInstituteFormSchema (zod), introduced IAdmin and IInstitute types, tightened Redux slice types (IAdmin/IInstitute), and updated PayloadAction typings.
Pages & Components Cleanup
src/app/(root)/institute/_components/index.ts, src/app/(auth)/create-institute/page.tsx
Removed legacy barrel exports; migrated create-institute UI to protected route and new form component.
Misc: Small UI tweaks
assorted files under src/app/(protected)/(root) and components
Minor style/variant updates (button variants, focus outline, shadow sizes), formatting changes, and refactors across teacher/student views and smaller components.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Proxy as Proxy Middleware
    participant UserSvc as getUser (server)
    participant AdminAPI as Admin API
    participant Browser as Browser/Router

    Client->>Proxy: Request protected route
    Proxy->>Proxy: Read `leadlly.in_admin_token` cookie
    alt token present
        Proxy->>UserSvc: call getUser()
        UserSvc->>AdminAPI: GET /api/admin/get (with token)
        AdminAPI-->>UserSvc: { success, admin }
        UserSvc-->>Proxy: admin
        alt admin has institutes
            Proxy->>Client: NextResponse.next()
        else admin has no institutes
            Proxy->>Client: Redirect /create-institute
        end
    else no token
        Proxy->>Client: Redirect /login
    end
Loading
sequenceDiagram
    participant User as User
    participant GoogleBtn as GoogleLoginButton
    participant GoogleOAuth as Google OAuth
    participant TokenRoute as /api/google/auth
    participant AdminAPI as Admin API
    participant Router as Next Router

    User->>GoogleBtn: Click login
    GoogleBtn->>GoogleOAuth: OAuth flow
    GoogleOAuth-->>GoogleBtn: OAuth token
    GoogleBtn->>TokenRoute: POST token, isAdmin flag
    TokenRoute->>AdminAPI: Validate & exchange
    AdminAPI-->>TokenRoute: { status, data: { success, admin } }
    TokenRoute->>TokenRoute: Set `leadlly.in_admin_token` cookie
    TokenRoute-->>GoogleBtn: Response
    alt status === 201 or no institutes
        GoogleBtn->>Router: navigate /create-institute
    else has institutes
        GoogleBtn->>Router: navigate /
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 I hopped through tokens, cookies in tow,

Query caches warmed with a soft data glow.
New components sprout, petals on code,
Dialogs and forms now lighten my load.
Hooray — a carrot-shaped PR to and fro! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% 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 'Refactor admin' is vague and does not clearly convey the scope or nature of the extensive changes. While it relates to admin-focused refactoring, it lacks specificity about what was refactored. Consider a more descriptive title such as 'Refactor authentication, API integration, and component architecture' or 'Restructure admin dashboard and institute management flows' to better reflect the comprehensive scope of changes.
✅ 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 docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-admin

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.

/.vscode
/node_modules
./dist
*. env
Copy link

Choose a reason for hiding this comment

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

Security Issue: Typo in ignore pattern *. env (space between * and .env) will NOT match .env files. This defeats the purpose of ignoring environment files that typically contain sensitive credentials and secrets. Environment files could be formatted by Prettier and potentially exposed in version control.

Confidence: 5/5

Suggested Fix
Suggested change
*. env
*.env

Remove the space between * and .env to properly match all .env files.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In .prettierignore at line 4, there is a typo in the ignore pattern "*. env" which has
a space between the asterisk and ".env". This will not match .env files as intended.
Fix this by removing the space to make it "*.env" so that all environment files
containing sensitive credentials are properly ignored by Prettier.

./dist
*. env
.env
.env .* No newline at end of file
Copy link

Choose a reason for hiding this comment

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

Security Issue: Typo in ignore pattern .env .* (space between .env and .*) will NOT match environment variant files like .env.local, .env.production, .env.development. These files typically contain environment-specific secrets and credentials. The typo defeats the security purpose of ignoring these sensitive files.

Confidence: 5/5

Suggested Fix
Suggested change
.env .*
.env.*

Remove the space between .env and .* to properly match all environment variant files.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In .prettierignore at line 6, there is a typo in the ignore pattern ".env .*" which has
a space between ".env" and ".*". This will not match environment variant files like
.env.local, .env.production, etc. as intended. Fix this by removing the space to make
it ".env.*" so that all environment variant files containing sensitive credentials are
properly ignored by Prettier.

import { cookies } from "next/headers";

export const getCookie = async (name: string) => {
export const getCookie = async (name = "leadlly.in_admin_token") => {
Copy link

Choose a reason for hiding this comment

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

Security Issue: Hardcoding the admin authentication token cookie name "leadlly.in_admin_token" as a default parameter exposes sensitive authentication implementation details. This makes it easier for attackers to:

  • Target the specific cookie in session hijacking attacks
  • Craft CSRF attacks knowing the exact cookie name
  • Perform cookie theft or manipulation attacks
    The hardcoded default also reduces security flexibility - if the cookie name needs to be changed after a security incident, all implicit usages must be identified and updated.

Confidence: 4/5

Recommended Action

Consider one of these approaches:

  1. Remove the default parameter - Require explicit cookie name at all call sites for better security awareness and control
  2. Use environment variable - Store the cookie name in an environment variable (e.g., process.env.ADMIN_TOKEN_COOKIE_NAME) to avoid hardcoding
  3. Use a centralized constant - Define the cookie name in a secure configuration file that's not committed to version control
    If keeping the default, ensure:
  • The cookie has HttpOnly, Secure, and SameSite=Strict flags set
  • Cookie name rotation strategy is documented
  • All usages are audited for security implications
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/cookie_actions.ts at line 5, the getCookie function now has a hardcoded
default parameter "leadlly.in_admin_token" for the admin authentication cookie name.
This exposes sensitive authentication implementation details and makes it easier for
attackers to target this specific cookie. Remove the default parameter and require
explicit cookie names at all call sites, or move the cookie name to an environment
variable (process.env.ADMIN_TOKEN_COOKIE_NAME) to avoid hardcoding sensitive values.
Review all call sites to ensure they explicitly specify the cookie name for better
security awareness.

standards?: string[];
}
type InstituteCreateData = z.infer<typeof CreateInstituteFormSchema>;

Copy link

Choose a reason for hiding this comment

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

Security Issue - Potential Authentication Bypass: The change from getCookie("token") to getCookie() now relies on the hardcoded default parameter "leadlly.in_admin_token" (as identified in Comment #4 on cookie_actions.ts).
Critical Problem: If the actual authentication cookie name is "token" (as the original code indicates), this change will cause getCookie() to look for the wrong cookie name ("leadlly.in_admin_token" instead of "token"). This means:

  • token variable will be undefined or null
  • The Cookie: token=${token} header will be malformed (e.g., Cookie: token=undefined)
  • All API requests will fail authentication or bypass authentication checks entirely
  • This affects ALL institute operations: createInstitute, getMyInstitute, getAllUserInstitutes
    Verify immediately: Check what the actual cookie name is in production. If it's "token", this is a breaking change that will cause complete authentication failure for all institute-related API calls.

Confidence: 5/5

Recommended Action

Immediate verification needed:

  1. Check the actual cookie name set during authentication (in login/auth routes)
  2. If the cookie name is "token", revert these changes and pass "token" explicitly
  3. If the cookie name is "leadlly.in_admin_token", then the original code was buggy and this fix is correct
    Recommended fix (if cookie name is actually "token"):
const token = await getCookie("token");

Or better yet, define a constant for the cookie name:

const AUTH_COOKIE_NAME = process.env.AUTH_COOKIE_NAME || "token";
const token = await getCookie(AUTH_COOKIE_NAME);

This same issue affects lines 39, 66, and 93 (commented code) in this file.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/institute_actions.ts at lines 13, 39, and 66, the code was changed from
getCookie("token") to getCookie() which now relies on a hardcoded default parameter
"leadlly.in_admin_token". This is a critical security issue because if the actual
authentication cookie name is "token" (as the original code suggests), all these API
calls will fail to retrieve the authentication token, causing authentication bypass.
Verify the actual cookie name used in production immediately. If it's "token", revert
to getCookie("token"). If it's "leadlly.in_admin_token", document this change as a bug
fix. Consider using an environment variable for the cookie name instead of hardcoding.

instituteId: string,
emails: string[]
) {
try {
Copy link

Choose a reason for hiding this comment

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

Same authentication issue as Comment #5 - The change from getCookie("token") to getCookie() creates the same potential authentication bypass vulnerability identified in institute_actions.ts. If the actual authentication cookie name is "token" (as the original code indicates), this will cause the addTeacherToInstitute function to fail authentication, as it will look for the wrong cookie name ("leadlly.in_admin_token" instead of "token").
This affects the API call on line 45 where Cookie: token=${token} will be malformed if token is undefined.

Confidence: 5/5

Suggested Fix
Suggested change
try {
const token = await getCookie("token");

Revert to explicitly passing the cookie name, or verify that the actual authentication cookie name is "leadlly.in_admin_token" and document this as a bug fix.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/mentor_actions.ts at line 17, the code was changed from getCookie("token")
to getCookie() which now relies on a hardcoded default parameter "leadlly.in_admin_token".
This is the same critical authentication issue identified in institute_actions.ts Comment #5.
Verify the actual authentication cookie name used in production. If it's "token", revert to
getCookie("token"). If it's "leadlly.in_admin_token", document this as a bug fix. Consider
using an environment variable for the cookie name to avoid hardcoding sensitive values.

instituteId: string,
emails: string[]
) {
try {
Copy link

Choose a reason for hiding this comment

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

Same authentication issue as Comments #5 and #6 - The change from getCookie("token") to getCookie() creates the same potential authentication bypass vulnerability identified in institute_actions.ts and mentor_actions.ts.
If the actual authentication cookie name is "token" (as the original code indicates), this will cause the addStudentsToInstitute function to fail authentication. The function will look for the wrong cookie name ("leadlly.in_admin_token" instead of "token"), resulting in:

  • token variable being undefined or null
  • Malformed Cookie: token=undefined header on line 45
  • Complete authentication failure for student addition operations
    This affects the API call to /api/student/add/${instituteId} which requires valid authentication.

Confidence: 5/5

Suggested Fix
Suggested change
try {
const token = await getCookie("token");

Revert to explicitly passing the cookie name. If the actual cookie name is "leadlly.in_admin_token", then document this as a bug fix and verify all authentication flows are working correctly.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/student_action.ts at line 17, the code was changed from getCookie("token")
to getCookie() which now relies on a hardcoded default parameter "leadlly.in_admin_token".
This is the same critical authentication issue identified in institute_actions.ts (Comment #5)
and mentor_actions.ts (Comment #6). Verify the actual authentication cookie name used in
production immediately. If it's "token", revert to getCookie("token"). If it's
"leadlly.in_admin_token", document this as a bug fix and verify all authentication flows.
Consider using an environment variable for the cookie name to avoid hardcoding sensitive values.


export const getUser = async () => {
const token = await getCookie("token");
export const getUser = cache(async () => {
Copy link

Choose a reason for hiding this comment

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

Same authentication issue as Comments #5, #6, and #7 - The change from getCookie("token") to getCookie() creates the same potential authentication bypass vulnerability identified in institute_actions.ts, mentor_actions.ts, and student_action.ts.
Critical Problem: This change affects the getUser function (now wrapped in React cache()), which is likely a core authentication function used throughout the application. If the actual authentication cookie name is "token" (as the original code indicates), this will cause:

  • token variable to be undefined or null
  • Malformed Cookie: token=undefined header on line 149
  • Complete authentication failure for user data retrieval
  • Cascading failures across the entire application since getUser is typically called to verify authentication state
    This same issue also affects:
  • Line 178: studentPersonalInfo function
  • Line 208: setTodaysVibe function
    Verify immediately: Check what the actual cookie name is in production. If it's "token", this is a breaking change that will cause complete authentication failure across all user-related operations.

Confidence: 5/5

Suggested Fix
Suggested change
export const getUser = cache(async () => {
const token = await getCookie("token");

Revert to explicitly passing the cookie name. If the actual cookie name is "leadlly.in_admin_token", then document this as a bug fix and verify all authentication flows are working correctly.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/user_actions.ts at lines 145, 178, and 208, the code was changed from
getCookie("token") to getCookie() which now relies on a hardcoded default parameter
"leadlly.in_admin_token". This is the same critical authentication issue identified in
Comments #5, #6, and #7 for other action files. The getUser function is particularly
critical as it's likely used throughout the application for authentication checks. Verify
the actual authentication cookie name used in production immediately. If it's "token",
revert all three instances to getCookie("token"). If it's "leadlly.in_admin_token",
document this as a bug fix and verify all authentication flows. Consider using an
environment variable for the cookie name to avoid hardcoding sensitive values.

setLoading(true);
// Dispatch institute data to Redux store
dispatch(instituteData(institute));
// dispatch(instituteData(institute));
Copy link

Choose a reason for hiding this comment

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

Security Concern - Potential Authorization Bypass: Commenting out dispatch(instituteData(institute)) removes the Redux state storage for the selected institute. The code still navigates to /institute/${institute._id} (line 60), but without storing the institute data in Redux.
Security Risk: If the destination page (/institute/[instituteId]/page.tsx) relies on Redux state to verify that the user has legitimate access to the selected institute, this change breaks that authorization check. A malicious user could potentially:

  1. Navigate directly to /institute/{any-institute-id} via URL manipulation
  2. Bypass the institute selection flow entirely
  3. Access institutes they don't own if the destination page doesn't perform server-side authorization
    Critical Questions:
  • Does /institute/[instituteId]/page.tsx perform server-side authorization checks?
  • Or does it rely on Redux state to verify the user selected this institute legitimately?
    If authorization relies on client-side Redux state, this is a critical security vulnerability. If the destination page performs proper server-side authorization (checking that the authenticated user owns/has access to the requested institute ID), then this change is safe but removes useful client-side state management.

Confidence: 4/5

Recommended Action

Immediate verification needed:

  1. Review /institute/[instituteId]/page.tsx to confirm it performs server-side authorization
  2. Ensure the page validates that the authenticated user has access to the requested institute ID
  3. Do NOT rely on client-side Redux state for authorization decisions
    If keeping the commented code:
  • Document why Redux dispatch was removed
  • Ensure all institute pages perform server-side authorization checks
  • Consider removing the commented code entirely if it's no longer needed
    If Redux state is needed:
  • Uncomment lines 35 and 58 to restore Redux functionality
  • Or implement proper server-side authorization if not already present
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/(auth)/select-institute/page.tsx at lines 35 and 58, the Redux dispatch
functionality has been commented out. This means the selected institute data is no
longer stored in Redux state before navigating to /institute/${institute._id}. Verify
that the destination page (/institute/[instituteId]/page.tsx) performs proper
server-side authorization to check that the authenticated user has access to the
requested institute ID. If authorization relies on client-side Redux state, this is a
critical security vulnerability that enables authorization bypass via direct URL
manipulation. Either uncomment the Redux dispatch code or ensure robust server-side
authorization is in place. Document the reason for this change.

export const dynamic = "force-dynamic";

const ProtectedLayout = async ({ children }: { children: React.ReactNode }) => {
const token = await getCookie();
Copy link

Choose a reason for hiding this comment

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

Critical Security Issue - Authentication Gateway Failure: This line uses getCookie() without specifying the cookie name, relying on the hardcoded default parameter "leadlly.in_admin_token" from cookie_actions.ts (see Comment #4).
Why this is CRITICAL: This is the authentication gateway for ALL protected routes in the application. If the actual authentication cookie name is "token" (as indicated by the original code in Comments #5-#8), then:

  1. Check the actual cookie name set during login (in /api/auth/login/route.ts)
  2. If it's "token", this is a breaking change causing complete application lockout
  3. If it's "leadlly.in_admin_token", verify that all authentication flows use this name consistently

Confidence: 5/5

Suggested Fix
Suggested change
const token = await getCookie();
const token = await getCookie("token");

Explicitly specify the cookie name. If the actual cookie name is "leadlly.in_admin_token", then use that and document this as a bug fix. Better yet, use an environment variable:

const AUTH_COOKIE_NAME = process.env.AUTH_COOKIE_NAME || "token";
const token = await getCookie(AUTH_COOKIE_NAME);
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/(protected)/layout.tsx at line 10, the code uses getCookie() without
specifying the cookie name, relying on a hardcoded default "leadlly.in_admin_token".
This is the authentication gateway for ALL protected routes. If the actual
authentication cookie name is "token" (as suggested by original code in other files),
this will cause complete application lockout - no authenticated users can access any
protected routes. Verify the actual cookie name used in /api/auth/login/route.ts
immediately. If it's "token", change line 10 to getCookie("token"). If it's
"leadlly.in_admin_token", document this as a bug fix and verify all authentication
flows. Consider using an environment variable for the cookie name to avoid hardcoding.

res.cookies.set('token', '', {
await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
{
Copy link

Choose a reason for hiding this comment

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

Critical Security Issue - Session Fixation Vulnerability: The logout route now clears a cookie named "leadlly.in_admin_token" instead of "token". This creates a critical security vulnerability if the actual authentication cookie name is "token" (as indicated by the original code).
Impact:

  1. What cookie name does /api/auth/login/route.ts actually set?
  2. If it sets "token", then this logout route will fail to clear it
  3. If it sets "leadlly.in_admin_token", then the original code was buggy
    Immediate verification required: Check the login route to confirm which cookie name is actually being set during authentication.

Confidence: 5/5

Suggested Fix
Suggested change
{
res.cookies.set("token", "", {

Revert to clearing the "token" cookie if that's what the login route sets. If the login route sets "leadlly.in_admin_token", then this change is correct but you must verify that ALL authentication flows (login, protected routes, API calls) consistently use "leadlly.in_admin_token".
Better approach: Use a centralized constant for the cookie name:

const AUTH_COOKIE_NAME = process.env.AUTH_COOKIE_NAME || "token";
res.cookies.set(AUTH_COOKIE_NAME, "", { ... });
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/api/auth/logout/route.ts at line 7, the logout route now clears a cookie
named "leadlly.in_admin_token" instead of "token". This is a critical security issue
because if the login route sets a cookie named "token", then logout will fail to clear
it, leaving users permanently logged in and creating a session fixation vulnerability.
Verify immediately what cookie name is set in src/app/api/auth/login/route.ts. If it's
"token", revert line 7 to res.cookies.set("token", "", {...}). If it's
"leadlly.in_admin_token", verify that ALL authentication flows (protected layout,
action files, API routes) consistently use this name. Consider using an environment
variable for the cookie name to avoid hardcoding and ensure consistency across the
codebase.

Comment on lines +15 to +19
res.cookies.set("leadlly.in_admin_token", token, {
httpOnly: true,
path: '/',
sameSite: 'strict',
expires: new Date('9999-12-31T23:59:59Z')
path: "/",
sameSite: "strict",
expires: new Date("9999-12-31T23:59:59Z"),
Copy link

Choose a reason for hiding this comment

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

Security Issue - Hardcoded Cookie Name & Lack of Centralization: The authentication cookie name "leadlly.in_admin_token" is now hardcoded in this verify route. This same cookie name is hardcoded in multiple other files (logout route, cookie_actions.ts default parameter). If these values get out of sync during future maintenance, authentication will break across the application.
Additional Concern: The cookie expiration is set to year 9999 (essentially never expires). While this might be intentional for persistent sessions, it creates a security risk if:

  • The token itself doesn't have server-side expiration validation
  • Compromised tokens remain valid indefinitely
  • Users cannot force session termination across devices
    Recommendation: Centralize the cookie name in a single configuration constant to prevent synchronization issues:

Confidence: 4/5

Suggested Fix

Create a centralized auth configuration file (e.g., src/config/auth.ts):

export const AUTH_CONFIG = {
COOKIE_NAME: process.env.AUTH_COOKIE_NAME || "leadlly.in_admin_token",
COOKIE_MAX_AGE: 30 * 24 * 60 * 60, // 30 days in seconds
} as const;

Then update this file to use the centralized constant:

Suggested change
res.cookies.set("leadlly.in_admin_token", token, {
httpOnly: true,
path: '/',
sameSite: 'strict',
expires: new Date('9999-12-31T23:59:59Z')
path: "/",
sameSite: "strict",
expires: new Date("9999-12-31T23:59:59Z"),
res.cookies.set(AUTH_CONFIG.COOKIE_NAME, token, {
httpOnly: true,
path: "/",
sameSite: "strict",
maxAge: AUTH_CONFIG.COOKIE_MAX_AGE,
});

Benefits:

  • Single source of truth for cookie name
  • Easy to change cookie name if needed (security incident response)
  • Environment variable support for different deployments
  • More reasonable cookie expiration (30 days instead of forever)
    Important: Update all other files that reference this cookie name to use the same centralized constant:
  • src/actions/cookie_actions.ts (default parameter)
  • src/app/api/auth/logout/route.ts (cookie clearing)
  • src/app/(protected)/layout.tsx (authentication check)
  • All action files that call getCookie()
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/api/auth/verify/route.ts at lines 15-19, the authentication cookie name
"leadlly.in_admin_token" is hardcoded. This same value is hardcoded in multiple other
files (logout route, cookie_actions.ts), creating a maintenance and security risk if
they get out of sync. Create a centralized auth configuration file at
src/config/auth.ts that exports AUTH_CONFIG with COOKIE_NAME and COOKIE_MAX_AGE
constants. Update line 15 to use AUTH_CONFIG.COOKIE_NAME instead of the hardcoded
string. Also consider changing the cookie expiration from year 9999 to a more
reasonable value like 30 days using maxAge instead of expires. Then update all other
files that reference this cookie name to use the same centralized constant for
consistency and security.

📍 This suggestion applies to lines 15-19

Comment on lines 16 to +20
const res = NextResponse.json(userData);

res.cookies.set('token', token, {
res.cookies.set("leadlly.in_admin_token", token, {
httpOnly: true,
path: '/',
sameSite: 'strict',
expires: new Date('9999-12-31T23:59:59Z')
});
path: "/",
Copy link

Choose a reason for hiding this comment

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

Security Issue - Hardcoded Cookie Name & Lack of Centralization: The authentication cookie name "leadlly.in_admin_token" is hardcoded here, matching the same hardcoded value in the verify route (Comment #12) and logout route (Comment #11). While this consistency is good, having the cookie name hardcoded in multiple files creates a maintenance and security risk.
Additional Security Concern: The cookie expiration is set to year 9999 (line 20), meaning the cookie essentially never expires. Combined with the lack of centralized configuration, if a security incident requires changing the cookie name or expiration policy, you'll need to update multiple files manually, increasing the risk of missing one and creating authentication inconsistencies.
Impact: If these hardcoded values get out of sync during future maintenance, authentication will break across the application. This is particularly risky for security-critical changes that need to be deployed quickly (e.g., responding to a credential leak).

Confidence: 4/5

Recommended Action

Create a centralized auth configuration file (e.g., src/config/auth.ts):

export const AUTH_CONFIG = {
COOKIE_NAME: process.env.AUTH_COOKIE_NAME || "leadlly.in_admin_token",
COOKIE_MAX_AGE: 30 * 24 * 60 * 60, // 30 days in seconds
} as const;

Then update this file to use the centralized constant:

import { AUTH_CONFIG } from "@/config/auth";
// In the POST handler:
res.cookies.set(AUTH_CONFIG.COOKIE_NAME, token, {
httpOnly: true,
path: "/",
sameSite: "strict",
maxAge: AUTH_CONFIG.COOKIE_MAX_AGE,
});

Benefits:

  • Single source of truth for cookie name and expiration
  • Easy to change cookie configuration if needed (security incident response)
  • Environment variable support for different deployments
  • More reasonable cookie expiration (30 days instead of forever)
    Important: Update all other files that reference this cookie name to use the same centralized constant:
  • src/app/api/auth/verify/route.ts (line 15)
  • src/app/api/auth/logout/route.ts (line 7)
  • src/actions/cookie_actions.ts (default parameter)
  • src/app/(protected)/layout.tsx (authentication check)
  • All action files that call getCookie()
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/api/google/auth/route.ts at lines 16-20, the authentication cookie name
"leadlly.in_admin_token" is hardcoded. This same value is hardcoded in multiple other
files (verify route, logout route, cookie_actions.ts), creating a maintenance and
security risk if they get out of sync. Create a centralized auth configuration file at
src/config/auth.ts that exports AUTH_CONFIG with COOKIE_NAME and COOKIE_MAX_AGE
constants. Update line 16 to use AUTH_CONFIG.COOKIE_NAME instead of the hardcoded
string. Also consider changing the cookie expiration from year 9999 to a more
reasonable value like 30 days using maxAge instead of expires. Then update all other
files that reference this cookie name to use the same centralized constant for
consistency and security.

📍 This suggestion applies to lines 16-20

import { getUser } from "./actions/user_actions";

export async function proxy(request: NextRequest) {
const token = request.cookies.get("leadlly.in_admin_token")?.value;
Copy link

Choose a reason for hiding this comment

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

Critical Security Issue - Hardcoded Cookie Name & Potential Authentication Bypass: This line hardcodes the authentication cookie name as "leadlly.in_admin_token", which is part of a systematic cookie name change across the entire codebase (see Comments #4-#13).
Critical Problems:

  1. Inconsistency Risk: If the actual authentication cookie set by /api/auth/login/route.ts or /api/google/auth/route.ts is named "token" (as the original code suggested), this proxy will fail to retrieve the authentication token, causing:
  • All authenticated users to be redirected to /login (line 17)
  • Complete application lockout for legitimate users
  • Authentication bypass if the check fails open
  1. Lack of Centralization: The cookie name is hardcoded in at least 13 different locations across the codebase. If these values get out of sync during maintenance or security incident response, authentication will break unpredictably.
  2. Security Incident Response: If a security incident requires changing the cookie name (e.g., after credential leak), you must manually update 13+ files, increasing the risk of missing one and creating authentication vulnerabilities.
    This is particularly critical because:
  • This proxy function is the authentication gateway for the entire application
  • It's called on EVERY request via the middleware matcher (line 37)
  • A failure here affects ALL routes, not just individual API calls
    Immediate verification required:
  1. Check what cookie name is actually set in /api/auth/login/route.ts and /api/google/auth/route.ts
  2. If it's "token", this is a breaking change causing complete application lockout
  3. If it's "leadlly.in_admin_token", verify ALL authentication flows use this name consistently

Confidence: 5/5

Recommended Action

Create a centralized auth configuration (e.g., src/config/auth.ts):

export const AUTH_CONFIG = {
COOKIE_NAME: process.env.AUTH_COOKIE_NAME || "leadlly.in_admin_token",
COOKIE_MAX_AGE: 30 * 24 * 60 * 60, // 30 days in seconds
} as const;

Then update this file:

import { AUTH_CONFIG } from "./config/auth";
export async function proxy(request: NextRequest) {
const token = request.cookies.get(AUTH_CONFIG.COOKIE_NAME)?.value;
// ... rest of the code
}

Benefits:

  • Single source of truth for cookie name
  • Environment variable support for different deployments
  • Easy to change cookie name during security incidents
  • Prevents synchronization issues across 13+ files
    Critical: Update ALL files that reference this cookie name to use the same centralized constant:
  • src/actions/cookie_actions.ts (default parameter)
  • src/app/(protected)/layout.tsx (line 10)
  • src/app/api/auth/logout/route.ts (line 7)
  • src/app/api/auth/verify/route.ts (line 15)
  • src/app/api/google/auth/route.ts (line 16)
  • All action files that call getCookie()
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/proxy.ts at line 7, the authentication cookie name "leadlly.in_admin_token" is
hardcoded. This is part of a systematic cookie name change across 13+ files in the
codebase, creating a critical security risk. If the actual authentication cookie name
set by the login routes is "token" (as original code suggested), this will cause
complete application lockout - all authenticated users will be redirected to /login.
Create a centralized auth configuration file at src/config/auth.ts that exports
AUTH_CONFIG with COOKIE_NAME constant (using environment variable with fallback).
Update line 7 to use AUTH_CONFIG.COOKIE_NAME. Then update ALL other files that
reference this cookie name (cookie_actions.ts, protected layout, logout route, verify
route, google auth route, and all action files) to use the same centralized constant.
Verify immediately what cookie name is actually set in the login routes to ensure
consistency.

headers: {
"Content-Type": "application/json",
Cookie: `token=${token}`,
isAdmin: "true",
Copy link

Choose a reason for hiding this comment

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

Critical Security Issue - Client-Controlled Authorization Header: Adding isAdmin: "true" as a request header is a severe security vulnerability if the backend API trusts this header for authorization decisions. This header is set on the client side and can be easily manipulated by attackers.
Attack Scenario:

  1. Attacker intercepts or modifies the request
  2. Adds or keeps isAdmin: "true" header
  3. If backend trusts this header, attacker gains admin privileges
  4. Can create institutes with elevated permissions or bypass authorization checks
    Impact: If the backend /api/institute endpoint uses this header to determine admin status without proper server-side authentication/authorization, this creates a complete authorization bypass vulnerability allowing any user to perform admin-level operations.

Confidence: 5/5

Recommended Action

Immediate verification required:

  1. Review the backend /api/institute endpoint implementation
  2. Ensure it performs proper server-side authorization using the authentication token
  3. Verify it does NOT trust the isAdmin header for authorization decisions
    If the header is required:
  • The backend MUST validate admin status from the JWT token or session, NOT from this header
  • The header should only be used as a hint or for routing, never for authorization
  • Consider removing this header entirely if the backend can determine admin status from the token
    Best practice:
  • Authorization decisions must ALWAYS be made server-side based on authenticated session/token
  • Never trust client-provided headers for security decisions
  • The authentication token (line 25) should contain all necessary authorization information
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/institute_actions.ts at line 26, a client-controlled header "isAdmin: true"
is being sent to the backend API. This is a critical security vulnerability if the backend
trusts this header for authorization decisions. Review the backend /api/institute endpoint
to ensure it performs proper server-side authorization using the authentication token and
does NOT trust the isAdmin header for authorization. If the backend correctly validates
admin status from the token, document why this header is needed. If the backend relies on
this header for authorization, this is a critical vulnerability that must be fixed by
implementing proper server-side authorization checks based on the authenticated token.

headers: {
"Content-Type": "application/json",
Cookie: `token=${token}`,
isAdmin: "true",
Copy link

Choose a reason for hiding this comment

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

Critical Security Issue - Client-Controlled Authorization Header: Adding isAdmin: "true" as a request header is a severe security vulnerability if the backend API trusts this header for authorization decisions. This header is set on the client side and can be easily manipulated by attackers.
Attack Scenario:

  1. Attacker intercepts or modifies the request using browser DevTools or a proxy
  2. Adds or keeps isAdmin: "true" header in any request
  3. If backend /api/admin/get endpoint trusts this header, attacker gains admin privileges
  4. Can retrieve admin user data or bypass authorization checks
    Impact: If the backend endpoint uses this header to determine admin status without proper server-side authentication/authorization validation, this creates a complete authorization bypass vulnerability allowing any authenticated user to access admin-only data.
    Related to Comment #15: This is the same pattern identified in institute_actions.ts where isAdmin: "true" header was added. This appears to be a systematic issue across multiple action files.

Confidence: 5/5

Recommended Action

Immediate verification required:

  1. Review the backend /api/admin/get endpoint implementation
  2. Ensure it performs proper server-side authorization using the authentication token (JWT validation)
  3. Verify it does NOT trust the isAdmin header for authorization decisions
    If the header is required:
  • The backend MUST validate admin status from the JWT token payload or session, NOT from this header
  • The header should only be used as a routing hint or API versioning, never for authorization
  • Consider removing this header entirely if the backend can determine admin status from the token
    Best practice:
  • Authorization decisions must ALWAYS be made server-side based on authenticated session/token claims
  • Never trust client-provided headers for security-critical decisions
  • The authentication token (line 154) should contain all necessary authorization information (roles, permissions)
  • Backend should decode and validate the JWT token to extract admin status
    If keeping the header:
    Document clearly in code comments that this header is NOT used for authorization and is only for routing/logging purposes.
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/user_actions.ts at line 155, a client-controlled header "isAdmin: true"
is being sent to the backend API endpoint /api/admin/get. This is a critical security
vulnerability if the backend trusts this header for authorization decisions. Review the
backend /api/admin/get endpoint to ensure it performs proper server-side authorization
by validating the JWT token and extracting admin status from the token payload, NOT
from the isAdmin header. If the backend correctly validates admin status from the token
claims, document in a code comment why this header is needed (e.g., for routing or
logging only). If the backend relies on this header for authorization, this is a
critical vulnerability that must be fixed immediately by implementing proper server-side
JWT validation and role checking based on the authenticated token.

"/api/google/auth",
{
access_token: credentialResponse.access_token,
isAdmin: true,
Copy link

Choose a reason for hiding this comment

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

Critical Security Issue - Client-Controlled Authorization Header: Adding isAdmin: true as a request body parameter is a severe security vulnerability if the backend API trusts this parameter for authorization decisions. This value is set on the client side and can be easily manipulated by attackers.
Attack Scenario:

  1. Attacker intercepts or modifies the request using browser DevTools or a proxy
  2. Sets isAdmin: true in the request body
  3. If backend /api/google/auth endpoint trusts this parameter, attacker gains admin privileges during Google OAuth login
  4. Can register as an admin user or elevate privileges during authentication
    Impact: If the backend endpoint uses this parameter to determine admin status without proper server-side validation, this creates a complete authorization bypass vulnerability allowing any user with a Google account to register as an admin.
    Related to Comments #15 and #16: This is the same pattern identified in institute_actions.ts and user_actions.ts where isAdmin: "true" header was added. This appears to be a systematic issue across multiple files in this PR.

Confidence: 5/5

Recommended Action

Immediate verification required:

  1. Review the backend /api/google/auth/route.ts endpoint implementation (modified in this PR - see Comment feat(api): add teacher and student management endpoints with filtering, pagination and type safety #13)
  2. Ensure it performs proper server-side authorization and does NOT trust the isAdmin parameter for authorization decisions
  3. Verify that admin status is determined by server-side logic (e.g., checking email domain, pre-approved admin list, or existing database records)
    If the parameter is required:
  • The backend MUST validate admin status through server-side logic, NOT from this client-provided parameter
  • The parameter should only be used as a hint for UI/routing purposes, never for authorization
  • Consider removing this parameter entirely if the backend can determine admin status independently
    Best practice:
  • Authorization decisions must ALWAYS be made server-side based on trusted data sources
  • Never trust client-provided parameters for security-critical decisions
  • Admin status should be determined by server-side validation (e.g., email whitelist, database lookup, or organizational domain verification)
    If keeping the parameter:
    Document clearly in code comments that this parameter is NOT used for authorization and is only for client-side routing/UI purposes. The backend must independently verify admin status.
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/(auth)/_components/GoogleLoginButton.tsx at line 29, a client-controlled
parameter "isAdmin: true" is being sent to the backend API endpoint /api/google/auth.
This is a critical security vulnerability if the backend trusts this parameter for
authorization decisions during Google OAuth authentication. Review the backend
/api/google/auth/route.ts endpoint to ensure it performs proper server-side
authorization and determines admin status through trusted server-side logic (such as
email domain validation, pre-approved admin list, or database lookup), NOT from the
isAdmin parameter sent by the client. If the backend correctly validates admin status
independently, document in a code comment why this parameter is needed (e.g., for
client-side routing only). If the backend relies on this parameter for authorization,
this is a critical vulnerability that must be fixed immediately by implementing proper
server-side admin validation logic.


res.cookies.set('token', '', {
await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
Copy link

Choose a reason for hiding this comment

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

Security Issue - Backend API URL Exposure: Using NEXT_PUBLIC_ADMIN_API_BASE_URL exposes the internal admin API base URL to all client-side code. Environment variables prefixed with NEXT_PUBLIC_* are embedded in the client-side JavaScript bundle and are visible to anyone inspecting the browser DevTools, page source, or network requests.
Security Risks:

  • Attackers can discover the backend admin API endpoint structure
  • If the backend API is meant to be internal/private, this exposure defeats that security boundary
  • Attackers can attempt direct attacks against the exposed backend API endpoints
  • The full API base URL becomes public knowledge, making targeted attacks easier
    Impact: This is particularly concerning for an admin API, as it exposes administrative endpoints that should typically remain hidden from public view.

Confidence: 5/5

Recommended Action

Option 1: Use server-side environment variable (Recommended)
Since this is a Next.js API route (server-side), use a server-only environment variable:

await fetch(
`${process.env.ADMIN_API_BASE_URL}/api/auth/logout`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}
);

Then in your .env file:

ADMIN_API_BASE_URL=https://internal-admin-api.example.com

Option 2: Use relative URL if backend is same domain
If the backend API is hosted on the same domain, use a relative URL:

await fetch("/api/auth/logout", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
});

Additional Recommendations:

  • Add error handling for the fetch call to log backend logout failures
  • Consider if this backend call is necessary - the cookie clearing on line 15 might be sufficient
  • If keeping the backend call, document why it's needed (e.g., server-side session invalidation)
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/api/auth/logout/route.ts at line 6, the code uses NEXT_PUBLIC_ADMIN_API_BASE_URL
which exposes the internal admin API base URL to all client-side code. Since this is a
Next.js API route (server-side), change it to use a server-only environment variable
instead. Replace process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL with
process.env.ADMIN_API_BASE_URL (without the NEXT_PUBLIC_ prefix). Update your .env file
to define ADMIN_API_BASE_URL with the backend API URL. This prevents exposing internal
API endpoints to the client-side JavaScript bundle. Also consider adding error handling
for the fetch call to log any backend logout failures.

Copy link

@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: 5

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (8)
src/actions/batch_actions.ts (2)

59-67: ⚠️ Potential issue | 🟡 Minor

Same copy-paste error in getInstituteBatch.

Error messages should reference batch retrieval, not "daily quiz answers".

🐛 Proposed fix
  } catch (error) {
    if (error instanceof Error) {
-      throw new Error(`Error in saving daily quiz answers: ${error.message}`);
+      throw new Error(`Error in fetching institute batches: ${error.message}`);
    } else {
      throw new Error(
-        "An unknown error occurred while saving daily quiz answers!",
+        "An unknown error occurred while fetching institute batches!",
      );
    }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/batch_actions.ts` around lines 59 - 67, The catch block inside
getInstituteBatch currently throws errors mentioning "saving daily quiz answers"
due to copy-paste; update the thrown messages in the catch (the branches
checking error instanceof Error and the else branch) to reference batch
retrieval (e.g., "Error retrieving institute batch" or "An unknown error
occurred while retrieving institute batch") and include the original
error.message in the instanceof Error branch so getInstituteBatch's logs
correctly describe the failure (locate the catch block in getInstituteBatch
where it does throw new Error(...)).

31-39: ⚠️ Potential issue | 🟡 Minor

Error messages reference wrong functionality.

The error messages mention "daily quiz answers" but this function handles batch creation. This appears to be a copy-paste error that could cause confusion during debugging.

🐛 Proposed fix
  } catch (error) {
    if (error instanceof Error) {
-      throw new Error(`Error in saving daily quiz answers: ${error.message}`);
+      throw new Error(`Error in creating batch: ${error.message}`);
    } else {
      throw new Error(
-        "An unknown error occurred while saving daily quiz answers!",
+        "An unknown error occurred while creating batch!",
      );
    }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/batch_actions.ts` around lines 31 - 39, The catch block in
src/actions/batch_actions.ts currently throws errors mentioning "daily quiz
answers" (the string "Error in saving daily quiz answers") which is a copy-paste
mistake; update both thrown messages to correctly reference batch creation
(e.g., "Error creating batch(s)" for Error instances and "An unknown error
occurred while creating batches" for the unknown branch) so logs match the
function's purpose—locate the catch that throws new Error with "Error in saving
daily quiz answers" and replace those messages accordingly.
src/actions/mentor_actions.ts (2)

82-98: ⚠️ Potential issue | 🟠 Major

Missing authentication token in API request.

getInstituteStudents doesn't include the authentication token in the request headers, unlike addTeacherToInstitute. If this endpoint requires authentication, requests will fail.

🔒 Proposed fix to add authentication
 export async function getInstituteStudents(instituteId: string) {
   try {
+    const token = await getCookie();
+
     // Validate input
     if (!instituteId) {
       throw new Error("Institute ID is required");
     }

     // Make API call to backend
     const response = await fetch(
       `${process.env.NEXT_PUBLIC_API_URL}/api/institutes/${instituteId}/students`,
       {
         method: "GET",
         headers: {
           "Content-Type": "application/json",
+          Cookie: `token=${token}`,
         },
       }
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/mentor_actions.ts` around lines 82 - 98, getInstituteStudents is
missing the authentication token in its fetch headers; mirror the approach used
in addTeacherToInstitute by retrieving the same auth token (use the existing
auth helper or the same storage mechanism used by addTeacherToInstitute), add an
Authorization header like "Authorization: Bearer <token>" to the request options
in getInstituteStudents, and validate the token exists (throw or handle error if
absent) while preserving the existing Content-Type header.

7-16: ⚠️ Potential issue | 🟡 Minor

JSDoc comment doesn't match function name.

The JSDoc describes "Add students to an institute" but the function is named addTeacherToInstitute. Either update the documentation to reflect "teachers" or rename the function if it's actually for students.

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

In `@src/actions/mentor_actions.ts` around lines 7 - 16, The JSDoc for
addTeacherToInstitute is incorrect (it says "Add students to an institute");
either update the comment to describe adding teachers or rename the function to
reflect student behavior—inspect the implementation of addTeacherToInstitute to
determine its purpose, then (a) if it manages teachers, change the JSDoc summary
and param descriptions to mention teachers and teacher emails, or (b) if it
actually adds students, rename the function to addStudentToInstitute (and update
any references) and adjust the JSDoc accordingly; ensure the function name,
JSDoc summary, and parameter descriptions (emails, instituteId) are consistent.
src/app/(protected)/(root)/institute/[instituteId]/batches/[batchId]/students/page.tsx (1)

206-248: ⚠️ Potential issue | 🟠 Major

Invalid Button variant "outline-solid" — use existing variant instead.

The buttonVariants only define: default, outline, secondary, ghost, destructive, link. The "outline-solid" variant does not exist and will cause type/styling issues.

Fix — replace with valid variant
               <Button
-                variant={filter === "All" ? "default" : "outline-solid"}
+                variant={filter === "All" ? "default" : "outline"}

Apply to all four filter buttons (lines 206, 217, 228, 239).

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

In
`@src/app/`(protected)/(root)/institute/[instituteId]/batches/[batchId]/students/page.tsx
around lines 206 - 248, The Button components that render the filters use a
non-existent variant "outline-solid"; update each of the filter Buttons (the
ones comparing filter === "All"/"Excellent"/"Optimal"/"Insufficient") to use a
valid variant from buttonVariants (e.g., replace "outline-solid" with "outline")
so the variant prop on those Buttons matches the defined variants and styling
works; keep the conditional logic (variant={filter === "X" ? "default" :
"outline"}) and the onClick handlers that call handleFilter("X") unchanged.
src/app/(protected)/(root)/(dashboard)/page.tsx (1)

60-82: ⚠️ Potential issue | 🟠 Major

Don’t ship the dashboard with its body commented out.

Lines 60-82 remove the institute, students, and teachers overview cards from the rendered page, so this route now lands as a title plus an empty content area. If the replacement UI is not ready, keep the existing section or gate the new behavior behind a flag.

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

In `@src/app/`(protected)/(root)/(dashboard)/page.tsx around lines 60 - 82, The
dashboard page currently has its main content commented out, removing
InstituteOverview, StudentsOverview, and TeachersOverview and leaving only the
title; restore those components (InstituteOverview, StudentsOverview,
TeachersOverview) so they consume dashboardData (dashboardData.institute,
dashboardData.students, dashboardData.teachers) and render normally, or if the
new UI isn't ready wrap the new layout behind a feature flag and render the
existing components when the flag is disabled; ensure props match the existing
prop names (instituteCode, address, contact, email, totalStudents,
activeCourses, averageAttendance, performanceIndex, totalTeachers, departments,
activeClasses, satisfactionRate) and remove the comment block so the components
appear on the page.
src/app/(auth)/_components/GoogleLoginButton.tsx (1)

21-43: ⚠️ Potential issue | 🟠 Major

Remove auth payload logging from the client flow.

Line 23 logs the Google OAuth response, which includes the access token, and Line 43 logs the backend auth response. Both are sensitive for a production login path and should not be written to the browser console.

🔒 Suggested fix
     onSuccess: async (credentialResponse) => {
       setIsLoading(true);
-      console.log("Google login successful:", credentialResponse);
       try {
         const res = await axios.post(
           "/api/google/auth",
@@
         toast.success("Login success", {
           description: res.data.message,
         });
-
-        console.log(res.data);
 
         if (res.status === 201) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(auth)/_components/GoogleLoginButton.tsx around lines 21 - 43, The
client flow currently logs sensitive auth data: remove the console.log("Google
login successful:", credentialResponse) inside the onSuccess handler and the
console.log(res.data) after the axios.post call; instead, if you need a debug
hint use a non-sensitive message (e.g., "Google login succeeded" or rely on
toast.success) and perform any detailed logging server-side. Locate these logs
in the onSuccess callback (references: onSuccess, credentialResponse,
axios.post, res.data, toast.success) and delete or replace the console
statements so access_token and backend auth payloads are never written to the
browser console.
src/app/StoreProvider.tsx (1)

17-22: ⚠️ Potential issue | 🟠 Major

Keep the Redux user state synced when the user prop changes.

This only dispatches userData(user) on the first render. If the server passes a different admin after hydration or a later navigation, the store keeps the stale value.

🧭 Suggested fix
-import { useRef } from "react";
+import { useEffect, useRef } from "react";
@@
   const storeRef = useRef<AppStore | null>(null);
   if (storeRef.current === null) {
-    // Create the store instance the first time this renders
     storeRef.current = makeStore();
-    storeRef.current.dispatch(userData(user));
   }
+
+  useEffect(() => {
+    storeRef.current?.dispatch(userData(user));
+  }, [user]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/StoreProvider.tsx` around lines 17 - 22, The store is only seeded
once so changes to the incoming user prop aren't propagated; after creating
storeRef.current via makeStore() and dispatching userData(user) on initial
render, also add an effect that watches the user prop and dispatches
storeRef.current.dispatch(userData(user)) whenever user changes (guarding that
storeRef.current is non-null). This keeps the Redux user state in sync with the
user prop while preserving the initial creation logic that uses makeStore() and
userData.
🟠 Major comments (20)
src/components/ui/dropdown-menu.tsx-50-50 (1)

50-50: ⚠️ Potential issue | 🟠 Major

Switch these Radix CSS-variable classes to Tailwind v4 syntax.

origin-[--radix-dropdown-menu-content-transform-origin] and max-h-[var(--radix-dropdown-menu-content-available-height)] are v3-style arbitrary values. In Tailwind v4, these utilities need the parenthesized CSS-variable form, otherwise the max-height/origin styles won't be generated.

♻️ Proposed fix
-      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
+      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)",
-        "z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
-        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin]",
+        "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
+        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-dropdown-menu-content-transform-origin)",

Also applies to: 68-69

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

In `@src/components/ui/dropdown-menu.tsx` at line 50, In the DropdownMenu content
class string (in src/components/ui/dropdown-menu.tsx, e.g., the
DropdownMenuContent/DropdownMenu component), replace v3-style arbitrary
utilities using raw CSS variables with Tailwind v4 parenthesized form: change
origin-[--radix-dropdown-menu-content-transform-origin] to
origin-[var(--radix-dropdown-menu-content-transform-origin)] and any
max-h-[var(--radix-dropdown-menu-content-available-height)] usages to
max-h-[var(--radix-dropdown-menu-content-available-height)] (update all
occurrences, including the other instances referenced in the comment).
src/app/api/google/auth/route.ts-18-23 (1)

18-23: ⚠️ Potential issue | 🟠 Major

Add secure flag to cookie for production.

The cookie is missing the secure flag, which means it could be transmitted over insecure HTTP connections. For authentication tokens, this should be true in production environments.

🔒 Proposed fix
    res.cookies.set("leadlly.in_admin_token", token, {
      httpOnly: true,
+     secure: process.env.NODE_ENV === "production",
      path: "/",
      sameSite: "strict",
      expires: new Date("9999-12-31T23:59:59Z"),
    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/google/auth/route.ts` around lines 18 - 23, The cookie set in
res.cookies.set("leadlly.in_admin_token", ...) lacks the secure flag; update the
res.cookies.set call in src/app/api/google/auth/route.ts to include secure: true
when running in production (e.g., process.env.NODE_ENV === "production" or an
isProduction env var) so the token is only sent over HTTPS in prod, keeping the
existing httpOnly, path, sameSite, and expires settings intact.
src/app/api/google/auth/route.ts-10-10 (1)

10-10: ⚠️ Potential issue | 🟠 Major

Remove debug logging of request body.

Logging the full request body may expose sensitive authentication data (tokens, user credentials) in production logs. This is a compliance/privacy risk.

🐛 Proposed fix
-    console.log(body, "here is the body");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/google/auth/route.ts` at line 10, Remove the debug console.log
that prints the request body to avoid leaking sensitive auth data: locate the
console.log(body, "here is the body") call in src/app/api/google/auth/route.ts
(inside the route handler that processes the auth request), delete that
statement, and if you need observability replace it with a non-sensitive log
(e.g., log only safe metadata like request id or status) or use a
conditional/debug-only logger that never runs in production.
src/proxy.ts-26-32 (1)

26-32: ⚠️ Potential issue | 🟠 Major

Missing null check before accessing user.admin.institutes[0].

If getUser() fails or returns unexpected data, accessing user.admin.institutes[0] will throw. Add defensive checks.

🛡️ Proposed fix
   if (token && isPublicPath) {
-    const user = await getUser();
-
-    return NextResponse.redirect(
-      new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
-    );
+    try {
+      const user = await getUser();
+      if (user?.admin?.institutes?.length > 0) {
+        return NextResponse.redirect(
+          new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
+        );
+      }
+      return NextResponse.redirect(new URL("/create-institute", request.url));
+    } catch {
+      // If user fetch fails, allow access to public path
+      return NextResponse.next();
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/proxy.ts` around lines 26 - 32, The code redirects to
`/institute/${user.admin.institutes[0]}` without verifying getUser() returned a
valid object and that admin/institutes exist and contain an entry; update the
block handling token && isPublicPath to validate the returned value from
getUser() (check user is non-null, user.admin exists, user.admin.institutes is
an array and has length > 0 and that institutes[0] is defined) before calling
NextResponse.redirect, and if any check fails either skip the redirect or choose
a safe fallback (e.g., redirect to a default page or return
NextResponse.next()); reference the token/isPublicPath condition, getUser(), and
the NextResponse.redirect call when making the change.
src/actions/mentor_actions.ts-40-50 (1)

40-50: ⚠️ Potential issue | 🟠 Major

Confirm API base URL inconsistency and check for missing admin header.

The inconsistency is confirmed: addTeacherToInstitute uses NEXT_PUBLIC_ADMIN_API_BASE_URL while getInstituteStudents uses NEXT_PUBLIC_API_URL. The same pattern exists in student_action.ts, suggesting intentional separation of admin vs. public endpoints.

However, compare the add operations across both files: student_action.ts includes isAdmin: "true" header in its add request, but mentor_actions.ts does not. Verify whether this header is required and if mentor_actions.ts is missing it unintentionally.

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

In `@src/actions/mentor_actions.ts` around lines 40 - 50, The POST in
mentor_actions.ts uses NEXT_PUBLIC_ADMIN_API_BASE_URL for /api/student/add but
omits the isAdmin header that student_action.ts includes; verify whether this
endpoint expects the admin flag and either (a) add headers: { "Content-Type":
"application/json", "Cookie": `token=${token}`, "isAdmin": "true" } to the fetch
call shown, or (b) if the endpoint should be the public API, change to
NEXT_PUBLIC_API_URL to match getInstituteStudents — update the fetch invocation
that constructs the URL and headers (using
process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL, the POST to
/api/student/add/${instituteId}, and the Cookie token) accordingly so the admin
header is consistent with student_action.ts.
src/app/(protected)/create-institute/page.tsx-15-16 (1)

15-16: ⚠️ Potential issue | 🟠 Major

Don’t vertically center a form page.

Line 15 uses items-center on a min-h-screen wrapper. Once CreateInstituteForm grows beyond the viewport—mobile, validation errors, browser zoom—the top of the card can become unreachable.

💡 Proposed fix
-    <div className="container mx-auto min-h-screen px-4 py-8 max-w-2xl flex items-center justify-center w-full">
+    <div className="container mx-auto min-h-screen px-4 py-8 max-w-2xl flex items-start justify-center w-full">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/create-institute/page.tsx around lines 15 - 16, The
wrapper div currently uses min-h-screen with items-center which vertically
centers the Card and can make the top unreachable when the form grows; update
the container's layout by removing or replacing items-center (e.g., use
items-start or omit vertical alignment) and keep sufficient top/bottom padding
(px-4 py-8) so the Card and the CreateInstituteForm can scroll normally; locate
the outer div with className="container mx-auto min-h-screen px-4 py-8 max-w-2xl
flex items-center justify-center w-full" and the Card component wrapping
CreateInstituteForm and change the flex alignment accordingly.
src/app/provider.tsx-12-14 (1)

12-14: ⚠️ Potential issue | 🟠 Major

Handle getUser() failures before rendering the provider.

Line 12 is unguarded. If the lookup throws during SSR, the whole tree fails instead of degrading to user={null}.

💡 Proposed fix
-  const userData = await getUser();
+  const userData = await getUser().catch(() => null);
 
-  return <StoreProvider user={userData?.admin}>{children}</StoreProvider>;
+  return <StoreProvider user={userData?.admin ?? null}>{children}</StoreProvider>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/provider.tsx` around lines 12 - 14, The getUser() call can throw
during SSR causing the whole render to fail; wrap the call to getUser() in a
try/catch inside the async provider component (the place that assigns userData)
and on error (or if it returns undefined) set userData to null before rendering;
then pass user={userData?.admin ?? null} into StoreProvider (the JSX that
returns <StoreProvider user={...}>{children}</StoreProvider>) so failures
degrade to no user instead of crashing.
src/app/api/auth/verify/route.ts-15-20 (1)

15-20: ⚠️ Potential issue | 🟠 Major

Add secure flag to the admin auth cookie to prevent transmission over HTTP.

Lines 15-20 set the admin token cookie without the secure flag, which allows browsers to transmit it over unencrypted HTTP connections outside of development environments. This weakens the security boundary for a sensitive authentication token.

💡 Proposed fix
     res.cookies.set("leadlly.in_admin_token", token, {
       httpOnly: true,
+      secure: process.env.NODE_ENV === "production",
       path: "/",
       sameSite: "strict",
       expires: new Date("9999-12-31T23:59:59Z"),
     });

Note: This same issue exists in src/app/api/auth/login/route.ts, src/app/api/auth/logout/route.ts, and src/app/api/google/auth/route.ts—apply the same fix to all admin token cookie configurations.

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

In `@src/app/api/auth/verify/route.ts` around lines 15 - 20, The admin auth cookie
set via res.cookies.set("leadlly.in_admin_token", token, { ... }) is missing the
secure flag; update the cookie options in the res.cookies.set calls (the ones
that set "leadlly.in_admin_token" in verify/login/logout/google auth routes) to
include secure: true in production (e.g., secure: process.env.NODE_ENV !==
'development' or secure: true behind your TLS-aware environment) so the cookie
is sent only over HTTPS, keeping all other existing options (httpOnly, path,
sameSite, expires) intact.
src/app/(protected)/(root)/institute/[instituteId]/_components/StudentsOverview.tsx-10-10 (1)

10-10: ⚠️ Potential issue | 🟠 Major

Do not make instituteId optional if the link depends on it.

Line 111 will render /institute/undefined/batches whenever this prop is missing, so the CTA silently becomes a broken navigation target. Make instituteId required or hide/disable the link until it exists.

🧭 Suggested fix
 interface StudentsOverviewProps {
   totalStudents: number;
   activeCourses: number;
   averageAttendance: number;
   performanceIndex: number;
-  instituteId?: string;
+  instituteId: string;
 }

Also applies to: 111-111

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

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/StudentsOverview.tsx
at line 10, The StudentsOverview component currently declares instituteId?:
string which allows rendering a link to `/institute/undefined/batches`; change
the prop to required (in the component props/type: instituteId: string) or,
alternatively, guard the link rendering in StudentsOverview (where the CTA is
created—the anchor/Link that builds `/institute/${instituteId}/batches`) to only
render or enable the link when instituteId is a non-empty string, e.g.,
hide/disable the CTA and provide fallback UI until instituteId exists.
src/app/api/auth/login/route.ts-16-21 (1)

16-21: ⚠️ Potential issue | 🟠 Major

Harden the auth cookie before shipping.

Lines 16-20 create a bearer-token cookie that is effectively permanent and not marked secure. That makes stale sessions linger indefinitely and weakens transport guarantees outside localhost. Prefer a session cookie or derive expiry from the token TTL, and set secure in production.

🔐 Suggested fix
     res.cookies.set("leadlly.in_admin_token", token, {
       httpOnly: true,
+      secure: process.env.NODE_ENV === "production",
       path: "/",
       sameSite: "strict",
-      expires: new Date("9999-12-31T23:59:59Z"),
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/auth/login/route.ts` around lines 16 - 21, The auth cookie set by
res.cookies.set("leadlly.in_admin_token", token, {...}) is too persistent and
not marked secure; update the login route to (1) avoid a permanent expires —
either make it a session cookie by omitting expires or derive expires/maxAge
from the token's TTL/JWT exp (use the token variable or decode its exp) and (2)
set secure: process.env.NODE_ENV === "production" (or equivalent env flag) so
the cookie is only sent over TLS in prod; keep httpOnly, path, sameSite as-is
and prefer using maxAge when deriving TTL.
src/app/(protected)/(root)/layout.tsx-15-20 (1)

15-20: ⚠️ Potential issue | 🟠 Major

Add staleTime to prevent immediate refetch on hydration.

Lines 17-20 prefetch without setting staleTime. React Query defaults to staleTime: 0, so this data becomes stale immediately. When HydrationBoundary restores it on the client, the useSuspenseQuery call in main-header.tsx will see stale data and refetch, creating the double-fetch you're trying to avoid. Add staleTime: 60 * 1000 (or match your QueryProvider defaults) to the prefetch options.

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

In `@src/app/`(protected)/(root)/layout.tsx around lines 15 - 20, The
prefetchQuery call using QueryClient (queryKey ["all_institutes"] and queryFn
getAllUserInstitutes) doesn't set staleTime so the data is immediately
considered stale and triggers a client refetch after hydration; update the
prefetchQuery options to include staleTime: 60 * 1000 (or your QueryProvider
default) so the prefetched data remains fresh across hydration and prevents the
double-fetch in useSuspenseQuery in main-header.tsx.
src/helpers/types/index.ts-588-623 (1)

588-623: ⚠️ Potential issue | 🟠 Major

Do not include credential fields on a client-shared admin type.

IAdmin now contains password and salt, and this type is already flowing into client-side provider/store code in this PR. Keeping secret fields on the shared shape makes accidental serialization to the browser much more likely.

🔐 Suggested fix
 export interface IAdmin {
   _id: string;
   firstname: string;
   lastname: string;
   email: string;
   role: "admin" | "super_admin";
   phone: string;
-  password: string;
-  salt: string;
   avatar: {
     public_id: string;
     url: string;
   };

If server-only code needs those fields, split the model into a public client DTO and a separate server-only type.

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

In `@src/helpers/types/index.ts` around lines 588 - 623, The IAdmin interface
exposes sensitive credential fields (password, salt) that are leaking into
client-side code; remove password and salt from the shared IAdmin type and
create a separate server-only interface (e.g., IAdminPrivate or IAdminServer)
that extends the public shape and includes password and salt for server use
only, then update client-side consumers (providers/stores) to depend on the
sanitized public DTO (e.g., IAdminPublic or the updated IAdmin) while server
code (auth, models) imports the server-only interface to access credentials.
src/app/(protected)/(root)/institute/[instituteId]/_components/add-students-dialog.tsx-28-30 (1)

28-30: ⚠️ Potential issue | 🟠 Major

Validate each comma-separated email in the schema.

This currently accepts any non-empty string, so values like foo, bar pass client-side and fail later. Validate the split list up front so the form error is immediate and specific.

✅ Suggested fix
+const parseEmails = (value: string) =>
+  value
+    .split(",")
+    .map((email) => email.trim())
+    .filter(Boolean);
+
 const AddStudentsFormSchema = z.object({
-  emails: z.string().min(1, { error: "Enter valid email addresses" }),
+  emails: z
+    .string()
+    .trim()
+    .min(1, { error: "Enter at least one email address" })
+    .refine(
+      (value) =>
+        parseEmails(value).every(
+          (email) => z.email().safeParse(email).success
+        ),
+      { error: "Enter valid comma-separated email addresses" }
+    ),
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/add-students-dialog.tsx
around lines 28 - 30, The emails field currently only checks non-empty string;
update AddStudentsFormSchema to validate each comma-separated address by
transforming the input into an array (split on commas, trim each entry, filter
out empty strings) and then validate that the resulting array has at least one
item and every entry is a valid email (use z's email validator or a regex) with
a clear error message; update any form handlers that expect a string to accept
the transformed array (or derive the array after validation) so client-side
validation rejects inputs like "foo, bar" immediately.
src/app/(protected)/create-institute/_components/create-institute-form.tsx-169-379 (1)

169-379: ⚠️ Potential issue | 🟠 Major

Fix the label/control associations across this form.

Most FieldLabel elements still point to htmlFor="name" while the actual controls don't expose matching ids. That breaks label click/focus behavior and screen-reader association for nearly every field in this section.

♿ Suggested fix pattern
 <Controller
   control={form.control}
   name="email"
   render={({ field, fieldState }) => (
     <Field data-invalid={fieldState.invalid} className="gap-1.5">
-      <FieldLabel htmlFor="name">Email</FieldLabel>
+      <FieldLabel htmlFor="email">Email</FieldLabel>
       <Input
+        id="email"
         {...field}
         placeholder="Enter institute email"
         required
         aria-invalid={fieldState.invalid}
         className="shadow-none"

Apply the same pattern to each field so every htmlFor matches a unique control id.

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

In `@src/app/`(protected)/create-institute/_components/create-institute-form.tsx
around lines 169 - 379, Many FieldLabel elements use htmlFor="name" but the
corresponding inputs/selects lack matching id attributes, breaking label-control
association; update each Controller render (e.g., ones with name="name",
"email", "contactNumber", "website", "address1", "address2", "city", "state",
"pincode", "country") so the rendered control (Input or Select) has an id that
matches the FieldLabel htmlFor (use a unique id per field, e.g.,
id={`${field.name}`}), and ensure Select uses the same id on its
SelectTrigger/SelectValue as appropriate; also keep FieldLabel htmlFor values
matched to those ids to restore click/focus and screen-reader associations.
src/app/(protected)/(root)/institute/[instituteId]/page.tsx-41-54 (1)

41-54: ⚠️ Potential issue | 🟠 Major

Replace the mock metrics before shipping this page.

These values are rendered for every institute, so the dashboard always shows the same student and teacher counts regardless of the selected institute. Wire the cards to real stats or hide them until the API exists.

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

In `@src/app/`(protected)/(root)/institute/[instituteId]/page.tsx around lines 41
- 54, The page currently uses a hard-coded dashboardData object
(students/teachers) so every institute shows identical metrics; replace that
static dashboardData with a real data flow by calling an async data function
(e.g., getInstituteDashboardStats(instituteId) or
fetch(`/api/institutes/${instituteId}/stats`)) inside the page component (or a
server-side loader) and map the returned students and teachers fields into the
existing cards, and if the API is not ready, hide the cards or render a
loading/skeleton state when dashboardData is null/undefined; update references
to dashboardData, students, and teachers to use the fetched stats (or a feature
flag) so metrics are institute-specific.
src/actions/institute_actions.ts-32-36 (1)

32-36: ⚠️ Potential issue | 🟠 Major

updateTag("userData") does not invalidate the institute queries.

None of the fetchers in this file use the userData tag. The getActiveInstitute and getAllUserInstitutes functions have no cache tags defined and will not be invalidated by this call. If React Query is used for these queries, the invalidation layers are separate and won't communicate.

Expected: Define cache tags on the fetchers that read institute data, or use a tag name that actually matches the queries being invalidated.

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

In `@src/actions/institute_actions.ts` around lines 32 - 36, The call to
updateTag("userData") does not invalidate institute queries because
getActiveInstitute and getAllUserInstitutes don't use that tag; either change
the tag passed to updateTag to the actual tag those fetchers use (e.g.,
"institute" or whatever cache key those fetchers register) or add the
appropriate cache tag definitions to the institute fetchers (getActiveInstitute
and getAllUserInstitutes) so they include the same tag name; update the call
site (updateTag) or the fetchers’ tag registration so the tag names match and
invalidation will trigger correctly.
src/app/(protected)/(root)/_components/main-header.tsx-71-78 (1)

71-78: ⚠️ Potential issue | 🟠 Major

Use onSelect instead of onClick for institute navigation.

DropdownMenuItem selection is Radix's cross-input activation signal—it fires for both pointer and keyboard interactions (Enter/Space on focused items). Using onClick bypasses keyboard activation, leaving users unable to switch institutes via keyboard navigation.

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

In `@src/app/`(protected)/(root)/_components/main-header.tsx around lines 71 - 78,
The DropdownMenuItem currently uses an onClick handler for navigation which
misses Radix's cross-input activation (keyboard) and prevents keyboard users
from selecting institutes; replace the onClick prop on DropdownMenuItem with an
onSelect handler that calls router.replace(`/institute/${institute._id}`)
(preserving the same key, className and activeInstitute check) so selection
works for both pointer and keyboard interactions.
src/components/ui/combobox.tsx-74-82 (1)

74-82: ⚠️ Potential issue | 🟠 Major

Render the InputGroupButton through the trigger's render prop instead of nesting.

The ComboboxTrigger renders a <button> by default, so the current code creates a nested button structure (<button><button>), which is invalid HTML and breaks click/focus behavior. Use the render prop to compose the custom button element.

Suggested fix
         {showTrigger && (
-          <InputGroupButton
-            size="icon-xs"
-            variant="ghost"
-            data-slot="input-group-button"
-            className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
-            disabled={disabled}
-          >
-            <ComboboxTrigger />
-          </InputGroupButton>
+          <ComboboxTrigger
+            disabled={disabled}
+            render={
+              <InputGroupButton
+                size="icon-xs"
+                variant="ghost"
+                data-slot="input-group-button"
+                className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent"
+                disabled={disabled}
+              />
+            }
+          />
         )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/combobox.tsx` around lines 74 - 82, The InputGroupButton is
being nested with ComboboxTrigger which causes a <button><button> invalid
structure; replace the nested ComboboxTrigger by using the trigger component's
render prop so InputGroupButton becomes the rendered trigger element. Locate the
InputGroupButton/ComboboxTrigger block and remove the inner <ComboboxTrigger />;
instead call the trigger's render prop (e.g., ComboboxTrigger render or similar
API) to pass trigger props/handlers into InputGroupButton so it receives the
trigger's aria/onclick/focus props and retains size/variant/disabled/className.
src/app/(protected)/(root)/institute/[instituteId]/page.tsx-26-31 (1)

26-31: ⚠️ Potential issue | 🟠 Major

Wrap the prefetched query in a HydrationBoundary to avoid duplicate fetches.

prefetchQuery() in a server component alone doesn't make the cached data available to client-side useQuery() hooks—they have their own QueryClient instance. The prefetched data is discarded, and InstituteOverview fetches again on the client, doubling your network requests. You must serialize the server cache with dehydrate(queryClient) and pass it through <HydrationBoundary> for the client to reuse it.

Suggested fix
import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query";

const queryClient = new QueryClient();

await queryClient.prefetchQuery({
  queryKey: ["active_institute", instituteId],
  queryFn: () => getActiveInstitute({ instituteId }),
});

return (
  <HydrationBoundary state={dehydrate(queryClient)}>
    {/* existing page JSX */}
  </HydrationBoundary>
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/institute/[instituteId]/page.tsx around lines 26
- 31, The server-side QueryClient created as QueryClient() and used with
queryClient.prefetchQuery(...) isn't being serialized to the client, so wrap
your returned JSX in a HydrationBoundary and pass state={dehydrate(queryClient)}
(import HydrationBoundary and dehydrate from `@tanstack/react-query`) so the
client-side useQuery() in InstituteOverview can reuse the prefetched cache
instead of refetching; ensure you add the imports for HydrationBoundary and
dehydrate and wrap the existing page JSX with <HydrationBoundary
state={dehydrate(queryClient)}>...</HydrationBoundary>.
src/actions/institute_actions.ts-80-95 (1)

80-95: ⚠️ Potential issue | 🟠 Major

Check both res.ok and responseData.success before returning.

fetch does not throw on HTTP 4xx/5xx status codes—it only rejects on transport failures. Currently, if the API returns 401/404/500 or success: false, the functions return the error response as if successful. Since these functions are used as TanStack Query queryFns (in main-header.tsx, layout.tsx, InstituteOverview.tsx), the query enters the success state instead of the error state when no exception is thrown. Additionally, callers like select-institute/page.tsx dereference data.institutes directly without checking data.success, which would cause runtime errors on API failures.

Suggested fix
     const responseData: { success: boolean; institute: IInstitute } =
       await res.json();
+    if (!res.ok || !responseData.success) {
+      throw new Error("Failed to fetch institute");
+    }
     return responseData;
     const responseData: {
       success: boolean;
       institutes: IInstitute[];
       count: number;
     } = await res.json();
+    if (!res.ok || !responseData.success) {
+      throw new Error("Failed to fetch institutes");
+    }
     return responseData;

Also applies to: 109-127

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

In `@src/actions/institute_actions.ts` around lines 80 - 95, The fetch block that
builds `res` and parses `responseData` must validate both the HTTP status and
the API payload before returning: after `const res = await fetch(...)` check `if
(!res.ok)` and throw an Error (including `res.status` and statusText or parsed
body) so TanStack Query treats it as a failure; after `const responseData =
await res.json()` also check `if (!responseData.success)` and throw an Error
(include `responseData` or a clear message). Apply this change for the institute
fetch shown (use the existing variables `res`, `responseData`, `instituteId`,
`token`) and replicate the same pattern for the other similar fetch in the file
(the block referenced at lines 109-127) so callers never receive a falsy payload
as success.
🟡 Minor comments (12)
src/app/api/auth/logout/route.ts-17-22 (1)

17-22: ⚠️ Potential issue | 🟡 Minor

Add secure flag for production cookie security.

The cookie should include secure: true in production to ensure it's only transmitted over HTTPS, preventing potential session hijacking over insecure connections.

🔒 Proposed fix
     res.cookies.set("leadlly.in_admin_token", "", {
       httpOnly: true,
       path: "/",
       sameSite: "strict",
+      secure: process.env.NODE_ENV === "production",
       expires: new Date(0),
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/auth/logout/route.ts` around lines 17 - 22, The logout cookie
reset call using res.cookies.set("leadlly.in_admin_token", ...) must include the
secure flag in production; update the options object passed to res.cookies.set
(in route.ts) to add secure: process.env.NODE_ENV === "production" (or an
equivalent runtime env check) so the cookie is only sent over HTTPS in
production while remaining usable in dev.
src/components/ui/skeleton.tsx-1-6 (1)

1-6: ⚠️ Potential issue | 🟡 Minor

Add missing React import for type reference.

The component uses React.HTMLAttributes<HTMLDivElement> but doesn't import React. All other UI components in this project follow the pattern of explicitly importing React for type references.

Proposed fix
+import * as React from "react"
+
 import { cn } from "@/lib/utils"
 
 function Skeleton({

Alternatively, import only the type:

+import type { HTMLAttributes } from "react"
+
 import { cn } from "@/lib/utils"
 
 function Skeleton({
   className,
   ...props
-}: React.HTMLAttributes<HTMLDivElement>) {
+}: HTMLAttributes<HTMLDivElement>) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/skeleton.tsx` around lines 1 - 6, The Skeleton component
uses the type React.HTMLAttributes<HTMLDivElement> but never imports React; add
an explicit import for React at the top of the file (e.g., import React from
"react" or import type React from "react" per project conventions) so the type
reference in the Skeleton function signature resolves correctly.
src/app/api/google/auth/route.ts-36-36 (1)

36-36: ⚠️ Potential issue | 🟡 Minor

Handle potentially undefined status.

If error.response is undefined (e.g., network error), error.response?.status will be undefined, and NextResponse will default to 200. Provide an explicit fallback.

🐛 Proposed fix
-      { status: error instanceof AxiosError ? error.response?.status : 500 }
+      { status: error instanceof AxiosError ? (error.response?.status ?? 500) : 500 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/google/auth/route.ts` at line 36, The status value passed to
NextResponse can be undefined when AxiosError.response is undefined (e.g.,
network errors); update the construction that currently uses error instanceof
AxiosError ? error.response?.status : 500 to provide an explicit fallback (for
example use the nullish coalescing fallback) so the status is always a valid
HTTP status (e.g., default to 500); locate the NextResponse creation in
src/app/api/google/auth/route.ts where error handling uses AxiosError and
replace the conditional to guarantee a numeric status.
src/components/ui/tooltip.tsx-22-25 (1)

22-25: ⚠️ Potential issue | 🟡 Minor

Update CSS variable syntax for Tailwind v4.

The origin-[--radix-tooltip-content-transform-origin] uses bracket syntax for CSS variables, which changed in Tailwind v4. CSS variables now require parentheses instead of brackets.

✏️ Proposed fix
       className={cn(
-        "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-tooltip-content-transform-origin]",
+        "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-tooltip-content-transform-origin)",
         className
       )}

As per the library documentation for Tailwind v4: "CSS Variables - Brackets → Parentheses: OLD: bg-[--brand-color] NEW: bg-(--brand-color). All arbitrary values using CSS variables must use parentheses."

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

In `@src/components/ui/tooltip.tsx` around lines 22 - 25, The Tailwind v4 syntax
for CSS variables is wrong in the tooltip's className: replace the
arbitrary-bracket usage origin-[--radix-tooltip-content-transform-origin] with
the v4 parenthesis form origin-(--radix-tooltip-content-transform-origin) inside
the cn(...) call in the Tooltip component so the CSS variable is applied
correctly; update the string passed to className (the one containing
"origin-[--radix-tooltip-content-transform-origin]") to use parentheses.
.prettierignore-4-6 (1)

4-6: ⚠️ Potential issue | 🟡 Minor

Fix typos in glob patterns.

Lines 4 and 6 have erroneous spaces that break the ignore patterns:

  • *. env should be *.env
  • .env .* should be .env.*

These patterns won't match the intended files (e.g., .env.local, .env.production).

✏️ Proposed fix
-*. env
+*.env
 .env
-.env .*
+.env.*
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.prettierignore around lines 4 - 6, The glob patterns in .prettierignore
contain typos: replace the incorrect entries "*. env" and ".env .*" with
corrected patterns "*.env" and ".env.*" so they properly match files like
.env.local and .env.production; update the .prettierignore file to remove the
stray spaces in those two lines (the file entries shown in the diff are the
locations to fix).
src/components/ui/field.tsx-194-215 (1)

194-215: ⚠️ Potential issue | 🟡 Minor

Skip rendering the alert when there are no actual error messages.

If errors is defined but every entry lacks message, this branch still returns an empty <ul>, so FieldError renders a blank role="alert". Filter the messages first and return null when none remain.

💡 Proposed fix
   const content = useMemo(() => {
     if (children) {
       return children
     }
 
-    if (!errors) {
+    const messages = errors?.flatMap((error) =>
+      error?.message ? [error.message] : []
+    ) ?? []
+
+    if (messages.length === 0) {
       return null
     }
 
-    if (errors?.length === 1 && errors[0]?.message) {
-      return errors[0].message
+    if (messages.length === 1) {
+      return messages[0]
     }
 
     return (
       <ul className="ml-4 flex list-disc flex-col gap-1">
-        {errors.map(
-          (error, index) =>
-            error?.message && <li key={index}>{error.message}</li>
-        )}
+        {messages.map((message, index) => (
+          <li key={index}>{message}</li>
+        ))}
       </ul>
     )
   }, [children, errors])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/field.tsx` around lines 194 - 215, The current useMemo for
content returns an empty <ul> when errors exists but none have a message,
causing FieldError to render a blank alert; update the useMemo in the content
computation to first derive a filteredMessages = (errors || []).filter(e =>
e?.message) and if filteredMessages.length === 0 return null, otherwise if
filteredMessages.length === 1 return filteredMessages[0].message, else render
the <ul> by mapping filteredMessages instead of errors (keep the existing
children-shortcircuit intact).
src/app/(auth)/login/page.tsx-21-23 (1)

21-23: ⚠️ Potential issue | 🟡 Minor

Add explicit alt text to the logo image.

Line 22 renders an image without alt, which makes the header noisier for screen readers. Use alt="" if this is purely decorative, or a short brand label if it is informative.

♿ Suggested fix
           <Avatar className="mx-auto mb-6 bg-primary/10 size-12">
-            <AvatarImage src="/leadlly.jpeg" className="size-full" />
+            <AvatarImage
+              src="/leadlly.jpeg"
+              alt="Leadlly logo"
+              className="size-full"
+            />
           </Avatar>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(auth)/login/page.tsx around lines 21 - 23, The AvatarImage inside
the login page (component Avatar / AvatarImage in page.tsx) is missing an alt
attribute; update the AvatarImage element to include an explicit alt (use alt=""
if the image is purely decorative or a short brand label like "Leadlly logo" if
it conveys information) so screen readers do not announce noisy or missing-alt
warnings.
src/helpers/schema/createInstituteSchema.ts-10-10 (1)

10-10: ⚠️ Potential issue | 🟡 Minor

contactNumber currently accepts arbitrary 10-character strings.

Line 10 only checks length, so obviously invalid values can still pass while the message says “valid phone number”. Tighten this to the phone formats you actually support before persisting it.

📞 Suggested fix
-  contactNumber: z.string().min(10, "Please enter a valid phone number"),
+  contactNumber: z
+    .string()
+    .trim()
+    .regex(/^\+?[0-9\s-]{10,15}$/, "Please enter a valid phone number"),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/helpers/schema/createInstituteSchema.ts` at line 10,
createInstituteSchema's contactNumber currently only checks length and accepts
arbitrary 10-char strings; change the schema for contactNumber in
createInstituteSchema to validate against the supported phone formats (e.g.,
digits-only 10-digit, optional country code/E.164 with leading '+', or your
app's accepted patterns) using z.string().regex(...) or z.string().refine(...)
with a clear error message; update the validation pattern/error text so invalid
formats are rejected before persisting and reference the contactNumber field in
the schema for locating the change.
src/app/(protected)/create-institute/_components/create-institute-form.tsx-482-493 (1)

482-493: ⚠️ Potential issue | 🟡 Minor

Fix the Tailwind class typo on the add-subject icon.

mr-2size-4 is parsed as one invalid class, so you lose both the spacing and the icon sizing here.

🪄 Suggested fix
-<Plus className="mr-2size-4" />
+<Plus className="mr-2 size-4" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/create-institute/_components/create-institute-form.tsx
around lines 482 - 493, The Plus icon's className has a typo "mr-2size-4" which
merges two classes into one invalid token; update the class on the Plus element
inside the Button in create-institute-form.tsx (the Add "… " button within the
CreateInstituteForm component) to separate the spacing and sizing classes (e.g.,
use "mr-2" and a size class such as "size-4" or "h-4 w-4") so the margin and
icon size are applied correctly.
src/app/(protected)/(root)/institute/[instituteId]/page.tsx-90-105 (1)

90-105: ⚠️ Potential issue | 🟡 Minor

Wire up or remove the Edit action.

This renders as a primary CTA, but it has no navigation target or click handler. Right now users hit a dead end.

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

In `@src/app/`(protected)/(root)/institute/[instituteId]/page.tsx around lines 90
- 105, The "Edit" Button in page.tsx is rendered without a click handler or
navigation target (dead CTA); either wire it to the institute edit route or
remove it. Fix by updating the Button usage (the Button element containing the
SVG and "Edit" text) to perform navigation: add an onClick that uses Next.js
router.push(`/institute/${instituteId}/edit`) or replace Button with a Link
component with href to the appropriate edit path, and include an accessible
label (aria-label="Edit institute") and any necessary disabled/loading state
handling so the CTA is actionable and meaningful.
src/components/ui/input-group.tsx-60-77 (1)

60-77: ⚠️ Potential issue | 🟡 Minor

Addon focus delegation only works for plain inputs.

The click handler hard-codes querySelector("input"), so InputGroupTextarea and any future control using data-slot="input-group-control" never receive focus from the addon. Because {...props} comes after onClick, a consumer can also overwrite the built-in focus behavior by accident.

Suggested fix
 function InputGroupAddon({
   className,
   align = "inline-start",
+  onClick,
   ...props
 }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
   return (
     <div
       role="group"
       data-slot="input-group-addon"
       data-align={align}
       className={cn(inputGroupAddonVariants({ align }), className)}
+      {...props}
       onClick={(e) => {
+        onClick?.(e)
+        if (e.defaultPrevented) {
+          return
+        }
         if ((e.target as HTMLElement).closest("button")) {
           return
         }
-        e.currentTarget.parentElement?.querySelector("input")?.focus()
+        e.currentTarget.parentElement
+          ?.querySelector<HTMLElement>('[data-slot="input-group-control"]')
+          ?.focus()
       }}
-      {...props}
     />
   )
 }
src/app/(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx-16-19 (1)

16-19: ⚠️ Potential issue | 🟡 Minor

Use useSuspenseQuery or add a loading state to match the Suspense boundary.

The useQuery hook in TanStack Query v5 does not suspend, so the <Suspense> boundary wrapping this component (in page.tsx, line 110) won't catch its loading state. While the parent pre-fetches the query, if the cache isn't hydrated or the client-side query runs before hydration is complete, the component will render with undefined activeInstitute instead of showing the fallback. Either switch to useSuspenseQuery to integrate with Suspense, or add an explicit loading/error branch within the component.

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

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx
around lines 16 - 19, The component currently calls useQuery({ queryKey:
["active_institute", instituteId], queryFn: () => getActiveInstitute({
instituteId }) }) which does not suspend and can render with activeInstitute
undefined under a Suspense boundary; update the hook to use useSuspenseQuery
with the same queryKey and queryFn (so the Suspense fallback will be shown) or,
if you prefer to keep useQuery, add an explicit loading/error branch that checks
the query state (isLoading/isError/isFetched and activeInstitute) before
rendering the main UI to avoid rendering with undefined activeInstitute; locate
the call to useQuery and either swap it to useSuspenseQuery(...) or add
conditional returns using isLoading/isError and activeInstitute.

try {

const token = await getCookie("token");
const token = await getCookie();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C2 'leadlly\.in_admin_token|getCookie\(|Cookie:' src

Repository: Leadlly/leadlly.admin.web

Length of output: 9443


Fix the forwarded cookie key name to match the authentication middleware.

The code reads the admin cookie correctly via getCookie() which uses the key "leadlly.in_admin_token", but forwards it upstream as Cookie: token=... instead of Cookie: leadlly.in_admin_token=.... The backend proxy and authentication endpoints all expect the full cookie key name "leadlly.in_admin_token", not just "token". This mismatch will cause authentication failures.

The same issue occurs at lines 44-48 and throughout similar action files (batch_actions.ts, institute_actions.ts, mentor_actions.ts, user_actions.ts).

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

In `@src/actions/student_action.ts` at line 18, The forwarded cookie header uses
the wrong key name—getCookie() returns the admin token (from
"leadlly.in_admin_token") but the request header is built as "Cookie: token=..."
causing auth failures; update the code that constructs the Cookie header (where
the local variable token is used) to forward the full cookie key name
"leadlly.in_admin_token" (i.e., change occurrences like `Cookie: token=${token}`
to `Cookie: leadlly.in_admin_token=${token}`) in student_action.ts and the other
action files (batch_actions.ts, institute_actions.ts, mentor_actions.ts,
user_actions.ts) so the backend proxy/auth middleware receives the expected
cookie key; keep using the existing getCookie() call and token variable.

Comment on lines +42 to +60
const handleAddStudents = async (
data: z.infer<typeof AddStudentsFormSchema>
) => {
try {
console.log(data);

const emails = data.emails
.split(",")
.map((email) => email.trim())
.filter((email) => email);

// const result = await addStudentsToInstitute(instituteId, emails);
// if (result.success) {
// toast.success(result.message);
// } else {
// toast.error(result.message);
// }
// form.reset();
// setIsAddStudentsOpen(false);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Wire the submit handler to the server action before shipping this dialog.

Right now submit only parses the textarea and exits. No students are added, no success state is shown, and the dialog never resets/closes.

🚨 Suggested fix
   const handleAddStudents = async (
     data: z.infer<typeof AddStudentsFormSchema>
   ) => {
     try {
-      console.log(data);
-
       const emails = data.emails
         .split(",")
         .map((email) => email.trim())
         .filter((email) => email);
 
-      //   const result = await addStudentsToInstitute(instituteId, emails);
-      //   if (result.success) {
-      //     toast.success(result.message);
-      //   } else {
-      //     toast.error(result.message);
-      //   }
-      //     form.reset();
-      //   setIsAddStudentsOpen(false);
+      const result = await addStudentsToInstitute(instituteId, emails);
+      if (!result.success) {
+        toast.error(result.message);
+        return;
+      }
+
+      toast.success(result.message);
+      form.reset();
+      setIsAddStudentsOpen(false);
     } catch (error) {
       console.error("Error adding students:", error);
       toast.error("Failed to add students. Please try again.");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/add-students-dialog.tsx
around lines 42 - 60, The submit handler handleAddStudents currently only parses
emails and returns; wire it to call the server action
addStudentsToInstitute(instituteId, emails), await the result, and handle
success/error by calling toast.success(result.message) or
toast.error(result.message); on success call form.reset() and
setIsAddStudentsOpen(false). Also wrap the call in the existing try/catch so
errors use toast.error and ensure any loading state (if present) is toggled
appropriately; reference handleAddStudents, addStudentsToInstitute, form.reset,
setIsAddStudentsOpen, toast.success, and toast.error when making the changes.

Comment on lines +5 to +14
await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Server-side fetch won't forward user cookies; response not validated.

Two issues with this external logout call:

  1. Token not forwarded: credentials: "include" is a browser-context option. In a server-side fetch, cookies from the incoming request are not automatically included. The user's auth token must be manually extracted from req.cookies and forwarded to the external API, otherwise the external service cannot identify which session to invalidate.

  2. Response not checked: If the external logout fails (non-2xx status), the code still clears the local cookie and returns success, leaving the external session active.

🐛 Proposed fix
 export async function GET(req: NextRequest) {
   try {
-    await fetch(
+    const token = req.cookies.get("leadlly.in_admin_token")?.value;
+
+    const response = await fetch(
       `${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
       {
         method: "GET",
         headers: {
           "Content-Type": "application/json",
+          ...(token && { Cookie: `leadlly.in_admin_token=${token}` }),
         },
-        credentials: "include",
       }
     );
+
+    if (!response.ok) {
+      const errorData = await response.json().catch(() => ({}));
+      throw new Error(errorData.message || "Logout failed");
+    }
+
     const res = NextResponse.json({ message: "Logged Out" });
📝 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
await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
credentials: "include",
}
);
export async function GET(req: NextRequest) {
try {
const token = req.cookies.get("leadlly.in_admin_token")?.value;
const response = await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/auth/logout`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
...(token && { Cookie: `leadlly.in_admin_token=${token}` }),
},
}
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || "Logout failed");
}
const res = NextResponse.json({ message: "Logged Out" });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/auth/logout/route.ts` around lines 5 - 14, The server-side logout
fetch in src/app/api/auth/logout/route.ts is not forwarding the user's cookie
and isn't checking the external response; update the route handler that calls
fetch(...) so it reads the incoming request's cookie (e.g., from the Request
object via req.headers.get("cookie") or req.cookies) and forwards that token by
adding a Cookie or Authorization header to the external fetch options (replace
credentials:"include" with explicit headers), then inspect the fetch response
(use response.ok / response.status) and only clear the local auth cookie and
return success when the external logout succeeds; if the external call fails,
return an error status (and do not clear the local cookie) and include the
external response details in the error handling.

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

import { getUser } from "./actions/user_actions";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify getUser is a server action
head -5 src/actions/user_actions.ts

Repository: Leadlly/leadlly.admin.web

Length of output: 170


🏁 Script executed:

cat -n src/proxy.ts

Repository: Leadlly/leadlly.admin.web

Length of output: 2109


Server actions cannot be called from Next.js middleware.

getUser() is a server action (from a file with "use server"), but middleware runs in the Edge runtime before the request reaches the server. This will fail at runtime.

Additionally, line 30 accesses user.admin.institutes[0] without null checks on user — if getUser() returns null, this will crash.

Consider:

  1. Making a direct fetch call to your auth endpoint from middleware
  2. Or validating the token using a lightweight JWT verification without calling server actions

Also applies to: 27-27, 35-35

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

In `@src/proxy.ts` at line 4, Middleware calls the server action getUser (from
"use server") which will fail in the Edge runtime and also accesses
user.admin.institutes[0] without null checks; replace the getUser() invocation
inside the middleware with a direct fetch to your auth endpoint (or perform a
lightweight JWT verify) to obtain user info, and add defensive checks before
accessing nested properties (e.g., confirm user and user.admin exist and that
user.admin.institutes is an array with length > 0) so you never read
user.admin.institutes[0] when user is null/undefined; update the middleware
function where getUser is referenced to use the fetch/JWT flow and guard all
downstream uses of user (including the code path that reads
user.admin.institutes[0]).


import { getUser } from "./actions/user_actions";

export async function proxy(request: NextRequest) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's a middleware.ts file that imports this proxy
fd -e ts -e js 'middleware' --exec cat {}

Repository: Leadlly/leadlly.admin.web

Length of output: 51


🏁 Script executed:

# Check if this is a Next.js project
cat package.json | grep -A 5 '"next"'

Repository: Leadlly/leadlly.admin.web

Length of output: 251


🏁 Script executed:

# Read the proxy.ts file to understand its purpose
cat -n src/proxy.ts

Repository: Leadlly/leadlly.admin.web

Length of output: 2109


🏁 Script executed:

# Search for imports or usages of the proxy function
rg "proxy" --type ts --type js -B 2 -A 2

Repository: Leadlly/leadlly.admin.web

Length of output: 346


🏁 Script executed:

# Check for middleware.ts at project root
ls -la middleware.ts 2>/dev/null || echo "Not found at root"

Repository: Leadlly/leadlly.admin.web

Length of output: 86


🏁 Script executed:

# Check the project structure around src/
ls -la src/ | head -20

Repository: Leadlly/leadlly.admin.web

Length of output: 631


Rename function to middleware and file to middleware.ts per Next.js convention.

Next.js middleware must export a function named middleware from a file named middleware.ts. The current file (src/proxy.ts) and function name (proxy) won't be recognized by Next.js, preventing the authentication logic from executing.

🔧 Proposed fix
  1. Rename file from src/proxy.ts to src/middleware.ts
  2. Rename function from proxy to middleware:
-export async function proxy(request: NextRequest) {
+export async function middleware(request: NextRequest) {
📝 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
export async function proxy(request: NextRequest) {
export async function middleware(request: NextRequest) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/proxy.ts` at line 6, The exported function named proxy must be renamed to
middleware and the source file renamed to middleware.ts so Next.js will load it
as middleware; update the export signature from export async function
proxy(request: NextRequest) to export async function middleware(request:
NextRequest) and move/rename the file accordingly, ensuring any internal
references or imports that called proxy are updated to middleware.

Comment on lines +594 to +595
phone: string;
password: string;
Copy link

Choose a reason for hiding this comment

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

Security Issue - Sensitive Credentials in Frontend Type Definition: The IAdmin interface includes password and salt fields in a shared types file that's likely imported across both frontend and backend code. This is a critical security vulnerability because:

  1. Password/Salt Should Never Reach Frontend: These fields should NEVER be included in any data sent to the frontend or stored in client-side state (Redux, React state, etc.)
  2. Type Definition Encourages Insecure Usage: Having these fields in the type definition suggests they might be used in frontend code, which would expose user credentials
  3. Related to Previous Issues: This connects to Comments Implemented a fully responsive Admin Dashboard with Batches and Students Page #12-#17 where isAdmin headers are being sent from the client - if admin objects with passwords are being passed to frontend components, this is a severe security breach
    Attack Scenario:
  • If any API endpoint returns admin data matching this interface to the frontend
  • Or if admin data is stored in Redux state (as suggested by the instituteSlice changes in this PR)
  • Attackers can extract password hashes and salts from browser DevTools, network requests, or Redux state
  • These can be used for offline password cracking attacks

Confidence: 5/5

Recommended Action

Create separate type definitions for frontend and backend:

  1. Backend-only type (for database/API operations):
// In a backend-only file (e.g., src/types/backend.ts)
export interface IAdminDB {
firstname: string;
lastname: string;
email: string;
role: "admin" | "super_admin";
phone: string;
password: string;  // Only in backend
salt: string;      // Only in backend
avatar: {
public_id: string;
url: string;
};
permissions: {
manageUsers: boolean;
manageBatches: boolean;
manageClasses: boolean;
manageTeachers: boolean;
manageStudents: boolean;
managePayments: boolean;
manageReports: boolean;
manageSettings: boolean;
manageAdmins: boolean;
};
institutes: string[];
lastLogin: string | null;
status: "Active" | "Inactive" | "Suspended";
activityLog: Array<{
action: string;
timestamp: string;
details?: string;
ipAddress?: string;
}>;
createdAt: string;
updatedAt: string;
}
  1. Frontend-safe type (for UI/Redux):
// In this file (src/helpers/types/index.ts)
export interface IAdmin {
firstname: string;
lastname: string;
email: string;
role: "admin" | "super_admin";
phone: string;
// password and salt REMOVED - never send to frontend
avatar: {
public_id: string;
url: string;
};
permissions: {
manageUsers: boolean;
manageBatches: boolean;
manageClasses: boolean;
manageTeachers: boolean;
manageStudents: boolean;
managePayments: boolean;
manageReports: boolean;
manageSettings: boolean;
manageAdmins: boolean;
};
institutes: string[];
lastLogin: string | null;
status: "Active" | "Inactive" | "Suspended";
createdAt: string;
updatedAt: string;
}

Critical verification needed:

  1. Review ALL API endpoints that return admin data to ensure they NEVER include password or salt fields
  2. Check Redux slices and React components to ensure admin credentials are never stored in frontend state
  3. Implement backend data sanitization to strip sensitive fields before sending to frontend
  4. Add TypeScript utility types to enforce this separation (e.g., Omit<IAdminDB, 'password' | 'salt'>)
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/helpers/types/index.ts at lines 594-595, the IAdmin interface includes password
and salt fields in a shared types file that's used across the application. This is a
critical security vulnerability because these sensitive credential fields should NEVER
be sent to the frontend or stored in client-side state. Remove the password and salt
fields from the IAdmin interface in this file. Create a separate backend-only type
definition (e.g., IAdminDB in src/types/backend.ts) that includes these fields for
database operations only. Review ALL API endpoints that return admin data to ensure
they never include password or salt fields in responses. Check Redux slices and React
components to ensure admin credentials are never stored in frontend state. Implement
backend data sanitization to strip sensitive fields before sending to frontend.

📍 This suggestion applies to lines 594-595


try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/institute/${instituteId}`,
Copy link

Choose a reason for hiding this comment

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

Security Issue - Backend API URL Exposure in Server Action: Using NEXT_PUBLIC_ADMIN_API_BASE_URL exposes the internal admin API base URL to all client-side code. Environment variables prefixed with NEXT_PUBLIC_* are embedded in the client-side JavaScript bundle and visible to anyone inspecting browser DevTools or network requests.
Why this matters for server actions: While this is a server action (runs on the server), Next.js bundles server actions into the client-side JavaScript to enable client-side invocation. This means the NEXT_PUBLIC_* variable and the full API endpoint structure become visible in the client bundle.
Security Risks:

  • Attackers can discover the backend admin API endpoint structure
  • If the backend API is meant to be internal/private, this exposure defeats that security boundary
  • Attackers can attempt direct attacks against the exposed backend API endpoints
  • The full API base URL becomes public knowledge, making targeted attacks easier
    This same issue affects:
  • Line 81 in the new getActiveInstitute function
  • Line 110 in the existing getAllUserInstitutes function (not shown in this patch but likely present)
  • Other functions in this file that use the same pattern

Confidence: 5/5

Recommended Action

Use a server-only environment variable instead:
Since this is a Next.js server action, use a server-only environment variable (without the NEXT_PUBLIC_ prefix):

const res = await fetch(
`${process.env.ADMIN_API_BASE_URL}/api/institute/${instituteId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Cookie: `token=${token}`,
isAdmin: "true",
},
credentials: "include",
}
);

Then in your .env file:

ADMIN_API_BASE_URL=https://internal-admin-api.example.com

Benefits:

  • Backend API URL remains server-side only
  • Not exposed in client-side JavaScript bundle
  • Reduces attack surface by hiding internal API structure
  • Follows security best practice of not exposing internal endpoints
    Apply this fix to ALL functions in this file that use NEXT_PUBLIC_ADMIN_API_BASE_URL:
  • createInstitute (line 25)
  • getMyInstitute (line 52)
  • getActiveInstitute (line 81) - this patch
  • getAllUserInstitutes (line 110) - this patch
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/institute_actions.ts at line 81 (and other fetch calls in this file),
the code uses process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL which exposes the internal
admin API base URL to the client-side JavaScript bundle. Even though these are server
actions, Next.js bundles them for client-side invocation, making NEXT_PUBLIC_*
variables visible in the browser. Replace all instances of
process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL with process.env.ADMIN_API_BASE_URL
(without the NEXT_PUBLIC_ prefix) throughout this file. Update your .env file to
define ADMIN_API_BASE_URL with the backend API URL. This prevents exposing internal
API endpoints to the client-side JavaScript bundle and reduces the attack surface.

headers: {
"Content-Type": "application/json",
Cookie: `token=${token}`,
isAdmin: "true",
Copy link

Choose a reason for hiding this comment

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

Same critical security issue as Comments #10, #11, and #12 - Adding isAdmin: "true" as a client-controlled request header creates a severe authorization bypass vulnerability if the backend API trusts this header for authorization decisions.
Impact for this endpoint: The /api/student/add/${instituteId} endpoint receives this client-controlled header. If the backend uses this header to determine admin privileges for adding students to institutes, any authenticated user can manipulate this header to gain admin access.
Attack scenario: Attacker modifies the request header to isAdmin: "true" and can add arbitrary students to any institute, bypassing proper authorization checks.

Confidence: 5/5

Suggested Fix
Suggested change
isAdmin: "true",
Cookie: `token=${token}`,

Remove the isAdmin: "true" header. The backend MUST determine admin status from the JWT token validation, not from client-provided headers.
Critical verification required:

  1. Review the backend /api/student/add/${instituteId} endpoint
  2. Ensure it validates admin status from the JWT token, NOT from the isAdmin header
  3. If the header is needed for routing only, document this clearly and ensure backend ignores it for authorization
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/student_action.ts at line 47, a client-controlled header "isAdmin: true"
is being sent to the backend API endpoint /api/student/add/${instituteId}. This is the
same critical security vulnerability identified in Comments #10, #11, and #12 for other
action files. Remove this header from line 47. Review the backend /api/student/add
endpoint to ensure it performs proper server-side authorization by validating admin
status from the JWT token payload, NOT from the isAdmin header. If the backend relies
on this header for authorization, this is a critical vulnerability that must be fixed
immediately by implementing proper server-side JWT validation and role checking.

const { pathname } = request.nextUrl;

const isPublicPath =
pathname === "/" ||
Copy link

Choose a reason for hiding this comment

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

Critical Security Issue - Authentication Bypass via Dead Code: Adding pathname === "/" to the isPublicPath check creates an authentication bypass vulnerability. This change makes the root path "/" publicly accessible, but the subsequent authentication check on lines 21-23 becomes unreachable dead code.
Why this is a critical vulnerability:

  1. Line 11 marks "/" as a public path (isPublicPath = true)
  2. Line 17 checks if (!token && !isPublicPath) - this will be FALSE for "/" because isPublicPath is true
  3. Lines 21-23 check if (!token && pathname === "/") - this code is UNREACHABLE because line 17 already handled the !token case for public paths
  4. Result: Unauthenticated users can access "/" without being redirected to login
    Attack scenario: An unauthenticated user navigates to "/" and bypasses authentication because the path is marked as public, while the intended authentication check (lines 21-23) never executes.

Confidence: 5/5

Suggested Fix
Suggested change
pathname === "/" ||
const isPublicPath =
pathname.startsWith("/login") ||
pathname.startsWith("/api/auth/login") ||
pathname.startsWith("/api/google/auth");

Remove pathname === "/" from the public paths list. The root path should NOT be public - it should require authentication. The check on lines 21-23 will then properly execute and redirect unauthenticated users to login.
Alternative approach (if you want "/" to be a landing page):
If the root path should be accessible to both authenticated and unauthenticated users with different behaviors, handle it separately BEFORE the isPublicPath check:

// Handle root path separately
if (pathname === "/") {
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
// Authenticated users continue to lines 27-31
}
const isPublicPath =
pathname.startsWith("/login") ||
pathname.startsWith("/api/auth/login") ||
pathname.startsWith("/api/google/auth");
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/proxy.ts at line 11, adding pathname === "/" to the isPublicPath check creates
a critical authentication bypass vulnerability. This makes the root path publicly
accessible, but the authentication check on lines 21-23 becomes unreachable dead code
because line 17's condition (!token && !isPublicPath) will be false for "/" when
there's no token. Remove pathname === "/" from line 11 so the root path requires
authentication. The check on lines 21-23 will then properly execute and redirect
unauthenticated users to login. If the root path needs special handling for both
authenticated and unauthenticated users, move the pathname === "/" check to a separate
condition BEFORE the isPublicPath logic to ensure proper authentication enforcement.

Comment on lines +27 to +31
const user = await getUser();

return NextResponse.redirect(
new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
);
Copy link

Choose a reason for hiding this comment

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

Security Issue - Unhandled Error in Authentication Redirect: The code calls getUser() and accesses user.admin.institutes[0] without error handling. If getUser() fails, returns null, or the admin has no institutes, this will cause an unhandled exception that crashes the middleware.
Potential failure scenarios:

  1. getUser() returns null/undefined (authentication token invalid or expired)
  2. user.admin is undefined (user object structure mismatch)
  3. user.admin.institutes is empty array (admin has no institutes assigned)
  4. user.admin.institutes[0] is undefined (array access out of bounds)
    Impact: Middleware crash causes the entire application to fail for authenticated users trying to access public paths. This creates a denial of service condition where legitimate users cannot access the application.

Confidence: 5/5

Suggested Fix
Suggested change
const user = await getUser();
return NextResponse.redirect(
new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
);
if (token && isPublicPath) {
try {
const user = await getUser();
if (!user?.admin?.institutes?.length) {
// Admin has no institutes, redirect to create-institute or error page
return NextResponse.redirect(new URL("/create-institute", request.url));
}
return NextResponse.redirect(
new URL(`/institute/${user.admin.institutes[0].toString()}`, request.url)
);
} catch (error) {
// If getUser fails, clear the invalid token and redirect to login
const response = NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("leadlly.in_admin_token");
return response;
}

Benefits:

  • Handles getUser() failures gracefully
  • Validates that admin has institutes before accessing array
  • Provides fallback behavior for edge cases
  • Prevents middleware crashes that would affect all users
  • Clears invalid tokens to prevent repeated failures
Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/proxy.ts at lines 27-31, the code calls getUser() and accesses
user.admin.institutes[0] without error handling. If getUser() fails, returns null, or
the admin has no institutes, this will cause an unhandled exception that crashes the
middleware, creating a denial of service condition. Wrap lines 27-31 in a try-catch
block. In the try block, validate that user?.admin?.institutes?.length exists before
accessing institutes[0]. If the admin has no institutes, redirect to /create-institute.
In the catch block, clear the invalid authentication token and redirect to /login. This
prevents middleware crashes and provides graceful fallback behavior for edge cases.

📍 This suggestion applies to lines 27-31

@beetle-ai
Copy link

beetle-ai bot commented Mar 12, 2026

Summary by Beetle

This PR significantly enhances the batch and institute management system by introducing comprehensive payment features, improving data validation, and streamlining the user experience. The changes focus on making batch creation more flexible with optional fields, adding subscription-based pricing (Free/Paid), and improving error handling across the application. The PR also includes a new students page and better institute logo management with S3 integration.

📁 File Changes Summary (Consolidated across all commits):

File Status Changes Description
src/actions/batch_actions.ts Modified +12/-3 Enhanced batch creation with new fields (mentors, description, about, payment object with subscription type, amount, currency). Updated API endpoint from /api/batch/institute/ to /api/institute/{id}/batches. Added validation to prevent fetching batches without a valid institute ID.
src/actions/institute_actions.ts Modified +14/-7 Added logo upload support with presigned URL handling. Removed redundant isAdmin headers. Enhanced error handling with validation for undefined/invalid institute IDs. Updated return types to include logoUploadUrl for S3 uploads.
src/actions/mentor_actions.ts
src/actions/student_action.ts
Modified +23/-9 Fixed API base URL inconsistencies (switched to NEXT_PUBLIC_ADMIN_API_BASE_URL). Improved error handling with content-type validation and better error messages. Removed isAdmin header. Enhanced null-safety with nullish coalescing operators.
src/actions/user_actions.ts Modified +0/-1 Removed unnecessary isAdmin header from user fetch requests.
src/app/(auth)/select-institute/page.tsx Modified +3/-3 Updated logo rendering to use nested logo.url property and changed image styling to rounded-full with object-cover for better circular display.
src/app/(protected)/(root)/_components/main-header.tsx Modified +5/-4 Added query enablement condition to prevent fetching with undefined institute IDs. Enhanced null-safety with optional chaining for activeInstitute?.institute?.name.
src/app/(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx Modified +7/-11 Added query enablement guard. Removed address2 field display. Applied optional chaining throughout for safer property access.
src/app/(protected)/(root)/institute/[instituteId]/_components/StudentsOverview.tsx Modified +1/-1 Fixed navigation link from /batches to /students for the "View All" button.
src/app/(protected)/(root)/institute/[instituteId]/batches/page.tsx Modified +282/-427 Major refactor: Replaced mock data with real API integration. Added comprehensive batch creation form with description, about section (whitespace-preserved), and payment configuration (Free/Paid with INR pricing). Implemented loading states, toast notifications, and form validation. Removed subject/teacher filters. Simplified batch display cards with pricing info and status badges.
src/app/(protected)/(root)/institute/[instituteId]/students/page.tsx Added +74/-0 New server-side rendered page displaying institute students in a responsive grid layout with color-coded cards showing student name, class/standard, and level information.
src/app/(protected)/create-institute/_components/create-institute-form.tsx Modified +127/-147 Redesigned form with circular logo upload UI using camera icon. Integrated S3 presigned URL upload flow. Made most fields optional (email, phone, address, city, state, pincode). Removed address2 field. Enhanced UX with better labels and optional indicators. Fixed combobox null-safety issues.
src/helpers/schema/createInstituteSchema.ts Modified +12/-11 Updated schema to make most fields optional with proper defaults. Added logo object schema { name, type }. Changed email validation to allow empty strings. Removed minimum length requirements for address, city, state, and pincode.
src/helpers/types/index.ts Modified +14/-12 Updated IInstitute interface: changed logo to optional nested object with url and key properties. Made all address-related fields, contact info, and metadata optional to match backend flexibility.

Total Changes: 14 files changed, +582 additions, -733 deletions

🗺️ Walkthrough:

sequenceDiagram
participant Admin
participant UI as Create Institute Form
participant Action as Institute Action
participant Backend as Admin API
participant S3 as AWS S3
Admin->>UI: Fill institute details + upload logo
UI->>UI: Validate form (name required, others optional)
UI->>Action: createInstitute(data + logo metadata)
Action->>Backend: POST /api/institute (with logo.name, logo.type)
Backend->>Backend: Create institute record
Backend->>S3: Generate presigned upload URL
Backend-->>Action: Return { success, data, logoUploadUrl }
Action-->>UI: Return response with logoUploadUrl
alt Logo exists
UI->>S3: PUT logoUploadUrl (upload file directly)
S3-->>UI: Upload complete
end
UI->>UI: Dispatch instituteData to Redux
UI->>Admin: Redirect to /institute/{id}
Note over Admin,S3: Batch Creation Flow
Admin->>UI: Navigate to Batches page
UI->>Action: getInstituteBatch(instituteId)
Action->>Backend: GET /api/institute/{id}/batches
Backend-->>UI: Return batches array
Admin->>UI: Click "Create New Batch"
UI->>UI: Open dialog with form
Admin->>UI: Enter name, standard, description, about
Admin->>UI: Toggle "Paid Batch" switch
alt Paid Batch
Admin->>UI: Enter amount (INR)
UI->>UI: Validate amount > 0
end
UI->>Action: createBatch({ name, standard, payment, ... })
Action->>Backend: POST /api/batch
Backend-->>UI: Return { success, data }
UI->>UI: Show success toast
UI->>UI: Refresh batches list
Loading

🎯 Key Changes:

  • Payment System Integration: Introduced subscription-based pricing for batches with Free/Paid options, amount configuration, and INR currency support
  • Enhanced Batch Management: Added rich text fields (description, about with whitespace preservation) and mentor assignment capabilities
  • S3 Logo Upload: Implemented presigned URL flow for direct client-to-S3 logo uploads, eliminating server-side file handling
  • Improved Data Validation: Added institute ID validation guards across multiple actions to prevent API calls with undefined/invalid IDs
  • Schema Flexibility: Made most institute fields optional (address, contact info, subjects, standards) to reduce friction during onboarding
  • API Endpoint Consistency: Standardized all API calls to use NEXT_PUBLIC_ADMIN_API_BASE_URL and updated batch endpoints to RESTful structure
  • New Students Page: Added dedicated server-rendered page for viewing institute students with responsive card layout
  • Better Error Handling: Enhanced error messages, content-type validation, and null-safety throughout the application
  • UI/UX Improvements: Circular logo upload with camera icon, loading states, toast notifications, and cleaner form layouts with optional field indicators
  • Code Cleanup: Removed redundant isAdmin headers, unused address2 field, and mock data in favor of real API integration
⚙️ Settings

Severity Threshold: Medium — Balanced feedback — medium and high severity issues only.Change in Settings
Custom Rules: Define your own review rules — Set Custom Rules
PR Summary: Configure PR summary — Change in Settings

📖 User Guide
  • Once repos are connected, PR analysis is automatically enabled. You can disable analysis for this repo from beetleai.dev/analysis
  • Comment @beetle on any PR to start analysis manually
  • Comment @beetle stop to stop any ongoing analysis

Follow us: Beetle · X · LinkedIn

try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/batch/institute/${instituteId}`,
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/institute/${instituteId}/batches`,
Copy link

Choose a reason for hiding this comment

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

Path Traversal Vulnerability: The instituteId parameter is directly interpolated into the URL without sanitization or validation. An attacker could inject malicious path segments (e.g., ../../admin/users) to access unauthorized endpoints.
While the check at line 49 prevents empty values, it doesn't validate the format or content of instituteId. This could allow path traversal attacks if an attacker controls this parameter.

Confidence: 5/5

Suggested Fix
Suggested change
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/institute/${instituteId}/batches`,
`${process.env.NEXT_PUBLIC_ADMIN_API_BASE_URL}/api/institute/${encodeURIComponent(instituteId)}/batches`,

Add URL encoding to prevent path traversal. Additionally, consider validating that instituteId matches an expected format (e.g., UUID, alphanumeric) before making the API call:

// Add validation before the fetch call
if (!/^[a-zA-Z0-9_-]+$/.test(instituteId)) {
throw new Error("Invalid institute ID format");
}

This ensures the instituteId contains only safe characters and prevents injection attacks.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/batch_actions.ts around line 56, the instituteId parameter is directly
interpolated into the URL without sanitization, creating a path traversal vulnerability;
wrap instituteId with encodeURIComponent() in the URL template string, and add validation
before the fetch call to ensure instituteId matches a safe format like /^[a-zA-Z0-9_-]+$/
to prevent injection attacks.

}

const response = await fetch(
`${baseUrl}/api/institute/${instituteId}/students`,
Copy link

Choose a reason for hiding this comment

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

Path Traversal Vulnerability: Same issue as #1 - The instituteId parameter is directly interpolated into the URL without sanitization or validation. An attacker could inject malicious path segments (e.g., ../../admin/users) to access unauthorized endpoints. See Comment #1 for detailed explanation.

Confidence: 5/5

Suggested Fix
Suggested change
`${baseUrl}/api/institute/${instituteId}/students`,
`${baseUrl}/api/institute/${encodeURIComponent(instituteId)}/students`,

Apply URL encoding to prevent path traversal. Additionally, add validation before the fetch call to ensure instituteId matches a safe format (e.g., alphanumeric, UUID).

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/student_action.ts around line 98, the instituteId parameter is directly
interpolated into the URL without sanitization, creating a path traversal vulnerability;
wrap instituteId with encodeURIComponent() in the URL template string, and add validation
before the fetch call (around line 84) to ensure instituteId matches a safe format like
/^[a-zA-Z0-9_-]+$/ to prevent injection attacks.

Comment on lines +118 to +120
if (!response.ok) {
throw new Error(data.message || "Failed to fetch students");
}
Copy link

Choose a reason for hiding this comment

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

Unsafe JSON Parsing Before Status Check: The code parses the response JSON (line 116) before checking if the response was successful (line 118). This means malicious or malformed JSON from error responses could be processed, potentially causing denial of service or exposing the application to JSON-based attacks.

Confidence: 4/5

Suggested Fix

Move the response.ok check before parsing the JSON to ensure only successful responses are processed:

if (!response.ok) {
throw new Error(
`Failed to fetch students (${response.status})`
);
}
const data = await response.json();

This ensures error responses are rejected before attempting to parse potentially malicious JSON payloads.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/actions/student_action.ts around lines 116-120, the code parses response JSON
before checking response.ok, which could process malicious JSON from error responses;
move the response.ok check (currently at line 118-120) to before the JSON parsing
(line 116), so that error responses are rejected before attempting to parse the body,
preventing potential JSON-based attacks.

📍 This suggestion applies to lines 118-120


<Link
href={`/institute/${params?.instituteId}/batches`}
href={`/institute/${instituteId}/students`}
Copy link

Choose a reason for hiding this comment

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

Path Traversal Risk in Client-Side Navigation: Similar to Comments #1 and #2, the instituteId parameter is directly interpolated into the URL without sanitization. While this is client-side navigation (lower risk than server-side API calls), an attacker who can control instituteId could potentially inject malicious path segments to navigate to unauthorized routes.
For consistency and defense-in-depth, the same URL encoding should be applied here as recommended in the server-side API calls.

Confidence: 4/5

Suggested Fix
Suggested change
href={`/institute/${instituteId}/students`}
href={`/institute/${encodeURIComponent(instituteId)}/students`}

Apply URL encoding to prevent potential path manipulation. This ensures consistency with the security fixes needed in batch_actions.ts and student_action.ts, and provides defense-in-depth protection even for client-side navigation.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/(protected)/(root)/institute/[instituteId]/_components/StudentsOverview.tsx
around line 111, the instituteId parameter is directly interpolated into the href URL
without sanitization; wrap instituteId with encodeURIComponent() in the template string
to prevent potential path manipulation and maintain consistency with security fixes in
other files handling instituteId.


<div className="flex justify-between items-center mb-4">
<Link
href={`/institute/${instituteId}/students`}
Copy link

Choose a reason for hiding this comment

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

Path Traversal Risk in Client-Side Navigation: The instituteId parameter is directly interpolated into the URL without sanitization. Similar to Comment #4, an attacker who can control instituteId could potentially inject malicious path segments to navigate to unauthorized routes. While this is client-side navigation (lower risk than server-side API calls), URL encoding should be applied for consistency and defense-in-depth.

Confidence: 4/5

Suggested Fix
Suggested change
href={`/institute/${instituteId}/students`}
href={`/institute/${encodeURIComponent(instituteId)}/students`}

Apply URL encoding to prevent potential path manipulation. This ensures consistency with the security fixes needed in other files handling instituteId.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/(protected)/(root)/institute/[instituteId]/batches/page.tsx around line 518,
the instituteId parameter is directly interpolated into the href URL without sanitization;
wrap instituteId with encodeURIComponent() in the template string to prevent potential
path manipulation and maintain consistency with security fixes in other files handling
instituteId.

Comment on lines +8 to +18
const { instituteId } = await params;

if (!instituteId) {
return (
<div className="container mx-auto px-4 py-8">
<p className="text-muted-foreground">Invalid institute. Please select an institute.</p>
</div>
);
}

const res = await getInstituteStudents(instituteId);
Copy link

Choose a reason for hiding this comment

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

Missing Input Validation for instituteId: The instituteId parameter is extracted from URL params and used directly without format validation. While the path traversal vulnerability in getInstituteStudents() is already reported (Comment #2), this file should implement input validation as a defense-in-depth measure to prevent malicious input from reaching the API layer.
The current check at line 10 only validates existence, not format. An attacker could inject path traversal sequences (e.g., ../../admin) or other malicious patterns that would be passed directly to the vulnerable API call.

Confidence: 4/5

Suggested Fix

Add format validation after extracting instituteId to ensure it matches expected patterns before passing to getInstituteStudents():

const { instituteId } = await params;
// Validate instituteId format (alphanumeric, hyphens, underscores only)
if (!instituteId || !/^[a-zA-Z0-9_-]+$/.test(instituteId)) {
return (
<div className="container mx-auto px-4 py-8">
<p className="text-muted-foreground">Invalid institute. Please select an institute.</p>
</div>
);
}
const res = await getInstituteStudents(instituteId);

This validation ensures only safe characters are accepted, providing an additional security layer even after the path traversal vulnerability in student_action.ts (Comment #2) is fixed.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/app/(protected)/(root)/institute/[instituteId]/students/page.tsx around lines 8-18,
the instituteId parameter is extracted from URL params and used without format validation;
modify the validation check at line 10 to include a regex pattern test like
!/^[a-zA-Z0-9_-]+$/.test(instituteId) to ensure only alphanumeric characters, hyphens,
and underscores are allowed, preventing malicious input patterns from reaching the API
layer as a defense-in-depth measure.

📍 This suggestion applies to lines 8-18

Copy link

@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: 9

Caution

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

⚠️ Outside diff range comments (2)
src/actions/batch_actions.ts (2)

68-76: ⚠️ Potential issue | 🟡 Minor

Same misleading error messages in getInstituteBatch.

The error messages reference "saving daily quiz answers" but this function fetches institute batches.

🐛 Proposed fix
   } catch (error) {
     if (error instanceof Error) {
-      throw new Error(`Error in saving daily quiz answers: ${error.message}`);
+      throw new Error(`Error fetching institute batches: ${error.message}`);
     } else {
       throw new Error(
-        "An unknown error occurred while saving daily quiz answers!",
+        "An unknown error occurred while fetching institute batches!",
       );
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/batch_actions.ts` around lines 68 - 76, The catch block in
getInstituteBatch currently throws errors mentioning "saving daily quiz answers"
which is misleading; update the thrown Error messages in the getInstituteBatch
function (the catch handling around the institute batch fetch) to reference
fetching institute batches (e.g., "Error fetching institute batch: ..." and a
generic "An unknown error occurred while fetching institute batches"). Ensure
you still preserve the original error.message when error instanceof Error and
use a clear, relevant message otherwise.

37-45: ⚠️ Potential issue | 🟡 Minor

Misleading error messages reference "daily quiz answers" instead of batch operations.

The error messages in createBatch incorrectly reference "daily quiz answers" when this function handles batch creation.

🐛 Proposed fix
   } catch (error) {
     if (error instanceof Error) {
-      throw new Error(`Error in saving daily quiz answers: ${error.message}`);
+      throw new Error(`Error creating batch: ${error.message}`);
     } else {
       throw new Error(
-        "An unknown error occurred while saving daily quiz answers!",
+        "An unknown error occurred while creating batch!",
       );
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/batch_actions.ts` around lines 37 - 45, The catch block in
createBatch incorrectly references "daily quiz answers"; update the thrown Error
messages in the createBatch function to reference batch creation (e.g., "Error
creating batch" and "An unknown error occurred while creating the batch") so
both the instance-of-Error branch and the generic branch accurately describe the
operation and include the existing error.message in the detailed branch.
🧹 Nitpick comments (4)
src/app/(protected)/(root)/institute/[instituteId]/students/page.tsx (2)

32-44: Avoid any type - define a proper Student interface.

Using any loses type safety and makes the code fragile. The nested property access (student?.academic?.standard, student?.details?.level?.number) would benefit from proper typing.

♻️ Proposed fix
+interface Student {
+  _id: string;
+  firstname?: string;
+  lastname?: string;
+  academic?: {
+    standard?: string;
+  };
+  details?: {
+    level?: {
+      number?: number;
+    };
+  };
+}
+
+const bgColors = ["bg-green-100", "bg-yellow-100", "bg-blue-100", "bg-red-100"];
+
 // In the component:
-            {students.map((student: any, index: number) => {
-              const bgColors = [
-                "bg-green-100",
-                "bg-yellow-100",
-                "bg-blue-100",
-                "bg-red-100",
-              ];
+            {students.map((student: Student, index: number) => {
               const bg = bgColors[index % bgColors.length];

Define the interface based on the actual API response shape from getInstituteStudents.

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

In `@src/app/`(protected)/(root)/institute/[instituteId]/students/page.tsx around
lines 32 - 44, Replace the use of the implicit any in students.map by defining a
Student interface that models the API response (including firstname, lastname,
academic?: { standard?: string }, details?: { level?: { number?: number } } and
any other fields used), update the students array and getInstituteStudents
return type to Student[] (or Promise<Student[]>) and change the map signature
from (student: any, index: number) to (student: Student, index: number); ensure
optional nested properties use the same optional types in the interface so the
existing accesses (student?.academic?.standard, student?.details?.level?.number)
are type-safe and adjust any other usages of student to satisfy the new Student
type.

18-19: Consider displaying error message when fetch fails.

When res.success is false, the error message from res.message is not shown to the user. The page silently shows "No students found" which could be misleading.

♻️ Proposed fix
   const res = await getInstituteStudents(instituteId);
-  const students = res.success ? res.students : [];
+  const students = res.students;
+  const errorMessage = !res.success ? res.message : null;

   return (
     <div className="container mx-auto px-4 py-8">
       {/* ... */}
       <div className="bg-white p-4 md:p-6 rounded-3xl shadow-section mb-6">
+        {errorMessage && (
+          <p className="text-sm text-destructive mb-4">{errorMessage}</p>
+        )}
         {students.length === 0 ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/institute/[instituteId]/students/page.tsx around
lines 18 - 19, The fetch result handling currently discards errors (const res =
await getInstituteStudents(instituteId); const students = res.success ?
res.students : []), so when res.success is false the UI shows "No students
found" instead of the real error; update this block to capture the error message
(e.g., const error = res.success ? null : res.message) and pass it into the page
render or component that displays students, then update the render logic to show
the error message when error is non-null (instead of treating an error as an
empty students array). Ensure you reference getInstituteStudents, res.success,
res.message and the students rendering branch so the page renders the error
string to the user.
src/app/(protected)/(root)/institute/[instituteId]/batches/page.tsx (1)

24-35: Unused imports: Command* and Popover* components.

These components are imported but not used in the JSX. Consider removing them to reduce bundle size.

🧹 Proposed fix
-import {
-  Command,
-  CommandEmpty,
-  CommandGroup,
-  CommandInput,
-  CommandItem,
-} from "@/components/ui/command";
-import {
-  Popover,
-  PopoverContent,
-  PopoverTrigger,
-} from "@/components/ui/popover";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/institute/[instituteId]/batches/page.tsx around
lines 24 - 35, Remove the unused UI imports to avoid increasing bundle size:
delete the unused symbols Command, CommandEmpty, CommandGroup, CommandInput,
CommandItem and Popover, PopoverContent, PopoverTrigger from the import
statements in the batches page so only actually used components remain; if any
of those components are later needed, re-add them where they are referenced,
otherwise keep the imports minimal to match the JSX usage in this file.
src/actions/institute_actions.ts (1)

43-47: Inconsistent error handling pattern across functions.

createInstitute returns a structured error object on catch, while getMyInstitute, getActiveInstitute, and getAllUserInstitutes throw errors. This inconsistency can make error handling unpredictable for consumers.

Consider standardizing to either always return structured responses or always throw, across all functions in this file.

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

In `@src/actions/institute_actions.ts` around lines 43 - 47, The file has
inconsistent error handling: createInstitute catches and returns a structured
error while getMyInstitute, getActiveInstitute, and getAllUserInstitutes throw
errors; standardize behavior across the module by choosing one pattern and
applying it to all functions. Pick either (A) always return a uniform result
object { success, data, error, ... } — update getMyInstitute,
getActiveInstitute, getAllUserInstitutes to catch exceptions and return the same
shaped response as createInstitute (include their data fields and set
error/message accordingly) — or (B) always throw errors — modify createInstitute
to rethrow the caught error (or remove the try/catch) so callers handle
exceptions consistently; update or document the exported function signatures
accordingly (referencing createInstitute, getMyInstitute, getActiveInstitute,
getAllUserInstitutes).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/actions/user_actions.ts`:
- Line 226: In the call updateTag("userData") replace the incorrect Next.js API
with revalidateTag("userData"); update any imports or references so that
revalidateTag is imported from the same Next.js utility (or removed if updateTag
was locally defined), and ensure the single argument tag string "userData" is
passed to revalidateTag exactly where updateTag was used (search for
updateTag(...) to locate all occurrences).
- Line 197: The code calls updateTag("userData") which doesn't exist in
next/cache; replace that call with revalidateTag("userData") to use the correct
API. Locate the invocation of updateTag in src/actions/user_actions.ts (search
for updateTag or the function that performs tag revalidation) and change the
symbol to revalidateTag, keeping the same argument and import style; also ensure
revalidateTag is imported from 'next/cache' if not already and remove any stale
updateTag import or references.

In `@src/app/`(auth)/select-institute/page.tsx:
- Around line 100-104: The Button is currently wrapped by Link which creates
nested interactive elements; instead add the asChild prop to Button so it
delegates rendering to the child Link: replace the Link-wrapping pattern with
Button(asChild) containing a Link(href="/create-institute") child, keep the
Button's className ("bg-primary hover:bg-primary/90") and the Link's href, and
ensure the visible text remains "Create Your First Institute" so the interactive
semantics come from the Link element rather than a nested button.
- Around line 121-125: The JSX now accesses institute.logo?.url but the
component still types institute.logo as a string; update the component/variable
type to use the canonical IInstitute (import IInstitute from your shared types)
so institute is typed as IInstitute and logo has type { url: string; key: string
} | null, then adjust any prop/interface where institute is declared to
IInstitute and ensure the conditional uses institute.logo?.url (or a null check)
to satisfy TypeScript.

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx:
- Around line 16-20: The component currently only reads data from useQuery and
doesn't handle loading/error states; update the useQuery call in
InstituteOverview to also destructure isLoading, isError, and error (e.g., const
{ data: activeInstitute, isLoading, isError, error } = useQuery(...)), then
return or render a loading placeholder/skeleton when isLoading, render a clear
error UI/message (using error) when isError, and guard all institute field usage
with activeInstitute (e.g., activeInstitute?.name) so the component doesn't
render undefined while fetching or after a failure.

In `@src/app/`(protected)/(root)/institute/[instituteId]/batches/page.tsx:
- Around line 113-115: The useEffect currently lists filters in its dependency
array and therefore re-runs fetchBatches whenever filters change even though
fetchBatches does not use filters; remove filters from the dependency array so
useEffect only depends on instituteId (i.e., useEffect(() => { fetchBatches();
}, [instituteId])) and keep client-side filtering where it is (line ~438), or
alternately, if you intend server-side filtering, update fetchBatches to accept
and use the filters parameter; reference: useEffect, fetchBatches, filters,
instituteId.
- Line 67: Remove the debug console.log that prints params in the batches page;
locate the statement using the identifier params (the console.log(params, "here
are the params") line in the institute batches page component) and delete it (or
replace with proper, conditional logging via a dev-only logger if needed) so no
debug output remains in production.

In `@src/app/`(protected)/create-institute/_components/create-institute-form.tsx:
- Around line 131-134: Guard against missing response data before navigating:
check that res.data and res.data._id exist before calling router.replace and
dispatching instituteData; if the _id is missing, avoid calling
router.replace(`/institute/${...}`) (and optionally show an error toast or keep
the dialog open via setDialogOpen) so you don't navigate to
/institute/undefined. Update the block around toast.success,
dispatch(instituteData(res.data)), router.replace(...) and
setDialogOpen?.(false) to conditionally run router.replace only when
res.data?._id is present and handle the missing-data case (e.g., log/error toast
or early return).
- Around line 121-129: Wrap the presigned S3 upload (the fetch using
res.logoUploadUrl with logoFile) in a try/catch and check the fetch response.ok;
if the upload fails throw or surface an error (e.g., set a form error state or
call your existing toast/error handler) instead of ignoring it so the user is
informed; include the HTTP status and error text in the logged/visible message,
and consider marking the institute creation as partially failed or triggering
cleanup (delete the created institute) if upload is critical. Ensure you modify
the code inside the res.success block that references logoFile and
res.logoUploadUrl.

---

Outside diff comments:
In `@src/actions/batch_actions.ts`:
- Around line 68-76: The catch block in getInstituteBatch currently throws
errors mentioning "saving daily quiz answers" which is misleading; update the
thrown Error messages in the getInstituteBatch function (the catch handling
around the institute batch fetch) to reference fetching institute batches (e.g.,
"Error fetching institute batch: ..." and a generic "An unknown error occurred
while fetching institute batches"). Ensure you still preserve the original
error.message when error instanceof Error and use a clear, relevant message
otherwise.
- Around line 37-45: The catch block in createBatch incorrectly references
"daily quiz answers"; update the thrown Error messages in the createBatch
function to reference batch creation (e.g., "Error creating batch" and "An
unknown error occurred while creating the batch") so both the instance-of-Error
branch and the generic branch accurately describe the operation and include the
existing error.message in the detailed branch.

---

Nitpick comments:
In `@src/actions/institute_actions.ts`:
- Around line 43-47: The file has inconsistent error handling: createInstitute
catches and returns a structured error while getMyInstitute, getActiveInstitute,
and getAllUserInstitutes throw errors; standardize behavior across the module by
choosing one pattern and applying it to all functions. Pick either (A) always
return a uniform result object { success, data, error, ... } — update
getMyInstitute, getActiveInstitute, getAllUserInstitutes to catch exceptions and
return the same shaped response as createInstitute (include their data fields
and set error/message accordingly) — or (B) always throw errors — modify
createInstitute to rethrow the caught error (or remove the try/catch) so callers
handle exceptions consistently; update or document the exported function
signatures accordingly (referencing createInstitute, getMyInstitute,
getActiveInstitute, getAllUserInstitutes).

In `@src/app/`(protected)/(root)/institute/[instituteId]/batches/page.tsx:
- Around line 24-35: Remove the unused UI imports to avoid increasing bundle
size: delete the unused symbols Command, CommandEmpty, CommandGroup,
CommandInput, CommandItem and Popover, PopoverContent, PopoverTrigger from the
import statements in the batches page so only actually used components remain;
if any of those components are later needed, re-add them where they are
referenced, otherwise keep the imports minimal to match the JSX usage in this
file.

In `@src/app/`(protected)/(root)/institute/[instituteId]/students/page.tsx:
- Around line 32-44: Replace the use of the implicit any in students.map by
defining a Student interface that models the API response (including firstname,
lastname, academic?: { standard?: string }, details?: { level?: { number?:
number } } and any other fields used), update the students array and
getInstituteStudents return type to Student[] (or Promise<Student[]>) and change
the map signature from (student: any, index: number) to (student: Student,
index: number); ensure optional nested properties use the same optional types in
the interface so the existing accesses (student?.academic?.standard,
student?.details?.level?.number) are type-safe and adjust any other usages of
student to satisfy the new Student type.
- Around line 18-19: The fetch result handling currently discards errors (const
res = await getInstituteStudents(instituteId); const students = res.success ?
res.students : []), so when res.success is false the UI shows "No students
found" instead of the real error; update this block to capture the error message
(e.g., const error = res.success ? null : res.message) and pass it into the page
render or component that displays students, then update the render logic to show
the error message when error is non-null (instead of treating an error as an
empty students array). Ensure you reference getInstituteStudents, res.success,
res.message and the students rendering branch so the page renders the error
string to the user.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89d63bcf-b486-41c8-a271-22043bd23ddf

📥 Commits

Reviewing files that changed from the base of the PR and between 5cef438 and c5a8505.

📒 Files selected for processing (14)
  • src/actions/batch_actions.ts
  • src/actions/institute_actions.ts
  • src/actions/mentor_actions.ts
  • src/actions/student_action.ts
  • src/actions/user_actions.ts
  • src/app/(auth)/select-institute/page.tsx
  • src/app/(protected)/(root)/_components/main-header.tsx
  • src/app/(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx
  • src/app/(protected)/(root)/institute/[instituteId]/_components/StudentsOverview.tsx
  • src/app/(protected)/(root)/institute/[instituteId]/batches/page.tsx
  • src/app/(protected)/(root)/institute/[instituteId]/students/page.tsx
  • src/app/(protected)/create-institute/_components/create-institute-form.tsx
  • src/helpers/schema/createInstituteSchema.ts
  • src/helpers/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/(protected)/(root)/institute/[instituteId]/_components/StudentsOverview.tsx
  • src/actions/mentor_actions.ts

const responseData = await res.json();

revalidateTag("userData");
updateTag("userData");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Replace updateTag with revalidateTag.

As noted above, updateTag doesn't exist in next/cache. Use revalidateTag instead:

-    updateTag("userData");
+    revalidateTag("userData");
📝 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
updateTag("userData");
revalidateTag("userData");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/user_actions.ts` at line 197, The code calls
updateTag("userData") which doesn't exist in next/cache; replace that call with
revalidateTag("userData") to use the correct API. Locate the invocation of
updateTag in src/actions/user_actions.ts (search for updateTag or the function
that performs tag revalidation) and change the symbol to revalidateTag, keeping
the same argument and import style; also ensure revalidateTag is imported from
'next/cache' if not already and remove any stale updateTag import or references.


const responseData = await res.json();
revalidateTag("userData");
updateTag("userData");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Replace updateTag with revalidateTag.

Same issue—use the correct Next.js API:

-    updateTag("userData");
+    revalidateTag("userData");
📝 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
updateTag("userData");
revalidateTag("userData");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/user_actions.ts` at line 226, In the call updateTag("userData")
replace the incorrect Next.js API with revalidateTag("userData"); update any
imports or references so that revalidateTag is imported from the same Next.js
utility (or removed if updateTag was locally defined), and ensure the single
argument tag string "userData" is passed to revalidateTag exactly where
updateTag was used (search for updateTag(...) to locate all occurrences).

Comment on lines 100 to 104
<Link href="/create-institute">
<Button className="bg-primary hover:bg-primary/90">Create Your First Institute</Button>
<Button className="bg-primary hover:bg-primary/90">
Create Your First Institute
</Button>
</Link>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e

echo "Button implementation:"
fd 'button.tsx' src/components/ui --exec sed -n '1,140p' {}

echo
echo "Current CTA markup in select-institute page:"
sed -n '96,108p' 'src/app/(auth)/select-institute/page.tsx'

Repository: Leadlly/leadlly.admin.web

Length of output: 3942


Fix invalid HTML nesting: extract Button from Link using asChild.

The Button component renders as a native <button> by default (when asChild is not set). Wrapping it inside Link creates nested interactive elements, which is invalid HTML and breaks keyboard navigation and screen-reader semantics. Use asChild to make the Link carry the interactive semantics directly.

♻️ Proposed fix
-          <Link href="/create-institute">
-            <Button className="bg-primary hover:bg-primary/90">
-              Create Your First Institute
-            </Button>
-          </Link>
+          <Button asChild className="bg-primary hover:bg-primary/90">
+            <Link href="/create-institute">Create Your First Institute</Link>
+          </Button>
📝 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
<Link href="/create-institute">
<Button className="bg-primary hover:bg-primary/90">Create Your First Institute</Button>
<Button className="bg-primary hover:bg-primary/90">
Create Your First Institute
</Button>
</Link>
<Button asChild className="bg-primary hover:bg-primary/90">
<Link href="/create-institute">Create Your First Institute</Link>
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(auth)/select-institute/page.tsx around lines 100 - 104, The Button
is currently wrapped by Link which creates nested interactive elements; instead
add the asChild prop to Button so it delegates rendering to the child Link:
replace the Link-wrapping pattern with Button(asChild) containing a
Link(href="/create-institute") child, keep the Button's className ("bg-primary
hover:bg-primary/90") and the Link's href, and ensure the visible text remains
"Create Your First Institute" so the interactive semantics come from the Link
element rather than a nested button.

Comment on lines +121 to +125
{institute.logo?.url ? (
<div className="w-16 h-16 mx-auto mb-4">
<img
src={institute.logo}
alt={`${institute.name} logo`}
className="w-full h-full object-contain"
<img
src={institute.logo.url}
alt={`${institute.name} logo`}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e

echo "Local type in select-institute page:"
sed -n '20,32p' 'src/app/(auth)/select-institute/page.tsx'

echo
echo "Changed logo access:"
rg -n -C1 'logo\?\.url|logo\.url' 'src/app/(auth)/select-institute/page.tsx'

echo
echo "Canonical institute type:"
sed -n '1,20p' 'src/helpers/types/index.ts'

Repository: Leadlly/leadlly.admin.web

Length of output: 1236


🏁 Script executed:

#!/bin/bash
set -e

echo "=== Searching for IInstitute definition ==="
rg -n 'interface IInstitute|type IInstitute' 'src/helpers/types/index.ts' -A 10

echo
echo "=== All usages of institute.logo in the codebase ==="
rg -n 'institute\.logo' --type tsx --type ts | head -20

echo
echo "=== Check line count of src/helpers/types/index.ts ==="
wc -l 'src/helpers/types/index.ts'

Repository: Leadlly/leadlly.admin.web

Length of output: 552


Align the new logo access with the shared institute type.

Lines 121–125 now treat logo as an object with a url property, but this file still types it as a string. That makes logo.url a TypeScript error and causes the page to drift from the canonical IInstitute contract in src/helpers/types/index.ts, which defines logo as { url: string; key: string } | null.

🛠️ Proposed fix
+import type { IInstitute } from "@/helpers/types";
...
-interface Institute {
-  _id: string;
-  name: string;
-  logo?: string;
-  createdAt: string;
-}
-
 export default function SelectInstitutePage() {
-  const [institutes, setInstitutes] = useState<Institute[]>([]);
+  const [institutes, setInstitutes] = useState<IInstitute[]>([]);
...
-  const handleSelectInstitute = async (institute: Institute) => {
+  const handleSelectInstitute = async (institute: IInstitute) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(auth)/select-institute/page.tsx around lines 121 - 125, The JSX now
accesses institute.logo?.url but the component still types institute.logo as a
string; update the component/variable type to use the canonical IInstitute
(import IInstitute from your shared types) so institute is typed as IInstitute
and logo has type { url: string; key: string } | null, then adjust any
prop/interface where institute is declared to IInstitute and ensure the
conditional uses institute.logo?.url (or a null check) to satisfy TypeScript.

Comment on lines +16 to +20
const { data: activeInstitute } = useQuery({
queryKey: ["active_institute", instituteId],
queryFn: () => getActiveInstitute({ instituteId }),
enabled: !!instituteId && instituteId !== "undefined",
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle loading and error states from useQuery.

The component only destructures data but doesn't handle isLoading or isError states. While data is being fetched, all institute fields (name, address, contact, email) render as empty/undefined, resulting in a poor user experience. Additionally, if the query fails, users receive no feedback.

Consider destructuring and handling these states:

-  const { data: activeInstitute } = useQuery({
+  const { data: activeInstitute, isLoading, isError } = useQuery({
     queryKey: ["active_institute", instituteId],
     queryFn: () => getActiveInstitute({ instituteId }),
     enabled: !!instituteId && instituteId !== "undefined",
   });
+
+  if (isLoading) {
+    return <InstituteOverviewSkeleton />; // or a loading placeholder
+  }
+
+  if (isError) {
+    return <div>Failed to load institute details.</div>;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(protected)/(root)/institute/[instituteId]/_components/InstituteOverview.tsx
around lines 16 - 20, The component currently only reads data from useQuery and
doesn't handle loading/error states; update the useQuery call in
InstituteOverview to also destructure isLoading, isError, and error (e.g., const
{ data: activeInstitute, isLoading, isError, error } = useQuery(...)), then
return or render a loading placeholder/skeleton when isLoading, render a clear
error UI/message (using error) when isError, and guard all institute field usage
with activeInstitute (e.g., activeInstitute?.name) so the component doesn't
render undefined while fetching or after a failure.

export default function BatchesPage() {
const params = useParams<{ instituteId: string }>();
const instituteId = params?.instituteId ?? "";
console.log(params, "here are the params")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove debug console.log statement.

This debug statement should be removed before merging to production.

🧹 Proposed fix
   const instituteId = params?.instituteId ?? "";
-console.log(params, "here are the params")
   const [batches, setBatches] = useState<ApiBatch[]>([]);
📝 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
console.log(params, "here are the params")
const instituteId = params?.instituteId ?? "";
const [batches, setBatches] = useState<ApiBatch[]>([]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/institute/[instituteId]/batches/page.tsx at line
67, Remove the debug console.log that prints params in the batches page; locate
the statement using the identifier params (the console.log(params, "here are the
params") line in the institute batches page component) and delete it (or replace
with proper, conditional logging via a dev-only logger if needed) so no debug
output remains in production.

Comment on lines +113 to +115
useEffect(() => {
fetchBatches();
}, [instituteId, filters]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

filters in useEffect dependency array triggers unnecessary refetches.

The filters state is included in the dependency array, but fetchBatches() doesn't use filters for the API call—filtering is done client-side on line 438. This causes unnecessary API calls when filters change.

🐛 Proposed fix
   useEffect(() => {
     fetchBatches();
-  }, [instituteId, filters]);
+  }, [instituteId]);
📝 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
useEffect(() => {
fetchBatches();
}, [instituteId, filters]);
useEffect(() => {
fetchBatches();
}, [instituteId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/(root)/institute/[instituteId]/batches/page.tsx around
lines 113 - 115, The useEffect currently lists filters in its dependency array
and therefore re-runs fetchBatches whenever filters change even though
fetchBatches does not use filters; remove filters from the dependency array so
useEffect only depends on instituteId (i.e., useEffect(() => { fetchBatches();
}, [instituteId])) and keep client-side filtering where it is (line ~438), or
alternately, if you intend server-side filtering, update fetchBatches to accept
and use the filters parameter; reference: useEffect, fetchBatches, filters,
instituteId.

Comment on lines +121 to +129
if (res.success) {
// Upload logo directly to S3 using the presigned URL
if (logoFile && res.logoUploadUrl) {
await fetch(res.logoUploadUrl, {
method: "PUT",
body: logoFile,
headers: { "Content-Type": logoFile.type },
});
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

S3 logo upload has no error handling.

If the presigned URL upload fails, the error is silently ignored. The institute is created but the logo won't be uploaded, potentially leaving the user confused.

🐛 Proposed fix
         // Upload logo directly to S3 using the presigned URL
         if (logoFile && res.logoUploadUrl) {
-          await fetch(res.logoUploadUrl, {
+          const uploadRes = await fetch(res.logoUploadUrl, {
             method: "PUT",
             body: logoFile,
             headers: { "Content-Type": logoFile.type },
           });
+          if (!uploadRes.ok) {
+            console.error("Logo upload failed:", uploadRes.status);
+            toast.warning("Institute created but logo upload failed. You can update the logo later.");
+          }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/create-institute/_components/create-institute-form.tsx
around lines 121 - 129, Wrap the presigned S3 upload (the fetch using
res.logoUploadUrl with logoFile) in a try/catch and check the fetch response.ok;
if the upload fails throw or surface an error (e.g., set a form error state or
call your existing toast/error handler) instead of ignoring it so the user is
informed; include the HTTP status and error text in the logged/visible message,
and consider marking the institute creation as partially failed or triggering
cleanup (delete the created institute) if upload is critical. Ensure you modify
the code inside the res.success block that references logoFile and
res.logoUploadUrl.

Comment on lines +131 to +134
toast.success("Institute created successfully!");
dispatch(instituteData(res.data));
router.replace(`/institute/${res.data?._id}`);
setDialogOpen?.(false);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential navigation to /institute/undefined if res.data is missing.

If the API returns success: true but data is undefined, router.replace(\/institute/${res.data?._id}`)will navigate to/institute/undefined`.

🐛 Proposed fix
       if (res.success) {
         // ... upload logic ...
 
         toast.success("Institute created successfully!");
-        dispatch(instituteData(res.data));
-        router.replace(`/institute/${res.data?._id}`);
+        if (res.data?._id) {
+          dispatch(instituteData(res.data));
+          router.replace(`/institute/${res.data._id}`);
+        }
         setDialogOpen?.(false);
📝 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
toast.success("Institute created successfully!");
dispatch(instituteData(res.data));
router.replace(`/institute/${res.data?._id}`);
setDialogOpen?.(false);
toast.success("Institute created successfully!");
if (res.data?._id) {
dispatch(instituteData(res.data));
router.replace(`/institute/${res.data._id}`);
}
setDialogOpen?.(false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(protected)/create-institute/_components/create-institute-form.tsx
around lines 131 - 134, Guard against missing response data before navigating:
check that res.data and res.data._id exist before calling router.replace and
dispatching instituteData; if the _id is missing, avoid calling
router.replace(`/institute/${...}`) (and optionally show an error toast or keep
the dialog open via setDialogOpen) so you don't navigate to
/institute/undefined. Update the block around toast.success,
dispatch(instituteData(res.data)), router.replace(...) and
setDialogOpen?.(false) to conditionally run router.replace only when
res.data?._id is present and handle the missing-data case (e.g., log/error toast
or early return).

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.

2 participants