Skip to content

feat: add profile personalization system with avatar and banner selec…#650

Open
avantikarathi01 wants to merge 1 commit into
knoxiboy:mainfrom
avantikarathi01:feature/profile-personalization
Open

feat: add profile personalization system with avatar and banner selec…#650
avantikarathi01 wants to merge 1 commit into
knoxiboy:mainfrom
avantikarathi01:feature/profile-personalization

Conversation

@avantikarathi01

@avantikarathi01 avantikarathi01 commented Jun 13, 2026

Copy link
Copy Markdown

User description

Description

Added a comprehensive Profile Personalization System that allows users to customize their profile appearance. Users can now select from predefined avatars and banner themes, with a live preview before saving. Preferences are stored in the database and reflect instantly across the platform.

Related Issue

Closes #632

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Documentation update (README, guides, comments)
  • Style / UI change (no logic change)
  • Code refactor (no behavior change)
  • Test addition or update
  • Breaking change (fix or feature that would cause existing functionality to change)

How Has This Been Tested?

  • Tested locally with npm run dev
  • Verified on mobile viewport (375px)
  • Verified on desktop viewport (1440px)

Checklist

  • I have tested my changes locally (npm run dev)
  • My code follows the existing code style (TypeScript, Tailwind, no any types)
  • I have not introduced unrelated changes (each PR should address one issue)
  • I have added comments where necessary
  • My branch is up to date with main
  • I have linked the related issue above
  • Screenshots are included (if this is a UI change)

CodeAnt-AI Description

Add profile style customization with avatars and banners

What Changed

  • Added a new Style tab on the profile page where users can pick an avatar and a banner, preview the result, and save their choices
  • The profile header now shows a banner behind the avatar, making personalization visible on the page
  • Avatar and banner choices are stored for each user and loaded when they return to the profile
  • Profile notifications remain available alongside the new personalization options

Impact

✅ More personal profile pages
✅ Saved profile styling between visits
✅ Faster profile setup

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

  • New Features

    • Added profile personalization with customizable avatar and banner selection.
    • New "Personalization" tab on profile page featuring live preview and selector UI.
    • Users can save their avatar and banner preferences.
  • UI/UX Improvements

    • Enhanced profile header design with banner styling.
    • Improved profile page layout and accessibility.

@codeant-ai

codeant-ai Bot commented Jun 13, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@vercel

vercel Bot commented Jun 13, 2026

Copy link
Copy Markdown

@avantikarathi01 is attempting to deploy a commit to the Karan Mani Tripathi 's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added gssoc'26 GSSoC program issue level:beginner Beginner level task type:bug Bug fix type:feature New feature size/l labels Jun 13, 2026
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR implements user profile personalization, allowing users to customize their profile appearance with avatar and banner selections. The feature adds preference schema columns, API endpoints for persistence, reusable selection components, and integrates personalization UI into the profile settings page with real-time preview and save capability.

Changes

Profile Personalization Feature

Layer / File(s) Summary
User Preference Schema
src/configs/schema.ts
usersTable extended with avatarPreference and bannerPreference columns, both non-null with string defaults.
Preference API Handler
src/app/api/profile/preference/route.ts
Authenticated GET and POST endpoints for reading and persisting user avatar/banner preferences keyed by Clerk user email.
Selection Components
src/components/AvatarSelector.tsx, src/components/BannerSelector.tsx
Two client-side selector components render fixed choice grids with visual selection state and onSelect callbacks.
Profile Page - Personalization Feature
src/app/profile/page.tsx
Profile page adds personalization state, BANNER_GRADIENTS styling map, handleSavePersonalization POST handler, banner header rendering, new "Personalization" tab in navigation, and full personalization tab UI with component integration and save button.
Profile Page - Supporting Updates
src/app/profile/page.tsx
Code refactoring (early returns for skeleton/error states), skeleton layout improvements for banner area, accessibility (aria-label on retry button), type annotations, fetch header formatting, doubt date display formatting, classroom teacher email truncation, and structural comment markers.
Environment Configuration
.env.example
DATABASE_URL updated to Neon PostgreSQL connection string with explicit sslmode=require and channel_binding=require.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements core personalization features (avatar selection, banner selection, database schema, API routes, UI components) but lacks theme customization, cursor personalization, animations, and accessibility features specified in issue #632. Complete remaining scope items: add theme personalization, cursor selection, animations, and accessibility support (reduced-motion, keyboard navigation, high-contrast themes, screen-reader friendliness).
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature—adding profile personalization with avatar and banner selection—which aligns with the primary changes across multiple files.
Out of Scope Changes check ✅ Passed All changes are directly related to profile personalization system implementation. The database schema updates, API routes, UI components, and configuration are all within the scope of the linked issue.

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

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

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

❤️ Share

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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⭐ Star Required

Hi @avantikarathi01! Thank you for your contribution to DoubtDesk.

Before we can complete the review and merge this pull request, please star the DoubtDesk repository.

Once you have starred the repository, please drop a comment here saying "done" and we will proceed with reviewing your PR. Thank you!

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hello there! 🎉 Thank you so much for your first pull request to DoubtDesk!

We really appreciate your contribution. A maintainer will review your code soon. If you are participating in GSSoC, ensure your PR is linked to an open issue. Please make sure you have followed all rules in our Contributing Guidelines. Happy coding!

@github-actions

Copy link
Copy Markdown

@coderabbitai review

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jun 13, 2026
@github-actions github-actions Bot removed the size:L This PR changes 100-499 lines, ignoring generated files label Jun 13, 2026
const user = await currentUser();
if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 });

const email = user.emailAddresses[0]?.emailAddress;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: This route identifies the user using emailAddresses[0] instead of the primary email used elsewhere in the profile APIs. If the first email is not the primary one, reads/updates can target no row or the wrong row, causing personalization preferences to appear unsaved. Use the same primary-email source as other profile endpoints and validate it exists. [incorrect variable usage]

