[FOSSOVERFLOW-25] Fixed an issue where the search bar kept re-rendering on each input. #56
[FOSSOVERFLOW-25] Fixed an issue where the search bar kept re-rendering on each input. #56alSN0W wants to merge 36 commits intoOpenLake:mainfrom
Conversation
Review engagement
added vote button
fixed displaying the user votes incorrectly issue
Styled Voting buttons and updated migration files
removed useVote as it's useless for now
added pagination type and utils
This reverts commit f2ac58d.
Revert "added pagination type and utils"
made changes recommended by code-rabbit
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
some code-rabbit changes
documentation changes
removed all the useless routes and functions
Revert "removed all the useless routes and functions"
Revert "Revert "removed all the useless routes and functions""
WalkthroughThis PR introduces pagination and voting system refactoring alongside React/Next.js dependency upgrades to major versions (React 19, Next 16). It adds pagination infrastructure (utilities, hooks, components), refactors the voting/rating API endpoints, introduces new UI components (Pagination, VoteButton), updates database schema and RLS policies, enables the magic link signin form, and modifies the JSX runtime configuration. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Client as Client Component
participant API as /api/ratings/vote
participant DB as Supabase (votes table)
participant Auth as Supabase Auth
User->>Client: Click Upvote/Downvote Button
Client->>Client: Optimistic UI Update
Client->>Auth: Get User Session
alt Unauthenticated
Auth-->>Client: No session
Client->>Client: Show toast & rollback
else Authenticated
Auth-->>Client: Session with auth.uid()
Client->>API: POST {review_id, vote_type}
API->>DB: Check existing vote by auth_id
alt Vote exists & same type
DB-->>API: Found matching vote
API->>DB: Delete vote
DB-->>API: Success
API-->>Client: action: removed
else Vote exists & different type
DB-->>API: Found different vote
API->>DB: Update vote_type
DB-->>API: Success
API-->>Client: action: updated
else No existing vote
DB-->>API: Not found
API->>DB: Insert new vote
DB-->>API: Success
API-->>Client: action: created
end
Client->>Client: Update UI with response
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
package.json (2)
60-61:⚠️ Potential issue | 🟠 MajorUpdate
eslint-config-nextto match Next.js version.
eslint-config-nextat13.5.1is significantly behind Next.js16.x. This may cause linting rule mismatches and false positives/negatives for Next.js 16 features.Proposed fix
"eslint": "8.49.0", - "eslint-config-next": "13.5.1", + "eslint-config-next": "^16.1.6",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 60 - 61, Update the eslint-config-next dependency to a version compatible with Next.js 16 to avoid lint rule mismatches: open package.json and change the "eslint-config-next" entry to match the Next.js major version (e.g., upgrade to a 16.x-compatible release), then run your package manager (npm/yarn/pnpm) to install and verify linting; ensure the "next" dependency version and "eslint-config-next" major versions align.
19-19:⚠️ Potential issue | 🔴 CriticalCritical:
@next/swc-wasm-nodejsandeslint-config-nextmust be updated to match Next.js 16.Both
@next/swc-wasm-nodejs(line 19) andeslint-config-next(line 61) are pinned at13.5.1whilenextis^16.1.6. The SWC and ESLint packages must match the Next.js major version to avoid build and configuration errors.Update both to:
@next/swc-wasm-nodejs:16.1.6eslint-config-next:16.1.6Alternatively, remove
@next/swc-wasm-nodejsentirely if you're not explicitly using WASM-based SWC (standard Next.js 16 installations don't require it).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 19, Update the pinned packages in package.json so they match the Next.js 16 major version: change "@next/swc-wasm-nodejs" from "13.5.1" to "16.1.6" (or remove it entirely if you don't need the WASM SWC) and change "eslint-config-next" from "13.5.1" to "16.1.6"; ensure package.json reflects these new versions and run install (npm/yarn) to update lockfile and verify builds.src/components/courses/Filters.tsx (1)
103-109:⚠️ Potential issue | 🟠 MajorParent search updates now wipe un-applied local filters.
This effect rehydrates departments, difficulties, and rating on every
currentFilterschange. Since search now updates the parent after debounce, typing in the search box will reset any local department/difficulty/rating edits the user has not applied yet.Suggested split
- useEffect(() => { - setLocalSearchQuery(currentFilters.searchQuery); - setLocalSelectedDepartments(currentFilters.departments); - setLocalSelectedDifficulties(currentFilters.difficulties); - setLocalRatingFilter([currentFilters.rating]); - }, [currentFilters]); + useEffect(() => { + setLocalSearchQuery(currentFilters.searchQuery); + }, [currentFilters.searchQuery]); + + useEffect(() => { + setLocalSelectedDepartments(currentFilters.departments); + setLocalSelectedDifficulties(currentFilters.difficulties); + setLocalRatingFilter([currentFilters.rating]); + }, [ + currentFilters.departments, + currentFilters.difficulties, + currentFilters.rating, + ]);
🧹 Nitpick comments (6)
src/utils/supabase/server-pages.ts (1)
2-4: Remove unused imports.
SerializeOptions(line 2) andserialize(line 4) from thecookiepackage are imported but never used. The code usesserializeCookieHeaderfrom@supabase/ssrinstead.Proposed fix
import { createServerClient, serializeCookieHeader } from '@supabase/ssr' -import { SerializeOptions } from 'cookie' import { NextApiRequest, NextApiResponse } from 'next' -import { serialize } from 'cookie'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/supabase/server-pages.ts` around lines 2 - 4, Remove the unused imports from the cookie package: delete the SerializeOptions and serialize imports at the top of the module since the code uses serializeCookieHeader from `@supabase/ssr` instead; update any import statement referencing SerializeOptions or serialize to eliminate them so only needed symbols (e.g., serializeCookieHeader) remain imported.src/types/reviews.tsx (1)
29-35: Consider addingauth_idtoVoteinterface or documenting its omission.The database schema (per
migration.sql) includesauth_idin the votes table, and the API route writes it. TheVoteinterface omits this field.If this is intentional (e.g.,
auth_idis server-only and shouldn't be exposed to clients), consider adding a comment to document this decision. Otherwise, add the field for schema consistency.📝 Option 1: Document the omission
/** * Vote from database - matches the votes table schema + * Note: auth_id is stored in the database but not exposed to clients */ export interface Vote { id: string; review_id: string; anonymous_id: string; vote_type: 'helpful' | 'unhelpful'; created_at: string; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/types/reviews.tsx` around lines 29 - 35, The Vote interface currently omits the auth_id column present in the database and written by the API route; update the Vote interface (named Vote in src/types/reviews.tsx) to include auth_id?: string if it should be exposed to clients, or add a concise comment above the Vote interface explaining that auth_id exists in migration.sql and is intentionally omitted from the client-facing type because it is server-only; make sure the change references the same symbol Vote and the migration.sql/votes table for clarity.src/pages/api/ratings/vote/index.ts (2)
81-86: Updatingcreated_atloses original vote timestamp.When a user changes their vote type, the code overwrites
created_atwith the current time. This loses the information about when the user originally voted. Consider either:
- Keeping
created_atunchanged (remove line 84), or- Adding an
updated_atfield to track modifications while preserving the original creation time.♻️ Proposed fix
const { error: updateError } = await supabase .from('votes') .update({ vote_type, anonymous_id, // Update anonymous_id in case it changed - created_at: new Date().toISOString() }) .eq('id', existingVote.id);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/api/ratings/vote/index.ts` around lines 81 - 86, The update call that sets created_at currently overwrites the original vote timestamp—locate the .update({...}) call that includes vote_type, anonymous_id, created_at and existingVote.id and remove the created_at assignment so the original creation time is preserved; if you need to track when the vote was changed instead, add an updated_at field in the update payload (e.g., updated_at: new Date().toISOString()) and ensure the database schema/migration and any model handling recognize updated_at rather than overwriting created_at.
148-148: Consider limiting the number of review IDs to prevent unbounded queries.The endpoint accepts any number of comma-separated
review_idswithout validation. A malicious or buggy client could send thousands of IDs, causing database performance issues.♻️ Proposed fix
const auth_id = user.id; const reviewIdArray = review_ids.split(',').map(id => id.trim()); + + // Limit batch size to prevent unbounded queries + const MAX_REVIEW_IDS = 100; + if (reviewIdArray.length > MAX_REVIEW_IDS) { + return res.status(400).json({ + error: `Maximum ${MAX_REVIEW_IDS} review_ids allowed per request` + }); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/api/ratings/vote/index.ts` at line 148, The handler currently builds reviewIdArray from review_ids without any limits; add input validation to prevent unbounded queries by defining a max (e.g. const MAX_REVIEW_IDS = 50), split/trim/filter/unique the review_ids string, validate each id format (numeric or UUID depending on your model), and if the resulting array length is 0 or > MAX_REVIEW_IDS return a 400 error; update the code around the line where reviewIdArray is created (the review_ids variable and reviewIdArray mapping) to enforce these checks before calling the database.src/components/courses/course_page/CoursePageReviews.tsx (2)
15-15: Replaceanywith proper type forreview.Using
anyloses type safety. Import and use theReviewtype from the types file.♻️ Proposed fix
+import { Review, VoteType } from "@/types/reviews"; + /* Single Review Card (vertical format) */ -const CourseReviewItem = ({ review, userVote }: { review: any; userVote?: VoteType }) => { +const CourseReviewItem = ({ review, userVote }: { review: Review; userVote?: VoteType }) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/courses/course_page/CoursePageReviews.tsx` at line 15, The CourseReviewItem prop uses review: any; update it to a concrete type by importing the Review interface and using review: Review in the CourseReviewItem props. Add an import for Review from the central types file (alongside the existing VoteType import if needed), then replace the anonymous any in the component signature "CourseReviewItem = ({ review, userVote }: { review: any; userVote?: VoteType })" with the typed form "review: Review" so the component and any consumers get proper type checking.
5-7: ImportVoteTypefrom the canonical types file.
VoteTypeis defined in both@/components/common/VoteButtonand@/types/reviews.tsx. Import from the shared types file to maintain a single source of truth and avoid type drift.♻️ Proposed fix
-import { VoteButton, VoteType } from "@/components/common/VoteButton"; +import { VoteButton } from "@/components/common/VoteButton"; +import { VoteType } from "@/types/reviews"; import { Pagination } from "@/components/common/Pagination"; import { usePaginatedReviews } from "@/hooks/usePaginatedReviews";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/courses/course_page/CoursePageReviews.tsx` around lines 5 - 7, CoursePageReviews currently imports VoteType from the VoteButton module which duplicates the canonical type; update the import in CoursePageReviews to import VoteType from the shared reviews types module (the canonical types file) and retain only the VoteButton import from the VoteButton module so the component uses the single source of truth for VoteType; if VoteButton still exports VoteType, remove or stop using that duplicate export to prevent type drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@next-env.d.ts`:
- Around line 4-7: The auto-generated import of "./.next/dev/types/routes.d.ts"
in next-env.d.ts should either be removed or enabled via Next.js typed routes;
either delete that import line from next-env.d.ts if you don't need typed
routes, or enable typed routes by adding typedRoutes: true to your
next.config.js (do not use experimental.typedRoutes). After enabling, rebuild so
Next.js regenerates this file automatically.
In `@src/components/common/VoteButton.tsx`:
- Around line 60-71: The handleVote function allows concurrent calls because
isLoading is set only after awaiting supabase.auth.getSession; introduce a
ref-based mutex (e.g., requestInFlightRef via useRef(false)) and check it at the
top of handleVote to return early if already true, set
requestInFlightRef.current = true and call setIsLoading(true) before any await,
then perform the supabase.auth.getSession call and existing optimistic
update/POST logic, and finally reset requestInFlightRef.current = false and
setIsLoading(false) in a finally block (being careful to clear the ref even when
returning early for an unauthenticated session) to avoid duplicate POSTs.
In `@src/components/courses/course_page/CoursePageReviews.tsx`:
- Around line 68-85: The page doesn't call the hook's refresh, so new reviews
only show after navigation; in CoursePageReviews destructure refresh from
usePaginatedReviews (alongside reviews, currentPage, etc.) and pass it as a
callback prop (e.g., onRefresh or refresh) into AddReviewButton so
AddReviewButton can invoke it after a successful submit to reload the current
page; update any AddReviewButton prop types/handler usage to call that callback
after submission.
- Around line 94-100: In CoursePageReviews, the fetch of
`/api/ratings/vote?review_ids=${reviewIds}` should check response.ok before
calling response.json(); update the async block around that fetch (the try block
that currently assigns response and votesData and calls setUserVotes) to: verify
response.ok, handle non-OK by parsing the error body or throwing a descriptive
error, and only call setUserVotes when votesData.success is true; include a
fallback to setUserVotes({}) or handle state accordingly and surface/log the
error so failures don’t silently break the component (refer to the variables
response, votesData, reviewIds, and the setUserVotes call).
In `@src/components/courses/Filters.tsx`:
- Around line 76-92: The debounced timeout in handleSearchChange closes over the
stale currentFilters snapshot, so when it fires it can overwrite newer filter
changes; fix by reading the latest filters at execution time instead of using
the closed-over currentFilters — introduce a ref (e.g., currentFiltersRef) that
mirror-updates whenever currentFilters changes and then inside the debounced
callback spread currentFiltersRef.current with searchQuery; keep
debouncedSearchRef and handleSearchChange intact but replace usage of
currentFilters in the timeout with currentFiltersRef.current to ensure the
merged FiltersState is always up-to-date when calling onFilterChange.
- Around line 69-70: The ref debouncedSearchRef is typed for NodeJS timers and
not initialized, which breaks React 19 client-side usage; change its type to a
browser-safe number and give it an initial value (e.g., useRef<number |
null>(null)) so debouncedSearchRef in the Filters component holds a numeric
timeout id (or null) instead of NodeJS.Timeout and satisfies useRef's required
initial value.
In `@src/hooks/usePaginatedReviews.ts`:
- Around line 4-12: Replace the locally declared Review interface in
usePaginatedReviews.ts with the canonical type import: remove the inline
"interface Review { ... }" and add "import { Review } from '@/types/reviews'";
ensure all usages in the file (e.g., any generics, state types, or function
signatures referencing Review) use the imported Review so the additional fields
like target_id, target_type, is_flagged, difficulty_rating, workload_rating,
knowledge_rating, teaching_rating, approachability_rating, and updated_at are
correctly typed.
In `@src/migrations/migration.sql`:
- Around line 457-459: The policy "Allow public to read votes" on the votes
table currently uses USING (true) which exposes sensitive columns (auth_id,
anonymous_id); change it so the raw votes table is not publicly selectable
(e.g., replace USING (true) with USING (false) or drop/disable the policy for
the votes table) and instead create a restricted read surface: add a view (e.g.,
reviews.votes or reviews.vote_totals) that only exposes aggregated
counts/allowed columns and grant SELECT on that view to public; ensure auth_id
and anonymous_id remain inaccessible from the public policy on the votes table.
- Around line 462-472: The RLS policies on the votes table only bind auth_id,
allowing clients to spoof anonymous_id; update the INSERT and UPDATE policies
for table votes (the "Authenticated users can insert their own votes" and "Users
can update their own votes" policies) so both identifiers are enforced: add
conditions that require anonymous_id to equal the caller's anonymous identifier
in both USING and WITH CHECK clauses (i.e., require auth_id = auth.uid() AND
anonymous_id = auth.anonymous_id(), or the equivalent function your auth system
exposes) so inserts/updates can only set both auth_id and anonymous_id to the
caller's values.
In `@src/pages/api/ratings/vote/index.ts`:
- Around line 46-56: The vote flow has a TOCTOU race: after checking for
existingVote you later call the insert (the supabase .from('votes').insert(...)
call) and treat all insert failures as generic 500s; catch the unique-constraint
duplicate key error from the insert (Postgres code '23505' or Supabase error
message containing "duplicate" / "unique") and handle it specially by returning
a 409 or treating it as a successful no-op (e.g., fetch and return the existing
vote) instead of a 500; update the insert error handling around the insert call
(the .insert(...) / .single() response handling) to detect error.code ===
'23505' (or check error.message) and respond accordingly.
In `@src/pages/api/reviews/index.ts`:
- Around line 78-83: The public reviews API is returning the stable field
anonymous_id in the .select(...) call which lets clients correlate anonymous
authors; remove anonymous_id from the returned columns (edit the .select call
that currently lists anonymous_id) so it is not sent in responses, keep using
anonymous_id internally for votes uniqueness if needed but never expose it, and
verify the votes(...) usage and any count logic still work after removing
anonymous_id from the selected projection.
- Around line 35-38: The current heuristic sets isUUID =
target_id.includes('-'), which misclassifies dashed codes as UUIDs; replace this
with a proper UUID validation (e.g., a dedicated isValidUUID function or a
library validator) and use that boolean in place of isUUID so only true RFC4122
UUIDs take the UUID branch; update the code that references target_id and isUUID
to call/assign isValidUUID(target_id) (or import/use validate from 'uuid') and
ensure the lookup path for non-UUID codes remains unchanged.
In `@src/types/reviews.tsx`:
- Around line 4-24: The Review interface must be updated to match the Supabase
ratings table: replace separated rating fields (rating_value, difficulty_rating,
workload_rating, knowledge_rating, teaching_rating, approachability_rating) with
a single rating_metrics object (containing keys like overall, difficulty,
workload, knowledge, teaching, approachability), rename votes to
helpfulness_score, and add semester, year, and display_date fields; then update
data access to query the actual "ratings" table (not "reviews") in
src/lib/data/reviews.tsx and use the correct foreign key (e.g., course_id if
present in the DB) and projection, and update
src/components/courses/CourseReviews.tsx to read ratings from
review.rating_metrics.overall (or appropriate nested keys) and avoid
non-existent review.user.* fields (map anonymous_id or the actual user structure
returned by the ratings row). Ensure the interface symbol Review and data access
in src/lib/data/reviews.tsx and CourseReviews.tsx are consistent with
rating_metrics and helpfulness_score names.
---
Outside diff comments:
In `@package.json`:
- Around line 60-61: Update the eslint-config-next dependency to a version
compatible with Next.js 16 to avoid lint rule mismatches: open package.json and
change the "eslint-config-next" entry to match the Next.js major version (e.g.,
upgrade to a 16.x-compatible release), then run your package manager
(npm/yarn/pnpm) to install and verify linting; ensure the "next" dependency
version and "eslint-config-next" major versions align.
- Line 19: Update the pinned packages in package.json so they match the Next.js
16 major version: change "@next/swc-wasm-nodejs" from "13.5.1" to "16.1.6" (or
remove it entirely if you don't need the WASM SWC) and change
"eslint-config-next" from "13.5.1" to "16.1.6"; ensure package.json reflects
these new versions and run install (npm/yarn) to update lockfile and verify
builds.
---
Nitpick comments:
In `@src/components/courses/course_page/CoursePageReviews.tsx`:
- Line 15: The CourseReviewItem prop uses review: any; update it to a concrete
type by importing the Review interface and using review: Review in the
CourseReviewItem props. Add an import for Review from the central types file
(alongside the existing VoteType import if needed), then replace the anonymous
any in the component signature "CourseReviewItem = ({ review, userVote }: {
review: any; userVote?: VoteType })" with the typed form "review: Review" so the
component and any consumers get proper type checking.
- Around line 5-7: CoursePageReviews currently imports VoteType from the
VoteButton module which duplicates the canonical type; update the import in
CoursePageReviews to import VoteType from the shared reviews types module (the
canonical types file) and retain only the VoteButton import from the VoteButton
module so the component uses the single source of truth for VoteType; if
VoteButton still exports VoteType, remove or stop using that duplicate export to
prevent type drift.
In `@src/pages/api/ratings/vote/index.ts`:
- Around line 81-86: The update call that sets created_at currently overwrites
the original vote timestamp—locate the .update({...}) call that includes
vote_type, anonymous_id, created_at and existingVote.id and remove the
created_at assignment so the original creation time is preserved; if you need to
track when the vote was changed instead, add an updated_at field in the update
payload (e.g., updated_at: new Date().toISOString()) and ensure the database
schema/migration and any model handling recognize updated_at rather than
overwriting created_at.
- Line 148: The handler currently builds reviewIdArray from review_ids without
any limits; add input validation to prevent unbounded queries by defining a max
(e.g. const MAX_REVIEW_IDS = 50), split/trim/filter/unique the review_ids
string, validate each id format (numeric or UUID depending on your model), and
if the resulting array length is 0 or > MAX_REVIEW_IDS return a 400 error;
update the code around the line where reviewIdArray is created (the review_ids
variable and reviewIdArray mapping) to enforce these checks before calling the
database.
In `@src/types/reviews.tsx`:
- Around line 29-35: The Vote interface currently omits the auth_id column
present in the database and written by the API route; update the Vote interface
(named Vote in src/types/reviews.tsx) to include auth_id?: string if it should
be exposed to clients, or add a concise comment above the Vote interface
explaining that auth_id exists in migration.sql and is intentionally omitted
from the client-facing type because it is server-only; make sure the change
references the same symbol Vote and the migration.sql/votes table for clarity.
In `@src/utils/supabase/server-pages.ts`:
- Around line 2-4: Remove the unused imports from the cookie package: delete the
SerializeOptions and serialize imports at the top of the module since the code
uses serializeCookieHeader from `@supabase/ssr` instead; update any import
statement referencing SerializeOptions or serialize to eliminate them so only
needed symbols (e.g., serializeCookieHeader) remain imported.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 68e08eaf-2f50-493c-b089-324c77869e8f
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (22)
next-env.d.tsnext.config.jspackage.jsonsrc/app/auth/signin/page.tsxsrc/app/courses/[courseId]/page.tsxsrc/components/common/Pagination.tsxsrc/components/common/VoteButton.tsxsrc/components/courses/Filters.tsxsrc/components/courses/course_page/CoursePageReviews.tsxsrc/hooks/usePaginatedReviews.tssrc/lib/pagination.tssrc/lib/withPagination.tssrc/migrations/migration.sqlsrc/pages/api/ratings/route.tssrc/pages/api/ratings/vote/index.tssrc/pages/api/ratings/vote/route.tssrc/pages/api/reviews/index.tssrc/types/pagination.tssrc/types/reviews.tsxsrc/utils/supabase/server-pages.tssrc/utils/supabase/server.tstsconfig.json
💤 Files with no reviewable changes (2)
- src/pages/api/ratings/vote/route.ts
- src/pages/api/ratings/route.ts
| import "./.next/dev/types/routes.d.ts"; | ||
|
|
||
| // NOTE: This file should not be edited | ||
| // see https://nextjs.org/docs/basic-features/typescript for more information. | ||
| // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Next.js 16 typed routes configuration
💡 Result:
To enable typed routes (statically typed links) in Next.js 16, turn on the stable typedRoutes flag in your next.config.* (don’t use experimental.typedRoutes anymore). [1]
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
typedRoutes: true,
}
export default nextConfigRequirements / what you get:
- Your project must be using TypeScript. [1]
- On
next dev/next build, Next.js generates route/link type definitions under.next/types, which TypeScript uses to type-checkLinkhref(and related navigation usage). [2]
Optionally, you can use the Route type from next:
import type { Route } from 'next'
const href: Route = '/dashboard'[2]
Sources: [1] [2]
🏁 Script executed:
# Check the Next.js version in package.json
fd package.json -x cat {} | grep -A 2 -B 2 "next"Repository: OpenLake/RateMyCourse
Length of output: 745
🏁 Script executed:
# Find and read the next-env.d.ts file
fd next-env.d.ts -x cat -n {}Repository: OpenLake/RateMyCourse
Length of output: 430
🏁 Script executed:
# Search for how Next.js 16 generates and structures the typed routes file
# Let's look for any .next directory structure hints or related configuration
fd -t d ".next" -x echo {} 2>/dev/null | head -20Repository: OpenLake/RateMyCourse
Length of output: 47
🏁 Script executed:
# Search the codebase for any references to the .next directory, next.config files, or typedRoutes configuration
rg -i "typedroutes|typed.*routes" -A 2 -B 2 --type ts --type js --type jsonRepository: OpenLake/RateMyCourse
Length of output: 47
🏁 Script executed:
# Check if next.config file exists and its content
fd "next.config" -x cat -n {}Repository: OpenLake/RateMyCourse
Length of output: 573
Remove or enable the typed routes import based on project needs.
This file is auto-generated by Next.js and should not be manually edited. Line 4 imports from ./.next/dev/types/routes.d.ts, which will only be generated if you enable typed routes. Either remove this import if typed routes are not needed, or enable them in next.config.js with typedRoutes: true (not experimental.typedRoutes, which is no longer used in Next.js 16). Once enabled, Next.js will regenerate this file automatically on each build.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@next-env.d.ts` around lines 4 - 7, The auto-generated import of
"./.next/dev/types/routes.d.ts" in next-env.d.ts should either be removed or
enabled via Next.js typed routes; either delete that import line from
next-env.d.ts if you don't need typed routes, or enable typed routes by adding
typedRoutes: true to your next.config.js (do not use experimental.typedRoutes).
After enabling, rebuild so Next.js regenerates this file automatically.
| const handleVote = async (voteType: 'helpful' | 'unhelpful') => { | ||
| if (isLoading) return; | ||
|
|
||
| // Check if user is authenticated BEFORE making API call | ||
| const { data: { session } } = await supabase.auth.getSession(); | ||
|
|
||
| if (!session) { | ||
| toast.error('Please sign in to vote on reviews'); | ||
| return; | ||
| } | ||
|
|
||
| setIsLoading(true); |
There was a problem hiding this comment.
Set the in-flight guard before the async session lookup.
isLoading does not flip until after Line 64 resolves, so two quick clicks can enter handleVote twice and send duplicate POSTs before the buttons disable. A ref-based mutex is the safest fix here.
Suggested shape
const requestInFlightRef = useRef(false);
const handleVote = async (voteType: 'helpful' | 'unhelpful') => {
if (requestInFlightRef.current) return;
requestInFlightRef.current = true;
setIsLoading(true);
try {
const { data: { session } } = await supabase.auth.getSession();
if (!session) {
toast.error('Please sign in to vote on reviews');
return;
}
// existing optimistic update + fetch...
} finally {
requestInFlightRef.current = false;
setIsLoading(false);
}
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/common/VoteButton.tsx` around lines 60 - 71, The handleVote
function allows concurrent calls because isLoading is set only after awaiting
supabase.auth.getSession; introduce a ref-based mutex (e.g., requestInFlightRef
via useRef(false)) and check it at the top of handleVote to return early if
already true, set requestInFlightRef.current = true and call setIsLoading(true)
before any await, then perform the supabase.auth.getSession call and existing
optimistic update/POST logic, and finally reset requestInFlightRef.current =
false and setIsLoading(false) in a finally block (being careful to clear the ref
even when returning early for an unauthenticated session) to avoid duplicate
POSTs.
| const { | ||
| reviews, | ||
| currentPage, | ||
| totalPages, | ||
| totalItems, | ||
| isLoading, | ||
| error, | ||
| hasNextPage, | ||
| hasPreviousPage, | ||
| goToPage, | ||
| } = usePaginatedReviews({ | ||
| targetId: id, | ||
| targetType: 'course', | ||
| initialPage: 1, | ||
| limit: 10, | ||
| sortBy: 'created_at', | ||
| sortOrder: 'desc', | ||
| }); |
There was a problem hiding this comment.
New reviews won't appear until page navigation.
The usePaginatedReviews hook provides a refresh() function (see line 123-126 of usePaginatedReviews.ts), but it's not destructured or passed to AddReviewButton. After submitting a new review, users won't see it until they navigate to another page and back.
Consider passing a refresh callback to AddReviewButton to reload the current page after submission.
🔧 Proposed fix
const {
reviews,
currentPage,
totalPages,
totalItems,
isLoading,
error,
hasNextPage,
hasPreviousPage,
goToPage,
+ refresh,
} = usePaginatedReviews({- <AddReviewButton courseId={id} />
+ <AddReviewButton courseId={id} onSuccess={refresh} />Also applies to: 116-116
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/courses/course_page/CoursePageReviews.tsx` around lines 68 -
85, The page doesn't call the hook's refresh, so new reviews only show after
navigation; in CoursePageReviews destructure refresh from usePaginatedReviews
(alongside reviews, currentPage, etc.) and pass it as a callback prop (e.g.,
onRefresh or refresh) into AddReviewButton so AddReviewButton can invoke it
after a successful submit to reload the current page; update any AddReviewButton
prop types/handler usage to call that callback after submission.
| try { | ||
| const response = await fetch(`/api/ratings/vote?review_ids=${reviewIds}`); | ||
| const votesData = await response.json(); | ||
|
|
||
| if (votesData.success) { | ||
| setUserVotes(votesData.votes || {}); | ||
| } |
There was a problem hiding this comment.
Missing error handling for non-OK response.
The fetch call doesn't check response.ok before parsing JSON. If the API returns an error status (4xx/5xx), response.json() may fail or return an error object that doesn't have success: true.
🛡️ Proposed fix
try {
const response = await fetch(`/api/ratings/vote?review_ids=${reviewIds}`);
+ if (!response.ok) {
+ console.error("Failed to fetch votes:", response.status);
+ return;
+ }
const votesData = await response.json();
if (votesData.success) {
setUserVotes(votesData.votes || {});
}📝 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.
| try { | |
| const response = await fetch(`/api/ratings/vote?review_ids=${reviewIds}`); | |
| const votesData = await response.json(); | |
| if (votesData.success) { | |
| setUserVotes(votesData.votes || {}); | |
| } | |
| try { | |
| const response = await fetch(`/api/ratings/vote?review_ids=${reviewIds}`); | |
| if (!response.ok) { | |
| console.error("Failed to fetch votes:", response.status); | |
| return; | |
| } | |
| const votesData = await response.json(); | |
| if (votesData.success) { | |
| setUserVotes(votesData.votes || {}); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/courses/course_page/CoursePageReviews.tsx` around lines 94 -
100, In CoursePageReviews, the fetch of
`/api/ratings/vote?review_ids=${reviewIds}` should check response.ok before
calling response.json(); update the async block around that fetch (the try block
that currently assigns response and votesData and calls setUserVotes) to: verify
response.ok, handle non-OK by parsing the error body or throwing a descriptive
error, and only call setUserVotes when votesData.success is true; include a
fallback to setUserVotes({}) or handle state accordingly and surface/log the
error so failures don’t silently break the component (refer to the variables
response, votesData, reviewIds, and the setUserVotes call).
| // Ref to store the debounce timeout for search | ||
| const debouncedSearchRef = useRef<NodeJS.Timeout>(); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In React 19 TypeScript, does useRefrequire an initial value, and what type is recommended for storingsetTimeouthandles in browser code instead ofNodeJS.Timeout?
💡 Result:
useRefinitial value (React 19 + TypeScript): Yes—under React 19’s TypeScript typings you should pass an initial value (e.g.useRef<HTMLDivElement | null>(null)oruseRef<number | undefined>(undefined)), rather than callinguseRef()with no arguments. The hook is documented asuseRef(initialValue). [1], [2]setTimeouthandle type in browser code: In browsers,setTimeoutreturns anumber, so prefernumber(or, to be explicit and avoid Node typings,ReturnType<typeof window.setTimeout>). [3], [4]
Sources
[1] React docs – useRef(initialValue) https://react.dev/reference/react/useRef
[2] React 19 migration note – “useRef() requires an initial value” https://deku.posstree.com/en/react/react-19-migration/
[3] Stack Overflow – browser setTimeout returns number, Node returns NodeJS.Timeout https://stackoverflow.com/questions/60245787/typescript-what-is-the-correct-type-for-a-timeout
[4] Stack Overflow – window.setTimeout typed as number vs Node typings https://stackoverflow.com/questions/45802988/typescript-use-correct-version-of-settimeout-node-vs-window
Initialize the debounce ref with a browser-safe type.
In React 19, useRef() requires an initial value. Additionally, NodeJS.Timeout is incorrect for client-side timers—setTimeout in browsers returns number.
Suggested fix
- const debouncedSearchRef = useRef<NodeJS.Timeout>();
+ const debouncedSearchRef = useRef<number | null>(null);📝 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.
| // Ref to store the debounce timeout for search | |
| const debouncedSearchRef = useRef<NodeJS.Timeout>(); | |
| // Ref to store the debounce timeout for search | |
| const debouncedSearchRef = useRef<number | null>(null); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/courses/Filters.tsx` around lines 69 - 70, The ref
debouncedSearchRef is typed for NodeJS timers and not initialized, which breaks
React 19 client-side usage; change its type to a browser-safe number and give it
an initial value (e.g., useRef<number | null>(null)) so debouncedSearchRef in
the Filters component holds a numeric timeout id (or null) instead of
NodeJS.Timeout and satisfies useRef's required initial value.
| CREATE POLICY "Authenticated users can insert their own votes" | ||
| ON votes FOR INSERT | ||
| TO authenticated | ||
| WITH CHECK (auth_id = auth.uid()); | ||
|
|
||
| -- Users can update their own votes | ||
| CREATE POLICY "Users can update their own votes" | ||
| ON votes FOR UPDATE | ||
| TO authenticated | ||
| USING (auth_id = auth.uid()) | ||
| WITH CHECK (auth_id = auth.uid()); |
There was a problem hiding this comment.
Vote write policies still allow anonymous ID spoofing.
These policies only bind auth_id. A client can still insert/update a vote with someone else’s anonymous_id, and because src/pages/api/reviews/index.ts:79 exposes that value today, they can occupy another user’s (review_id, anonymous_id) unique slot. The write checks should bind both identifiers to the caller.
Suggested fix
CREATE POLICY "Authenticated users can insert their own votes"
ON votes FOR INSERT
TO authenticated
-WITH CHECK (auth_id = auth.uid());
+WITH CHECK (
+ auth_id = auth.uid()
+ AND anonymous_id = get_anonymous_id()
+);
@@
CREATE POLICY "Users can update their own votes"
ON votes FOR UPDATE
TO authenticated
-USING (auth_id = auth.uid())
-WITH CHECK (auth_id = auth.uid());
+USING (
+ auth_id = auth.uid()
+ AND anonymous_id = get_anonymous_id()
+)
+WITH CHECK (
+ auth_id = auth.uid()
+ AND anonymous_id = get_anonymous_id()
+);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/migrations/migration.sql` around lines 462 - 472, The RLS policies on the
votes table only bind auth_id, allowing clients to spoof anonymous_id; update
the INSERT and UPDATE policies for table votes (the "Authenticated users can
insert their own votes" and "Users can update their own votes" policies) so both
identifiers are enforced: add conditions that require anonymous_id to equal the
caller's anonymous identifier in both USING and WITH CHECK clauses (i.e.,
require auth_id = auth.uid() AND anonymous_id = auth.anonymous_id(), or the
equivalent function your auth system exposes) so inserts/updates can only set
both auth_id and anonymous_id to the caller's values.
| const { data: existingVote, error: checkError } = await supabase | ||
| .from('votes') | ||
| .select('id, vote_type') | ||
| .eq('review_id', review_id) | ||
| .eq('auth_id', auth_id) // ← Check by real user, not anonymous_id | ||
| .single(); | ||
|
|
||
| if (checkError && checkError.code !== 'PGRST116') { | ||
| console.error('Error checking existing vote:', checkError); | ||
| return res.status(500).json({ error: 'Failed to check existing vote' }); | ||
| } |
There was a problem hiding this comment.
Potential race condition between vote check and insert.
There's a TOCTOU (time-of-check to time-of-use) window between checking for an existing vote (lines 46-51) and inserting a new one (lines 101-108). Two concurrent requests could both pass the existence check and attempt to insert, causing a unique constraint violation on (review_id, auth_id).
The database constraint will catch this, but the error handling at line 110-113 treats all insert errors generically. Consider catching the unique constraint violation specifically and returning a more informative response (or treating it as a successful no-op).
🛡️ Proposed fix to handle duplicate constraint gracefully
if (insertError) {
- console.error('Error inserting vote:', insertError);
- return res.status(500).json({ error: 'Failed to cast vote' });
+ // Handle unique constraint violation (concurrent request already inserted)
+ if (insertError.code === '23505') {
+ // Retry by fetching the existing vote
+ return res.status(409).json({ error: 'Vote already exists, please retry' });
+ }
+ console.error('Error inserting vote:', insertError);
+ return res.status(500).json({ error: 'Failed to cast vote' });
}Also applies to: 100-108
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/api/ratings/vote/index.ts` around lines 46 - 56, The vote flow has
a TOCTOU race: after checking for existingVote you later call the insert (the
supabase .from('votes').insert(...) call) and treat all insert failures as
generic 500s; catch the unique-constraint duplicate key error from the insert
(Postgres code '23505' or Supabase error message containing "duplicate" /
"unique") and handle it specially by returning a 409 or treating it as a
successful no-op (e.g., fetch and return the existing vote) instead of a 500;
update the insert error handling around the insert call (the .insert(...) /
.single() response handling) to detect error.code === '23505' (or check
error.message) and respond accordingly.
| // Check if target_id is a code (not a UUID) | ||
| // UUIDs have format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (contains dashes) | ||
| // Codes are simpler: mal100, cs101, etc. (no dashes) | ||
| const isUUID = target_id.includes('-'); |
There was a problem hiding this comment.
Use a real UUID check here.
Line 38 treats any identifier containing - as a UUID. A dashed course/professor identifier will skip the lookup path and be sent straight to the UUID column filter, which can produce empty results or malformed-UUID failures.
Suggested fix
- const isUUID = target_id.includes('-');
+ const isUUID =
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(target_id);📝 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.
| // Check if target_id is a code (not a UUID) | |
| // UUIDs have format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (contains dashes) | |
| // Codes are simpler: mal100, cs101, etc. (no dashes) | |
| const isUUID = target_id.includes('-'); | |
| // Check if target_id is a code (not a UUID) | |
| // UUIDs have format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (contains dashes) | |
| // Codes are simpler: mal100, cs101, etc. (no dashes) | |
| const isUUID = | |
| /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(target_id); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/api/reviews/index.ts` around lines 35 - 38, The current heuristic
sets isUUID = target_id.includes('-'), which misclassifies dashed codes as
UUIDs; replace this with a proper UUID validation (e.g., a dedicated isValidUUID
function or a library validator) and use that boolean in place of isUUID so only
true RFC4122 UUIDs take the UUID branch; update the code that references
target_id and isUUID to call/assign isValidUUID(target_id) (or import/use
validate from 'uuid') and ensure the lookup path for non-UUID codes remains
unchanged.
| .select(` | ||
| id, anonymous_id, rating_value, comment, votes, | ||
| is_flagged, difficulty_rating, workload_rating, | ||
| knowledge_rating, teaching_rating, approachability_rating, | ||
| created_at, updated_at | ||
| `, { count: 'exact' }) |
There was a problem hiding this comment.
Don’t expose anonymous_id from a public reviews API.
This route is readable without authentication, so returning a stable anonymous_id lets clients correlate all reviews written by the same “anonymous” author across different targets. That weakens the platform’s anonymity model and also makes the votes(review_id, anonymous_id) uniqueness slot easier to abuse.
Suggested fix
.select(`
- id, anonymous_id, rating_value, comment, votes,
+ id, rating_value, comment, votes,
is_flagged, difficulty_rating, workload_rating,
knowledge_rating, teaching_rating, approachability_rating,
created_at, updated_at
`, { count: 'exact' })📝 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.
| .select(` | |
| id, anonymous_id, rating_value, comment, votes, | |
| is_flagged, difficulty_rating, workload_rating, | |
| knowledge_rating, teaching_rating, approachability_rating, | |
| created_at, updated_at | |
| `, { count: 'exact' }) | |
| .select(` | |
| id, rating_value, comment, votes, | |
| is_flagged, difficulty_rating, workload_rating, | |
| knowledge_rating, teaching_rating, approachability_rating, | |
| created_at, updated_at | |
| `, { count: 'exact' }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/api/reviews/index.ts` around lines 78 - 83, The public reviews API
is returning the stable field anonymous_id in the .select(...) call which lets
clients correlate anonymous authors; remove anonymous_id from the returned
columns (edit the .select call that currently lists anonymous_id) so it is not
sent in responses, keep using anonymous_id internally for votes uniqueness if
needed but never expose it, and verify the votes(...) usage and any count logic
still work after removing anonymous_id from the selected projection.
| export interface Review { | ||
| id: string; | ||
| courseId: string; | ||
| courseName: string; | ||
| courseCode: string; | ||
| semester: string; | ||
| overallRating: number; | ||
| workloadRating: number; | ||
| contentRating: number; | ||
| teachingRating: number; | ||
| supportRating: number; | ||
| comment: string; | ||
| date: string; | ||
| user: { | ||
| id: string; | ||
| name: string; | ||
| avatar: string; | ||
| }; | ||
| anonymous_id: string; | ||
| target_id: string; | ||
| target_type: 'course' | 'professor'; | ||
| rating_value: number; // Overall rating 1-5 | ||
| comment: string | null; | ||
| votes: number; // Net helpful votes (helpful - unhelpful) | ||
| is_flagged: boolean; | ||
| created_at: string; | ||
| updated_at: string; | ||
|
|
||
| // Course-specific ratings (null for professor reviews) | ||
| difficulty_rating: number | null; | ||
| workload_rating: number | null; | ||
|
|
||
| // Professor-specific ratings (null for course reviews) | ||
| knowledge_rating: number | null; | ||
| teaching_rating: number | null; | ||
| approachability_rating: number | null; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find files still using old Review field names
echo "=== Files using old 'course_id' field ==="
rg -n "course_id" --type ts --type tsx -g '!**/migration*'
echo -e "\n=== Files using old 'overallRating' field ==="
rg -n "overallRating" --type ts --type tsx
echo -e "\n=== Files using 'review.user' pattern ==="
rg -n "review\.user\." --type ts --type tsxRepository: OpenLake/RateMyCourse
Length of output: 297
🏁 Script executed:
#!/bin/bash
# Find files still using old Review field names
echo "=== Files using old 'course_id' field ==="
rg "course_id" -g "*.ts" -g "*.tsx"
echo -e "\n=== Files using old 'overallRating' field ==="
rg "overallRating" -g "*.ts" -g "*.tsx"
echo -e "\n=== Files using 'review.user' pattern ==="
rg "review\.user\." -g "*.ts" -g "*.tsx"
echo -e "\n=== Check Review interface in reviews.tsx ==="
cat src/types/reviews.tsx | head -30Repository: OpenLake/RateMyCourse
Length of output: 4234
🏁 Script executed:
#!/bin/bash
# Check imports and usage in CourseReviews.tsx
echo "=== CourseReviews.tsx imports and Review type usage ==="
head -30 src/components/courses/CourseReviews.tsx
echo -e "\n=== Check variable type in CourseReviews ==="
rg -A 5 "const reviews" src/components/courses/CourseReviews.tsx | head -20
# Check imports and usage in lib/data/reviews.tsx
echo -e "\n=== lib/data/reviews.tsx content ==="
cat src/lib/data/reviews.tsxRepository: OpenLake/RateMyCourse
Length of output: 1538
🏁 Script executed:
#!/bin/bash
# Check the Supabase types for Reviews table
echo "=== Supabase Reviews table schema ==="
rg -A 20 "type Tables = {" src/types/supabase.ts | rg -A 15 "reviews:"
echo -e "\n=== Check the complete Reviews definition ==="
rg -B 2 -A 15 "reviews:" src/types/supabase.ts | head -40Repository: OpenLake/RateMyCourse
Length of output: 260
🏁 Script executed:
#!/bin/bash
# Read the supabase types file to find Reviews table definition
echo "=== Reading supabase.ts to find Reviews table ==="
cat src/types/supabase.ts | grep -A 30 "reviews"Repository: OpenLake/RateMyCourse
Length of output: 114
🏁 Script executed:
#!/bin/bash
# Check file size first
wc -l src/types/supabase.ts
# Read the entire supabase.ts file
cat src/types/supabase.tsRepository: OpenLake/RateMyCourse
Length of output: 9619
Schema mismatch: Review interface does not match the actual Supabase ratings table.
The Review interface in src/types/reviews.tsx is incompatible with the database schema defined in src/types/supabase.ts:
- Database has a
ratingstable (notreviews), with fields likerating_metrics(JSON object containingoverall,difficulty,workload, etc.) instead of separate fields likerating_valueanddifficulty_rating - Database uses
helpfulness_scoreinstead ofvotes - Database includes
semester,year, anddisplay_datefields not in the interface - Code in
src/lib/data/reviews.tsxqueries a non-existentreviewstable with a non-existentcourse_idfield—this is already broken src/components/courses/CourseReviews.tsxaccessesreview.overallRatingandreview.user.*which do not exist in either the interface or database schema
Align the Review interface with the actual ratings table schema and fix dependent queries and component accessors.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/types/reviews.tsx` around lines 4 - 24, The Review interface must be
updated to match the Supabase ratings table: replace separated rating fields
(rating_value, difficulty_rating, workload_rating, knowledge_rating,
teaching_rating, approachability_rating) with a single rating_metrics object
(containing keys like overall, difficulty, workload, knowledge, teaching,
approachability), rename votes to helpfulness_score, and add semester, year, and
display_date fields; then update data access to query the actual "ratings" table
(not "reviews") in src/lib/data/reviews.tsx and use the correct foreign key
(e.g., course_id if present in the DB) and projection, and update
src/components/courses/CourseReviews.tsx to read ratings from
review.rating_metrics.overall (or appropriate nested keys) and avoid
non-existent review.user.* fields (map anonymous_id or the actual user structure
returned by the ratings row). Ensure the interface symbol Review and data access
in src/lib/data/reviews.tsx and CourseReviews.tsx are consistent with
rating_metrics and helpfulness_score names.
Bug Fixes & Feature Enhancement: Search Filter Improvements
Summary
Fixes critical search input focus issue and implements real-time search with debouncing for better user experience.
Fixes
Search Input Losing Focus After Each Keystroke
Problem: Users had to re-click the search input after typing each character.
Root Cause: Parent component re-renders triggered
useEffectto resetlocalSearchQuerywhile user was actively typing, causing the input to lose focus.Solution:
Enhancements
Real-Time Search with Debouncing
Previous: Users had to click "Apply" button after typing search query.
New: Search executes automatically as users type with 300ms debounce delay.
Implementation:
Benefits:
Testing
Breaking Changes
None. Backward compatible enhancement.
Summary by CodeRabbit
Release Notes
New Features
Updates