Severity Level: Major ⚠️
- ⚠️ Personalization API may target non-existent users with multiple emails.
- ⚠️ Preferences GET can return empty object despite saved row.
- ⚠️ Preferences POST may succeed without updating any record.
Steps of Reproduction ✅
1. Authenticate as a user and hit the profile API at `GET /api/profile` implemented in
`src/app/api/profile/route.ts:10-27`, which derives `email` from
`clerkUser?.primaryEmailAddress?.emailAddress` (line 18) and uses that to look up the
`usersTable` row (line 26).

2. Configure the Clerk account so that `user.emailAddresses[0]?.emailAddress` differs from
`user.primaryEmailAddress?.emailAddress` (Clerk supports multiple email addresses; only
the primary is used by `/api/profile` and `/api/profile/stats` at
`src/app/api/profile/stats/route.ts:15-19`).

3. While logged in, call the new preferences endpoint `GET /api/profile/preference`
implemented in `src/app/api/profile/preference/route.ts:7-25`; inside, it computes `const
email = user.emailAddresses[0]?.emailAddress;` (line 14) and queries `usersTable.email`
with that value (lines 16-23), which does not match the primary-email-backed row in
`usersTable` (`email` column defined at `src/configs/schema.ts:4-7`).

4. Observe that the handler returns `{ preferences: {} }` (line 25) even though a valid
user row exists, and that subsequent `POST /api/profile/preference` calls (lines 28-47)
also use `user.emailAddresses[0]?.emailAddress` (line 35) and therefore update zero rows,
making avatar/banner changes appear unsaved for such users.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/app/api/profile/preference/route.ts
**Line:** 14:14
**Comment:**
	*Incorrect Variable Usage: This route identifies the user using `emailAddresses[0]` instead of the primary email used elsewhere in the profile APIs. If the first email is not the primary one, reads/updates can target no row or the wrong row, causing personalization preferences to appear unsaved. Use the same primary-email source as other profile endpoints and validate it exists.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment thread src/app/profile/page.tsx
Comment on lines +106 to +107
const [avatarPreference, setAvatarPreference] = useState("default");
const [bannerPreference, setBannerPreference] = useState("none");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Personalization state is hardcoded to defaults and never hydrated from the new GET preferences API, so previously saved avatar/banner choices are lost on reload and the UI always starts from default values. Load persisted preferences during profile fetch and initialize these states from the API response. [incomplete implementation]

Severity Level: Major ⚠️
- ⚠️ Profile page ignores stored avatar and banner preferences.
- ⚠️ Users see default styling after reload despite saved choices.
- ⚠️ Preferences GET endpoint unused, reducing personalization feature completeness.
Steps of Reproduction ✅
1. Persist non-default personalization values for a user by updating
`usersTable.avatarPreference` and `usersTable.bannerPreference` in the database (columns
defined in `src/configs/schema.ts:21-22`) or by calling `POST /api/profile/preference`
implemented in `src/app/api/profile/preference/route.ts:28-47` with `{ avatarPreference,
bannerPreference }`.

2. Verify that `GET /api/profile/preference` (same file, lines 7-25) returns these stored
values by authenticating and calling the endpoint; it selects `avatarPreference` and
`bannerPreference` from `usersTable` (lines 16-23) and responds with `{ preferences:
result[0] ?? {} }` (line 25).

3. Navigate to the `/profile` page rendered by `ProfilePage` in
`src/app/profile/page.tsx:105`; on initial render, React initializes `avatarPreference`
and `bannerPreference` state to `"default"` and `"none"` respectively (lines 106-107) and
never updates them from any API response.

4. During profile loading, `fetchProfile()` only calls `/api/profile` and
`/api/profile/stats` (lines 167-175) and sets `profileData` and `activityStats` (lines
177-181); there is no call to `/api/profile/preference`, and neither `AvatarSelector` nor
`BannerSelector` (`src/components/AvatarSelector.tsx`,
`src/components/BannerSelector.tsx`) perform any fetching—they simply render based on the
`selected` prop. As a result, when the page loads, the live preview and selectors always
show the `"default"` avatar and `"none"` banner, ignoring the non-default values returned
in step 2.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/app/profile/page.tsx
**Line:** 106:107
**Comment:**
	*Incomplete Implementation: Personalization state is hardcoded to defaults and never hydrated from the new GET preferences API, so previously saved avatar/banner choices are lost on reload and the UI always starts from default values. Load persisted preferences during profile fetch and initialize these states from the API response.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment thread src/app/profile/page.tsx
const handleSavePersonalization = async () => {
setIsSavingPersonalization(true);
try {
const res = await fetch("/api/profile/preferences", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The client posts personalization data to /api/profile/preferences, but the new API route is implemented at /api/profile/preference (singular). This mismatch makes save requests hit a non-existent endpoint and always fail at runtime; align the client URL with the actual route path. [api mismatch]

Severity Level: Critical 🚨
- ❌ Save Preferences button always fails with 404 backend response.
- ⚠️ Users cannot persist avatar or banner personalization choices.
- ⚠️ Any future client reusing endpoint inherits broken path.
Steps of Reproduction ✅
1. Navigate to the `/profile` page rendered by `ProfilePage` in
`src/app/profile/page.tsx:105` as an authenticated user (the route is wired via Next.js
app router).

2. Switch to the "Style" tab by clicking the `TabsTrigger` with value `"personalization"`
(around `src/app/profile/page.tsx:368-370`) to reveal the personalization UI and change
avatar or banner via `AvatarSelector` and `BannerSelector` (lines 500-503).

3. Click the "Save Preferences" button defined in `TabsContent value="personalization"` at
`src/app/profile/page.tsx:506-511`, whose `onClick` handler calls
`handleSavePersonalization` (defined at lines 143-161).

4. Inside `handleSavePersonalization`, the client performs
`fetch("/api/profile/preferences", { method: "POST", ... })` (line 146), but the only
matching API route on disk is `src/app/api/profile/preference/route.ts` (singular,
discovered via `ls src/app/api/profile`), so Next.js returns 404; as `res.ok` is false,
the code executes `toast.error("Failed to save preferences")` (lines 151-155) and no
personalization data is persisted.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/app/profile/page.tsx
**Line:** 146:146
**Comment:**
	*Api Mismatch: The client posts personalization data to `/api/profile/preferences`, but the new API route is implemented at `/api/profile/preference` (singular). This mismatch makes save requests hit a non-existent endpoint and always fail at runtime; align the client URL with the actual route path.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment thread src/app/profile/page.tsx
Comment on lines +146 to +150
const res = await fetch("/api/profile/preferences", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ avatarPreference, bannerPreference }),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Architect Review — CRITICAL

The personalization save handler posts to /api/profile/preferences, but the backend route added in this PR is /api/profile/preference (singular), so in normal usage the save request will hit a non-existent endpoint and fail.

Suggestion: Align the frontend fetch URL and the Next.js app route path to the same contract (singular vs plural) and add a small integration test for the personalization save flow so endpoint drift is caught automatically.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** src/app/profile/page.tsx
**Line:** 146:150
**Comment:**
	*CRITICAL: The personalization save handler posts to `/api/profile/preferences`, but the backend route added in this PR is `/api/profile/preference` (singular), so in normal usage the save request will hit a non-existent endpoint and fail.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

Comment thread src/app/profile/page.tsx
Comment on lines +486 to +503
<div className={`w-full h-20 transition-all duration-500 ${BANNER_GRADIENTS[bannerPreference] ?? BANNER_GRADIENTS.none}`} />
<div className="bg-slate-50 dark:bg-zinc-900 px-4 pb-4 flex items-center gap-3">
<div className="w-14 h-14 rounded-full bg-white dark:bg-zinc-800 border-4 border-white dark:border-zinc-900 -mt-7 flex items-center justify-center text-2xl shadow">
{user.name.charAt(0)}
</div>
<div className="mt-1">
<p className="font-bold text-slate-900 dark:text-white text-sm">{user.name}</p>
<p className="text-xs text-slate-500 dark:text-zinc-400">{user.role}</p>
</div>
</div>
</div>
</div>

{/* Avatar Selector */}
<AvatarSelector selected={avatarPreference} onSelect={setAvatarPreference} />

{/* Banner Selector */}
<BannerSelector selected={bannerPreference} onSelect={setBannerPreference} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Architect Review — HIGH

Avatar selection is captured in state and sent to the backend, but both the live preview and main profile header still render only the Clerk image or user.name.charAt(0), so the chosen avatarPreference is never applied visually.

Suggestion: Define a mapping from avatarPreference to the rendered avatar (e.g., emoji or themed avatar) and use it consistently in the live preview and profile header so users can immediately see the effect of their selection.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** src/app/profile/page.tsx
**Line:** 486:503
**Comment:**
	*HIGH: Avatar selection is captured in state and sent to the backend, but both the live preview and main profile header still render only the Clerk image or `user.name.charAt(0)`, so the chosen `avatarPreference` is never applied visually.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

Comment on lines +14 to +35
const email = user.emailAddresses[0]?.emailAddress;

const result = await db
.select({
avatarPreference: usersTable.avatarPreference,
bannerPreference: usersTable.bannerPreference,
})
.from(usersTable)
.where(eq(usersTable.email, email))
.limit(1);

return NextResponse.json({ preferences: result[0] ?? {} });
}

export async function POST(req: NextRequest) {
const { userId } = await auth();
if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

const user = await currentUser();
if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 });

const email = user.emailAddresses[0]?.emailAddress;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Architect Review — HIGH

This route derives the lookup key from user.emailAddresses[0]?.emailAddress instead of the established primaryEmailAddress?.emailAddress pattern used in other profile APIs, so it can read or update the wrong usersTable row when email ordering differs and lacks a guard for missing email.

Suggestion: Match the existing API convention by using clerkUser?.primaryEmailAddress?.emailAddress with an explicit missing-email guard before querying/updating usersTable, keeping this route consistent with src/app/api/profile/route.ts and src/app/api/profile/stats/route.ts.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** src/app/api/profile/preference/route.ts
**Line:** 14:35
**Comment:**
	*HIGH: This route derives the lookup key from `user.emailAddresses[0]?.emailAddress` instead of the established `primaryEmailAddress?.emailAddress` pattern used in other profile APIs, so it can read or update the wrong `usersTable` row when email ordering differs and lacks a guard for missing email.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

Comment thread .env.example
# 1. Database (Neon PostgreSQL)
# Required for all data storage. Ensure you append ?sslmode=require
DATABASE_URL=postgresql://user:password@host/database?sslmode=require
DATABASE_URL=postgresql://neondb_owner:npg_LIf6Jch9DEgz@ep-blue-sunset-aid5472c-pooler.c-4.us-east-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Architect Review — CRITICAL

A concrete Neon PostgreSQL connection string with real-looking credentials has been committed into .env.example, which should only contain placeholders and not actual secrets, risking credential exposure.

Suggestion: Replace this connection string with a sanitized placeholder format and rotate/revoke the exposed database credentials immediately.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** .env.example
**Line:** 8:8
**Comment:**
	*CRITICAL: A concrete Neon PostgreSQL connection string with real-looking credentials has been committed into `.env.example`, which should only contain placeholders and not actual secrets, risking credential exposure.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

Comment on lines +4 to +35
{ id: "none", label: "No Banner", gradient: "bg-slate-100 dark:bg-zinc-900" },
{ id: "ocean", label: "Ocean", gradient: "bg-gradient-to-r from-blue-400 to-cyan-400" },
{ id: "sunset", label: "Sunset", gradient: "bg-gradient-to-r from-orange-400 to-pink-500" },
{ id: "forest", label: "Forest", gradient: "bg-gradient-to-r from-green-400 to-emerald-600" },
{ id: "galaxy", label: "Galaxy", gradient: "bg-gradient-to-r from-purple-600 to-indigo-700" },
{ id: "fire", label: "Fire", gradient: "bg-gradient-to-r from-red-500 to-orange-500" },
];

interface BannerSelectorProps {
selected: string;
onSelect: (id: string) => void;
}

export default function BannerSelector({ selected, onSelect }: BannerSelectorProps) {
return (
<div>
<h3 className="text-sm font-bold text-slate-700 dark:text-zinc-300 mb-3">
Choose Banner
</h3>
<div className="grid grid-cols-2 gap-3">
{BANNERS.map((banner) => (
<button
key={banner.id}
onClick={() => onSelect(banner.id)}
className={`relative h-16 rounded-xl border-2 transition-all duration-200 ${banner.gradient}
${selected === banner.id
? "border-blue-500 scale-[1.02]"
: "border-slate-200 dark:border-zinc-700 hover:border-blue-300"
}`}
>
<span className="absolute bottom-1 left-2 text-xs font-bold text-white drop-shadow">
{banner.label}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎨 Design Review — HIGH

Do you think the white banner-label text on the very light "No Banner" tile (bg-slate-100) remains readable enough, or is it likely to fall below WCAG 2.1 AA contrast for small text (1.4.3) despite the drop shadow?

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a **Design Review** comment — a question about the UX/design of frontend code. It is intentionally framed as a question, not a prescription. The author may agree or disagree.

**Path:** src/components/BannerSelector.tsx
**Line:** 4:35
**Comment:**
	*HIGH: Do you think the white banner-label text on the very light "No Banner" tile (`bg-slate-100`) remains readable enough, or is it likely to fall below WCAG 2.1 AA contrast for small text (1.4.3) despite the drop shadow?

- If you agree with the proposal, apply a small, localized change (swap a color token, bump a font size, adjust spacing, add an aria-label, etc.).
- If you disagree, or the answer depends on a design decision a human should make, explain your reasoning and ask the user how to proceed.
Do NOT refactor surrounding components or apply other design changes that weren't asked about.

Comment thread src/app/profile/page.tsx
}

export default function ProfilePage() {
const [avatarPreference, setAvatarPreference] = useState("default");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 Architect Review — HIGH

The profile page initializes avatarPreference/bannerPreference to hardcoded defaults and never hydrates them from backend data, so even though /api/profile/preference persists these fields, saved personalization choices are lost on reload.

Suggestion: Load persisted personalization via the existing profile fetch (or a dedicated /api/profile/preference GET) and initialize avatarPreference/bannerPreference state from the server response before rendering the header and live preview.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** src/app/profile/page.tsx
**Line:** 106:182
**Comment:**
	*HIGH: The profile page initializes `avatarPreference`/`bannerPreference` to hardcoded defaults and never hydrates them from backend data, so even though `/api/profile/preference` persists these fields, saved personalization choices are lost on reload.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

@codeant-ai

codeant-ai Bot commented Jun 13, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@avantikarathi01

Copy link
Copy Markdown
Author

Done! ⭐ I have starred the repository.

Thank you for the opportunity to contribute to DoubtDesk under GSSoC'26!
Looking forward to your review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 (1)
src/app/profile/page.tsx (1)

177-182: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Hydrate personalization state from fetched profile data.

On Lines 177–182, profile data is loaded but avatarPreference and bannerPreference state are never initialized from server values. The page therefore falls back to "default"/"none" after reload even when saved values exist.

Suggested fix
       .then(([profileRes, statsRes]: [ProfileData, { success: boolean; stats: ActivityStats }]) => {
         if (profileRes.user && statsRes.success) {
           setProfileData(profileRes);
           setActivityStats(statsRes.stats);
           setEmailNotificationsEnabled(profileRes.user.emailNotificationsEnabled ?? true);
+          setAvatarPreference(profileRes.user.avatarPreference ?? "default");
+          setBannerPreference(profileRes.user.bannerPreference ?? "none");
         } else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/profile/page.tsx` around lines 177 - 182, When handling the fetch
result in the .then callback (where setProfileData, setActivityStats, and
setEmailNotificationsEnabled are called), also hydrate the personalization state
by calling setAvatarPreference(profileRes.user.avatarPreference ?? "default")
and setBannerPreference(profileRes.user.bannerPreference ?? "none") so saved
server values are applied on load instead of falling back to "default"/"none".
🧹 Nitpick comments (2)
.env.example (1)

6-8: ⚡ Quick win

Clarify Comment & Format for Neon Connection String

The comment on line 7 mentions appending ?sslmode=require, but the actual line 8 now includes both sslmode=require and channel_binding=require. Update the inline comment to reflect both query parameters so future developers understand the full requirement.

Proposed update to the comment
  # 1. Database (Neon PostgreSQL)
  # Required for all data storage. Ensure you append ?sslmode=require&channel_binding=require
  DATABASE_URL=postgresql://neondb_owner:YOUR_PASSWORD_HERE@ep-your-endpoint.c-4.us-east-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.example around lines 6 - 8, Update the inline comment above
DATABASE_URL to accurately reflect the required Neon connection query
parameters: change the note that says "Ensure you append ?sslmode=require" to
mention both parameters, e.g. "Ensure you append
?sslmode=require&channel_binding=require", so the comment matches the actual
DATABASE_URL value and guides future developers to include both sslmode and
channel_binding.
src/components/AvatarSelector.tsx (1)

25-25: ⚡ Quick win

Make the avatar grid responsive on small screens.

On Line 25, grid-cols-4 is fixed and can get cramped on phones. Use a breakpoint-based column count.

Suggested fix
-      <div className="grid grid-cols-4 gap-3">
+      <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">

As per coding guidelines, “**/*.tsx: Review for: Responsive design considerations”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/AvatarSelector.tsx` at line 25, The avatar grid in the
AvatarSelector component currently uses a fixed "grid grid-cols-4 gap-3" which
is cramped on small screens; update the container div's Tailwind classes to use
responsive column counts (for example: "grid grid-cols-2 sm:grid-cols-3
md:grid-cols-4 gap-3") so phones show fewer columns and larger avatars while
tablets/desktops keep more columns. Locate the div with className "grid
grid-cols-4 gap-3" in AvatarSelector.tsx and replace the class string
accordingly.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.env.example:
- Line 8: Replace the real-looking credentials in the DATABASE_URL value in
.env.example with an obvious placeholder (e.g.,
postgresql://neondb_owner:YOUR_PASSWORD_HERE@ep-your-endpoint.example/neondb?sslmode=require&channel_binding=require),
preserve the ?sslmode... query string, and add a short inline comment warning to
replace with real credentials before use; no change needed to the code that
reads process.env.DATABASE_URL (it only trims/returns the value).

In `@src/app/api/profile/preference/route.ts`:
- Around line 14-15: The code reads const email =
user.emailAddresses[0]?.emailAddress; but doesn't guard that email may be
undefined before DB operations; update the route handlers in route.ts to
explicitly check that user and email exist (e.g., if (!user || !email) return
NextResponse.json({ error: 'Email not found' }, { status: 400 }) or 404) before
calling any DB query/update; apply this guard in both locations where email is
derived (the initial read at const email = ... and the later use around lines
35–36) so no database access runs with an unresolved identity key.
- Line 28: The POST handler (function POST) lives under the singular route but
the frontend posts to the plural endpoint; fix by aligning names: move/rename
the API route from "preference" to "preferences" so the POST(req: NextRequest)
handler is exposed at /api/profile/preferences (or alternatively change the
frontend to POST to /api/profile/preference if you prefer the singular); ensure
only one canonical path is used and update imports/route folder name accordingly
so the POST handler receives the requests.
- Around line 36-44: Validate and sanitize avatarPreference and bannerPreference
before calling db.update: define allowed value sets (e.g.,
ALLOWED_AVATAR_PREFERENCES, ALLOWED_BANNER_PREFERENCES) and a MAX_PREF_LENGTH,
check that each incoming value is a string, its length <= MAX_PREF_LENGTH, and
is one of the allowed values, and only include the property in the .set payload
when validation passes (otherwise reject the request with 4xx). Update the logic
around req.json(), avatarPreference/bannerPreference extraction, and the
db.update(usersTable).set(...) call in route.ts to perform these checks (or
return an error) so invalid or oversized values are never written to the DB.

In `@src/app/profile/page.tsx`:
- Around line 488-490: The preview avatar currently always renders
user.name.charAt(0); change the JSX in the avatar preview div to respect
avatarPreference by conditionally rendering the selected avatar (e.g., if
avatarPreference is a URL/image show an <img> with that src, if it's an index or
symbol render the corresponding avatar glyph/component, otherwise fallback to
user.name.charAt(0)). Update the preview div (the element that currently
contains {user.name.charAt(0)}) to read avatarPreference and render the
appropriate content so the instant preview reflects the selected avatar choice.
- Line 97: The retry button currently uses a generic aria-label ("Interactive
button") which overrides the visible text; locate the retry button element in
page.tsx (the element with aria-label="Interactive button" and visible text
"Retry Sync Connection") and either remove the aria-label entirely so the
visible text is used as the accessible name, or replace it with a descriptive
label such as aria-label="Retry Sync Connection" (or a more specific
description) so the accessible name matches the visible text and intent.
- Around line 146-150: The client is POSTing to the wrong route string: update
the fetch call that currently sends to "/api/profile/preferences" (in the code
that builds body JSON with avatarPreference and bannerPreference) to use the
server's actual route "/api/profile/preference" (or alternatively rename the
server route to match the plural path) so the client and API path match and the
POST stops returning 404.

In `@src/components/AvatarSelector.tsx`:
- Around line 27-35: The avatar buttons in AvatarSelector currently only change
visual styling; add accessibility state by setting aria-pressed on the button to
reflect selection (e.g., aria-pressed={selected === avatar.id}) so assistive
tech can identify the active avatar; update the JSX for the button rendered in
the map (the element using onClick={() => onSelect(avatar.id)} and className
conditional on selected) to include this attribute and ensure it updates
whenever selected changes.

In `@src/components/BannerSelector.tsx`:
- Line 4: The "No Banner" option in BannerSelector uses a light gradient ({ id:
"none", label: "No Banner", gradient: "bg-slate-100 dark:bg-zinc-900" }) but the
label rendering uses a hardcoded "text-white" class, causing low contrast;
update the BannerSelector rendering to avoid hardcoded "text-white" by either
adding a textColor property to the option objects (e.g. textColor:
"text-slate-700 dark:text-white" for the "none" option) and using that class
when rendering the label, or detect the option id "none" and apply a conditional
class ("text-slate-700 dark:text-white") instead of "text-white" so light
backgrounds get a dark text color while dark backgrounds keep white text; ensure
the change replaces the static "text-white" usage in the label rendering code.

---

Outside diff comments:
In `@src/app/profile/page.tsx`:
- Around line 177-182: When handling the fetch result in the .then callback
(where setProfileData, setActivityStats, and setEmailNotificationsEnabled are
called), also hydrate the personalization state by calling
setAvatarPreference(profileRes.user.avatarPreference ?? "default") and
setBannerPreference(profileRes.user.bannerPreference ?? "none") so saved server
values are applied on load instead of falling back to "default"/"none".

---

Nitpick comments:
In @.env.example:
- Around line 6-8: Update the inline comment above DATABASE_URL to accurately
reflect the required Neon connection query parameters: change the note that says
"Ensure you append ?sslmode=require" to mention both parameters, e.g. "Ensure
you append ?sslmode=require&channel_binding=require", so the comment matches the
actual DATABASE_URL value and guides future developers to include both sslmode
and channel_binding.

In `@src/components/AvatarSelector.tsx`:
- Line 25: The avatar grid in the AvatarSelector component currently uses a
fixed "grid grid-cols-4 gap-3" which is cramped on small screens; update the
container div's Tailwind classes to use responsive column counts (for example:
"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3") so phones show fewer
columns and larger avatars while tablets/desktops keep more columns. Locate the
div with className "grid grid-cols-4 gap-3" in AvatarSelector.tsx and replace
the class string accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cd63ee1c-4b85-4c00-83ba-439c5b1970cd

📥 Commits

Reviewing files that changed from the base of the PR and between 30c282f and 2a6a1cc.

📒 Files selected for processing (6)
  • .env.example
  • src/app/api/profile/preference/route.ts
  • src/app/profile/page.tsx
  • src/components/AvatarSelector.tsx
  • src/components/BannerSelector.tsx
  • src/configs/schema.ts

Comment thread .env.example
# 1. Database (Neon PostgreSQL)
# Required for all data storage. Ensure you append ?sslmode=require
DATABASE_URL=postgresql://user:password@host/database?sslmode=require
DATABASE_URL=postgresql://neondb_owner:npg_LIf6Jch9DEgz@ep-blue-sunset-aid5472c-pooler.c-4.us-east-1.aws.neon.tech/neondb?sslmode=require&channel_binding=require

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== .env.example (lines 1-20) =="
nl -ba .env.example | sed -n '1,30p'

echo
echo "== Search for the exact Neon password string pattern (npg_) =="
rg -n --hidden --no-ignore -S "npg_[A-Za-z0-9]+" . || true

echo
echo "== Search for DATABASE_URL occurrences =="
rg -n --hidden --no-ignore "DATABASE_URL" . || true

echo
echo "== Locate and inspect getDatabaseUrl() implementation =="
fd -a "database-url.ts" . || true
rg -n --hidden --no-ignore "getDatabaseUrl" src || true

# Print the relevant file(s) with line numbers (only small sections)
for f in $(fd -a "database-url.ts" src . 2>/dev/null || true); do
  echo
  echo "== $f (full file if <= 250 lines, else first 250) =="
  lines=$(wc -l < "$f")
  if [ "$lines" -le 250 ]; then
    nl -ba "$f"
  else
    nl -ba "$f" | sed -n '1,250p'
  fi
done

echo
echo "== Inspect drizzle/db integration file(s) that reference getDatabaseUrl() =="
rg -n --hidden --no-ignore "getDatabaseUrl\\(" src || true
fd -a "db.tsx" src . || true

Repository: knoxiboy/DoubtDesk

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Repo files: check .env.example exists =="
ls -la
test -f ".env.example" && echo ".env.example: OK" || (echo ".env.example: MISSING" && exit 0)

echo
echo "== .env.example (first 30 lines with line numbers via awk) =="
awk 'NR<=30{printf "%d:%s\n", NR, $0}' .env.example

echo
echo "== Search for Neon-like password strings (npg_...) =="
rg -n --hidden --no-ignore -S "npg_[A-Za-z0-9]+" . || true

echo
echo "== Search for DATABASE_URL occurrences =="
rg -n --hidden --no-ignore "DATABASE_URL" . || true

echo
echo "== Locate and inspect getDatabaseUrl() implementation =="
fd -a "database-url.ts" . || true
rg -n --hidden --no-ignore "getDatabaseUrl" src . || true

echo
echo "== Print src/configs/database-url.ts if present (first 220 lines) =="
f=$(fd -a "database-url.ts" src . 2>/dev/null | head -n 1 || true)
if [ -n "${f:-}" ] && [ -f "$f" ]; then
  lines=$(wc -l < "$f")
  if [ "$lines" -le 220 ]; then
    awk '{printf "%d:%s\n", NR, $0}' "$f"
  else
    awk 'NR<=220{printf "%d:%s\n", NR, $0}' "$f"
  fi
else
  echo "src/configs/database-url.ts not found by fd"
fi

echo
echo "== Inspect drizzle/db integration that references getDatabaseUrl() =="
rg -n --hidden --no-ignore "getDatabaseUrl\\(" src || true
fd -a "db.tsx" src . || true
for f in $(fd -a "db.tsx" src . 2>/dev/null | head -n 20); do
  echo
  echo "== File: $f (first 220 lines) =="
  awk 'NR<=220{printf "%d:%s\n", NR, $0}' "$f"
done

Repository: knoxiboy/DoubtDesk

Length of output: 12748


Replace the real-looking DATABASE_URL credentials in .env.example (line 8) with obvious placeholders.

  • .env.example commits a Neon connection string with plaintext credentials (neondb_owner + password starting npg_...). An .env.example should never contain functional/secret-like values—if these are real (or could be), rotate/revoke them before merging.
  • src/configs/database-url.ts only trims and returns process.env.DATABASE_URL unchanged, so the ?sslmode=require&channel_binding=require query parameters are preserved (no functional issue there).

Update line 8 to a clearly fake placeholder value (e.g., postgresql://neondb_owner:YOUR_PASSWORD_HERE@ep-your-endpoint.../neondb?sslmode=require&channel_binding=require) and add a short comment warning to replace before use.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.env.example at line 8, Replace the real-looking credentials in the
DATABASE_URL value in .env.example with an obvious placeholder (e.g.,
postgresql://neondb_owner:YOUR_PASSWORD_HERE@ep-your-endpoint.example/neondb?sslmode=require&channel_binding=require),
preserve the ?sslmode... query string, and add a short inline comment warning to
replace with real credentials before use; no change needed to the code that
reads process.env.DATABASE_URL (it only trims/returns the value).

Comment on lines +14 to +15
const email = user.emailAddresses[0]?.emailAddress;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard missing user email before DB access.

Line 14 and Line 35 can resolve to undefined. Add an explicit check and return 400 (or 404) before querying/updating so DB operations never run with an unresolved identity key.

Proposed patch
 export async function GET() {
   const { userId } = await auth();
   if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

   const user = await currentUser();
   if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 });

   const email = user.emailAddresses[0]?.emailAddress;
+  if (!email) return NextResponse.json({ error: "Email not found" }, { status: 400 });

   const result = await db
@@
 export async function POST(req: NextRequest) {
@@
   const user = await currentUser();
   if (!user) return NextResponse.json({ error: "User not found" }, { status: 404 });

   const email = user.emailAddresses[0]?.emailAddress;
+  if (!email) return NextResponse.json({ error: "Email not found" }, { status: 400 });
   const body = await req.json();

Also applies to: 35-36

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/profile/preference/route.ts` around lines 14 - 15, The code reads
const email = user.emailAddresses[0]?.emailAddress; but doesn't guard that email
may be undefined before DB operations; update the route handlers in route.ts to
explicitly check that user and email exist (e.g., if (!user || !email) return
NextResponse.json({ error: 'Email not found' }, { status: 400 }) or 404) before
calling any DB query/update; apply this guard in both locations where email is
derived (the initial read at const email = ... and the later use around lines
35–36) so no database access runs with an unresolved identity key.

return NextResponse.json({ preferences: result[0] ?? {} });
}

export async function POST(req: NextRequest) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix endpoint contract mismatch (/preference vs /preferences).

Line 28 defines handlers under /api/profile/preference (singular), but src/app/profile/page.tsx posts to /api/profile/preferences (plural). This breaks personalization save requests. Align both sides to one path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/profile/preference/route.ts` at line 28, The POST handler
(function POST) lives under the singular route but the frontend posts to the
plural endpoint; fix by aligning names: move/rename the API route from
"preference" to "preferences" so the POST(req: NextRequest) handler is exposed
at /api/profile/preferences (or alternatively change the frontend to POST to
/api/profile/preference if you prefer the singular); ensure only one canonical
path is used and update imports/route folder name accordingly so the POST
handler receives the requests.

Comment on lines +36 to +44
const body = await req.json();
const { avatarPreference, bannerPreference } = body;

await db
.update(usersTable)
.set({
...(avatarPreference && { avatarPreference }),
...(bannerPreference && { bannerPreference }),
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate personalization payload before update.

Line 36–44 accepts arbitrary truthy values. Enforce allowed avatar/banner values (and max length) before writing, otherwise invalid preferences can be persisted or fail at DB constraint boundaries.

Proposed patch
 import { NextRequest, NextResponse } from "next/server";
 import { auth, currentUser } from "`@clerk/nextjs/server`";
 import { db } from "`@/configs/db`";
 import { usersTable } from "`@/configs/schema`";
 import { eq } from "drizzle-orm";
+import { z } from "zod";

+const preferenceSchema = z.object({
+  avatarPreference: z.enum(["default", "avatar1", "avatar2", "avatar3"]).optional(),
+  bannerPreference: z.enum(["none", "educational", "technology", "science", "minimal"]).optional(),
+});
@@
-  const body = await req.json();
-  const { avatarPreference, bannerPreference } = body;
+  const body = await req.json();
+  const parsed = preferenceSchema.safeParse(body);
+  if (!parsed.success) {
+    return NextResponse.json({ error: "Invalid preferences payload" }, { status: 400 });
+  }
+  const { avatarPreference, bannerPreference } = parsed.data;
📝 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
const body = await req.json();
const { avatarPreference, bannerPreference } = body;
await db
.update(usersTable)
.set({
...(avatarPreference && { avatarPreference }),
...(bannerPreference && { bannerPreference }),
})
import { NextRequest, NextResponse } from "next/server";
import { auth, currentUser } from "`@clerk/nextjs/server`";
import { db } from "`@/configs/db`";
import { usersTable } from "`@/configs/schema`";
import { eq } from "drizzle-orm";
import { z } from "zod";
const preferenceSchema = z.object({
avatarPreference: z.enum(["default", "avatar1", "avatar2", "avatar3"]).optional(),
bannerPreference: z.enum(["none", "educational", "technology", "science", "minimal"]).optional(),
});
const body = await req.json();
const parsed = preferenceSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({ error: "Invalid preferences payload" }, { status: 400 });
}
const { avatarPreference, bannerPreference } = parsed.data;
await db
.update(usersTable)
.set({
...(avatarPreference && { avatarPreference }),
...(bannerPreference && { bannerPreference }),
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/profile/preference/route.ts` around lines 36 - 44, Validate and
sanitize avatarPreference and bannerPreference before calling db.update: define
allowed value sets (e.g., ALLOWED_AVATAR_PREFERENCES,
ALLOWED_BANNER_PREFERENCES) and a MAX_PREF_LENGTH, check that each incoming
value is a string, its length <= MAX_PREF_LENGTH, and is one of the allowed
values, and only include the property in the .set payload when validation passes
(otherwise reject the request with 4xx). Update the logic around req.json(),
avatarPreference/bannerPreference extraction, and the
db.update(usersTable).set(...) call in route.ts to perform these checks (or
return an error) so invalid or oversized values are never written to the DB.

Comment thread src/app/profile/page.tsx
onClick={onRetry}
className="inline-flex items-center gap-2 rounded-xl border border-slate-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 px-4 py-2.5 text-sm font-semibold text-slate-700 dark:text-zinc-300 shadow-sm transition-all duration-200 hover:bg-slate-50 dark:hover:bg-zinc-800 active:scale-[0.98]"
aria-label="Interactive button">
aria-label="Interactive button">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a descriptive aria-label on the retry button.

Line 97 sets aria-label="Interactive button", which is generic and overrides the meaningful visible text (“Retry Sync Connection”). Use a specific label or remove aria-label entirely.

Suggested fix
-        aria-label="Interactive button">
+        aria-label="Retry sync connection">

As per coding guidelines, “**/*.tsx: Review for: Accessibility (aria labels, keyboard navigation)”.

📝 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
aria-label="Interactive button">
aria-label="Retry sync connection">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/profile/page.tsx` at line 97, The retry button currently uses a
generic aria-label ("Interactive button") which overrides the visible text;
locate the retry button element in page.tsx (the element with
aria-label="Interactive button" and visible text "Retry Sync Connection") and
either remove the aria-label entirely so the visible text is used as the
accessible name, or replace it with a descriptive label such as
aria-label="Retry Sync Connection" (or a more specific description) so the
accessible name matches the visible text and intent.

Source: Coding guidelines

Comment thread src/app/profile/page.tsx
Comment on lines +146 to +150
const res = await fetch("/api/profile/preferences", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ avatarPreference, bannerPreference }),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

POST endpoint path does not match the implemented API route.

On Line 146, the client calls /api/profile/preferences, but the provided handler is at src/app/api/profile/preference/route.ts (singular). This likely returns 404 and prevents saving personalization.

Suggested fix
-      const res = await fetch("/api/profile/preferences", {
+      const res = await fetch("/api/profile/preference", {
📝 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
const res = await fetch("/api/profile/preferences", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ avatarPreference, bannerPreference }),
});
const res = await fetch("/api/profile/preference", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ avatarPreference, bannerPreference }),
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/profile/page.tsx` around lines 146 - 150, The client is POSTing to
the wrong route string: update the fetch call that currently sends to
"/api/profile/preferences" (in the code that builds body JSON with
avatarPreference and bannerPreference) to use the server's actual route
"/api/profile/preference" (or alternatively rename the server route to match the
plural path) so the client and API path match and the POST stops returning 404.

Comment thread src/app/profile/page.tsx
Comment on lines +488 to +490
<div className="w-14 h-14 rounded-full bg-white dark:bg-zinc-800 border-4 border-white dark:border-zinc-900 -mt-7 flex items-center justify-center text-2xl shadow">
{user.name.charAt(0)}
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Live preview does not reflect selected avatar choice.

On Lines 488–490, preview avatar renders user.name.charAt(0) regardless of avatarPreference. This breaks the “instant preview” expectation while selecting avatars (Line 500).

Also applies to: 500-500

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/profile/page.tsx` around lines 488 - 490, The preview avatar
currently always renders user.name.charAt(0); change the JSX in the avatar
preview div to respect avatarPreference by conditionally rendering the selected
avatar (e.g., if avatarPreference is a URL/image show an <img> with that src, if
it's an index or symbol render the corresponding avatar glyph/component,
otherwise fallback to user.name.charAt(0)). Update the preview div (the element
that currently contains {user.name.charAt(0)}) to read avatarPreference and
render the appropriate content so the instant preview reflects the selected
avatar choice.

Comment on lines +27 to +35
<button
key={avatar.id}
onClick={() => onSelect(avatar.id)}
className={`flex flex-col items-center gap-1 p-3 rounded-xl border-2 transition-all duration-200
${selected === avatar.id
? "border-blue-500 bg-blue-50 dark:bg-blue-950/30"
: "border-slate-200 dark:border-zinc-800 hover:border-blue-300"
}`}
>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Expose selected state to assistive tech on avatar buttons.

On Line 27, the button only changes style when selected. Add semantic state (aria-pressed) so screen readers can identify the active avatar.

Suggested fix
           <button
             key={avatar.id}
             onClick={() => onSelect(avatar.id)}
+            aria-pressed={selected === avatar.id}
+            aria-label={`Select ${avatar.label} avatar`}
             className={`flex flex-col items-center gap-1 p-3 rounded-xl border-2 transition-all duration-200

As per coding guidelines, “**/*.tsx: Review for: Accessibility (aria labels, keyboard navigation)”.

📝 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
<button
key={avatar.id}
onClick={() => onSelect(avatar.id)}
className={`flex flex-col items-center gap-1 p-3 rounded-xl border-2 transition-all duration-200
${selected === avatar.id
? "border-blue-500 bg-blue-50 dark:bg-blue-950/30"
: "border-slate-200 dark:border-zinc-800 hover:border-blue-300"
}`}
>
<button
key={avatar.id}
onClick={() => onSelect(avatar.id)}
aria-pressed={selected === avatar.id}
aria-label={`Select ${avatar.label} avatar`}
className={`flex flex-col items-center gap-1 p-3 rounded-xl border-2 transition-all duration-200
${selected === avatar.id
? "border-blue-500 bg-blue-50 dark:bg-blue-950/30"
: "border-slate-200 dark:border-zinc-800 hover:border-blue-300"
}`}
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/AvatarSelector.tsx` around lines 27 - 35, The avatar buttons
in AvatarSelector currently only change visual styling; add accessibility state
by setting aria-pressed on the button to reflect selection (e.g.,
aria-pressed={selected === avatar.id}) so assistive tech can identify the active
avatar; update the JSX for the button rendered in the map (the element using
onClick={() => onSelect(avatar.id)} and className conditional on selected) to
include this attribute and ensure it updates whenever selected changes.

Source: Coding guidelines

"use client";

const BANNERS = [
{ id: "none", label: "No Banner", gradient: "bg-slate-100 dark:bg-zinc-900" },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix low-contrast banner label text for the “No Banner” option.

Line 34 always uses text-white, but Line 4 defines a light background for none. This makes the label hard to read.

Suggested fix
-            <span className="absolute bottom-1 left-2 text-xs font-bold text-white drop-shadow">
+            <span
+              className={`absolute bottom-1 left-2 text-xs font-bold drop-shadow ${
+                banner.id === "none" ? "text-slate-700 dark:text-zinc-200" : "text-white"
+              }`}
+            >
               {banner.label}
             </span>

As per coding guidelines, “**/*.tsx: Review for: Accessibility (aria labels, keyboard navigation)”.

Also applies to: 34-36

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/BannerSelector.tsx` at line 4, The "No Banner" option in
BannerSelector uses a light gradient ({ id: "none", label: "No Banner",
gradient: "bg-slate-100 dark:bg-zinc-900" }) but the label rendering uses a
hardcoded "text-white" class, causing low contrast; update the BannerSelector
rendering to avoid hardcoded "text-white" by either adding a textColor property
to the option objects (e.g. textColor: "text-slate-700 dark:text-white" for the
"none" option) and using that class when rendering the label, or detect the
option id "none" and apply a conditional class ("text-slate-700
dark:text-white") instead of "text-white" so light backgrounds get a dark text
color while dark backgrounds keep white text; ensure the change replaces the
static "text-white" usage in the label rendering code.

Source: Coding guidelines

@knoxiboy

knoxiboy commented Jun 18, 2026

Copy link
Copy Markdown
Owner

@avantikarathi01 Resolve merge conflicts and also look into the suggestions from coderabbit and codeant to improve the code quality of this pr

@knoxiboy knoxiboy added merge-conflict PR has merge conflicts that need resolution quality : needs-work labels Jun 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc'26 GSSoC program issue level:beginner Beginner level task merge-conflict PR has merge conflicts that need resolution size/l type:bug Bug fix type:feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]:User Profile Personalization & Customization System

2 participants