feat: claims workflow - #101
Conversation
|
Warning Review limit reached
Next review available in: 43 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughBackend claim routes gain async image resolution, cursor-based pagination, revised status transitions, a shared match-confirmation transaction helper, and reworked match-suggestion scoring/upserts; claim status enum and item status transitions expand to include new match statuses. Frontend adds security claims list/detail pages, many claim UI components, display utilities, types, an API helper, and unrelated component/form fixes. ChangesBackend claims/items API
Estimated code review effort: 4 (Complex) | ~60 minutes Security claims UI
Estimated code review effort: 4 (Complex) | ~75 minutes Sequence Diagram(s)sequenceDiagram
participant ClaimDetailPage
participant ClaimsAPI
participant CampusesAPI
participant MatchSuggestionsAPI
ClaimDetailPage->>ClaimsAPI: fetch claim by id
ClaimDetailPage->>CampusesAPI: fetch campuses
ClaimDetailPage->>MatchSuggestionsAPI: fetchMatchSuggestions(claimId)
ClaimsAPI-->>ClaimDetailPage: claim detail
MatchSuggestionsAPI-->>ClaimDetailPage: suggestions list
ClaimDetailPage->>ClaimDetailPage: getClaimDetailMode/getClaimWorkflowSteps
sequenceDiagram
participant Handler
participant Transaction
participant Claim
participant Notification
Handler->>Transaction: applyMatchConfirmation(claim, item, studentId)
Transaction->>Claim: update itemId/status
Transaction->>Notification: create match notification
Transaction-->>Handler: commit
Handler->>Claim: findUniqueOrThrow(claimDetailSelect)
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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: 5
🧹 Nitpick comments (11)
backend/src/routes/claims.ts (2)
979-982: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRedundant re-fetch.
applyMatchConfirmationalready returns the updated claim selected withclaimDetailSelect, so thefindUniqueOrThrowre-query is an extra round-trip. Return the helper result directly.♻️ Proposed simplification
- const updated = await prisma.$transaction(async (tx) => { - await applyMatchConfirmation(tx, claim, item.itemId, actor.userId); - - return tx.claim.findUniqueOrThrow({ - where: { claimId: claim.claimId }, - select: claimDetailSelect, - }); - }); + const updated = await prisma.$transaction((tx) => + applyMatchConfirmation(tx, claim, item.itemId, actor.userId) + );🤖 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 `@backend/src/routes/claims.ts` around lines 979 - 982, `applyMatchConfirmation` is already returning the updated claim with `claimDetailSelect`, so the extra `tx.claim.findUniqueOrThrow` in the claims route is a redundant re-fetch. Update the code path that calls `applyMatchConfirmation` to return its result directly, and remove the follow-up query while keeping the same selected claim shape.
1347-1385: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winRemove stale match suggestions during regeneration.
score >= 60candidates are upserted, but older rows for the same claim are never cleared, so reruns can keep returning outdated suggestions fromGET /claims/:id/match-suggestions. Clear or dismiss the claim’s non-qualifying pending suggestions in the same transaction before writing the new set.🤖 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 `@backend/src/routes/claims.ts` around lines 1347 - 1385, The match-suggestion regeneration flow in the claim scoring logic only upserts current `scoredCandidates` and leaves older suggestions behind, so stale rows can still be returned later. Update the transaction in the `claims.ts` scoring section to clear or mark dismissed the claim’s existing non-qualifying pending `matchSuggestion` records before writing the new set, then keep the existing `tx.matchSuggestion.upsert` and `tx.claim.update` behavior for the fresh candidates.foundit-ui/app/security/claims/page.tsx (3)
404-517: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffTable rendered with div/Flex, not semantic markup.
Rows are div-based rather than
<table>/<tr>/<td>, reducing table semantics for assistive tech (though rows remain navigable as links). Consider semantic table markup if accessibility compliance is a priority for this page.🤖 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 `@foundit-ui/app/security/claims/page.tsx` around lines 404 - 517, The claims list in claims/page.tsx is rendered with Box/Flex divs instead of semantic table elements, which weakens accessibility semantics. Update the table-like structure built around visibleClaims, renderSortableHeader, and the row link wrapper to use proper table markup such as table/thead/tbody/tr/th/td while preserving the existing sorting, link navigation, and styling behavior.
267-270: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUnbounded pagination button list.
pageNumbersrenders one button per page with no windowing/truncation. With larger claim volumes this could render dozens or hundreds of buttons in the pagination bar.♻️ Suggested windowed pagination
- const pageNumbers = Array.from( - { length: totalPages }, - (_, index) => index + 1 - ); + const pageNumbers = getPaginationWindow(currentPage, totalPages, 5);Also applies to: 549-576
🤖 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 `@foundit-ui/app/security/claims/page.tsx` around lines 267 - 270, The pagination in the claims page is rendering an unbounded button for every entry in pageNumbers, which can overwhelm the UI as totalPages grows. Update the pagination logic in the claims page component so the page number list is windowed/truncated around the current page instead of using the full Array.from range, and apply the same change anywhere the same pagination rendering is used (including the other referenced block). Keep the existing pagination controls, but limit visible page buttons through a helper or computed slice tied to currentPage and totalPages.
382-387: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueStatus filter dropdown omits "approved".
matchesClaimStatusFiltersupports an'approved'filter case, but this select only offersneeds_action,waiting_on_student,completed,rejected— users can't isolate approved claims directly.🤖 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 `@foundit-ui/app/security/claims/page.tsx` around lines 382 - 387, The status filter dropdown is missing the approved option even though matchesClaimStatusFilter already handles the approved case. Update the select in claims/page.tsx to include an option for approved alongside the existing statuses, and make sure the option value matches the filter value expected by matchesClaimStatusFilter so approved claims can be filtered directly.foundit-ui/components/claims/ClaimMatchCard.tsx (2)
33-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDouble
onSelectinvocation on click.The wrapping
BoxfiresonSelectviaonClick, and the radio'sonChangealso firesonSelectfor the same click (event bubbles from input to Box). Harmless since selection is idempotent, but consider removing the redundant handler on one of them for clarity.🤖 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 `@foundit-ui/components/claims/ClaimMatchCard.tsx` around lines 33 - 65, The ClaimMatchCard component is invoking onSelect twice for the same user action because both the wrapping Box onClick and the RadioInput onChange trigger it. Update ClaimMatchCard so only one of these handlers calls onSelect, keeping the selection behavior in a single place for clarity and to avoid redundant event handling.
10-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
formatItemIdhelper.Identical function is redefined in
ClaimMatchedItemCard.tsx. Consider moving it tofoundit-ui/utils/claimDisplay.tsalongside the other shared display helpers.♻️ Proposed consolidation
-function formatItemId(itemId: string): string { - return itemId.slice(0, 8).toUpperCase(); -} +import { formatItemId } from '`@/utils/claimDisplay`';🤖 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 `@foundit-ui/components/claims/ClaimMatchCard.tsx` around lines 10 - 12, The formatItemId helper is duplicated in ClaimMatchCard.tsx and ClaimMatchedItemCard.tsx, so consolidate it into the shared claim display utilities in claimDisplay.ts and update both components to import and use the shared function. Keep the behavior unchanged (slice first 8 characters and uppercase), and remove the local duplicate definition from ClaimMatchCard so there is a single source of truth.foundit-ui/utils/claimDisplay.ts (1)
442-471: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider a typed union for
filterinstead ofstring.
matchesClaimStatusFilteraccepts a barestringand silently falls through todefault: return truefor any unrecognized value (same as the empty-filter case). Narrowingfilterto a union of the known literals ('needs_action' | 'waiting_on_student' | 'completed' | 'pending' | 'approved' | 'rejected' | '') would let TypeScript catch typos in filter values (e.g. in the status dropdown) at compile time instead of silently showing all claims.🤖 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 `@foundit-ui/utils/claimDisplay.ts` around lines 442 - 471, Narrow the filter parameter in matchesClaimStatusFilter from a bare string to a typed union of the known status literals plus the empty value so invalid filter values are caught at compile time. Update the function signature in claimDisplay.ts, and make sure any callers such as the status dropdown or related filter builders pass one of the allowed values ('needs_action', 'waiting_on_student', 'completed', 'pending', 'approved', 'rejected', or ''). Keep the current matching logic unchanged, but let TypeScript enforce correctness instead of silently falling through to the default case.foundit-ui/components/claims/ClaimReleaseCard.tsx (1)
15-22: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winBadge doesn't reflect
canReleasestate.The button/lock icon change based on
canRelease, but the badge is always "Pending" even when the checklist is complete.♻️ Proposed fix
- <Badge colorPalette="gray" variant="subtle"> - Pending - </Badge> + <Badge colorPalette={canRelease ? 'green' : 'gray'} variant="subtle"> + {canRelease ? 'Ready' : 'Pending'} + </Badge>🤖 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 `@foundit-ui/components/claims/ClaimReleaseCard.tsx` around lines 15 - 22, The badge in ClaimReleaseCard does not reflect the current canRelease state and is hardcoded to “Pending.” Update the badge rendering in ClaimReleaseCard to derive its label (and any related styling) from canRelease, matching the existing logic used for the button/lock icon so the UI stays consistent when the checklist becomes complete.foundit-ui/app/security/claims/[id]/page.tsx (1)
87-96: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winNot-found detection via string-matching error messages is fragile.
message.toLowerCase().includes('not found')(Line 91) will misroute if the backend's error text changes or is localized, silently showing a generic error instead of the 404 page (or vice versa).Consider having
fetchClaimByIdsurface the HTTP status (e.g., throw a typed error withstatus), and checkerr.status === 404here instead of parsing message text.🤖 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 `@foundit-ui/app/security/claims/`[id]/page.tsx around lines 87 - 96, The not-found handling in page.tsx currently depends on parsing the error message text in the claim-loading catch block, which is fragile. Update fetchClaimById to throw a typed error that includes the HTTP status, then in the page component’s error handling use that status (for example, checking for 404) to call setNotFoundState(true) instead of matching message.toLowerCase().includes('not found'); keep setError for all other cases.foundit-ui/components/claims/ClaimVerificationChecklist.tsx (1)
68-85: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winCheckbox lacks an associated label for screen readers.
The checkbox at Line 69 has no
htmlFor/aria-labeltying it toitem.label/item.description, so assistive tech users can't determine what the checkbox controls.♿️ Proposed fix
<Checkbox type="checkbox" mt={1} checked={value[item.key]} onChange={() => toggle(item.key)} accentColor="var(--chakra-colors-blue-500)" + aria-label={item.label} />🤖 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 `@foundit-ui/components/claims/ClaimVerificationChecklist.tsx` around lines 68 - 85, The Checkbox in ClaimVerificationChecklist is missing an accessible label association, so screen readers cannot tell what each control represents. Update the Checkbox/label pairing in the mapped item render so each checkbox is explicitly associated with item.label and, if helpful, item.description via an accessible name such as aria-label or a proper label/ID linkage, and keep the toggle(item.key) behavior unchanged.
🤖 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 `@backend/src/routes/claims.ts`:
- Around line 976-977: Relinking currently allows finalized claims to flow
through applyMatchConfirmation, which can reset a claim back to under_review and
emit a new match_found notification. Update the relinking path in claims.ts
around the prisma.$transaction/applyMatchConfirmation flow to only allow
submitted or under_review claims, or make applyMatchConfirmation preserve
approved/rejected state and skip notification resets when the claim is already
linked. Use the existing claim status checks and the applyMatchConfirmation
helper to locate the logic.
In `@foundit-ui/app/security/claims/`[id]/page.tsx:
- Around line 62-105: The claim-loading effect in page.tsx keeps the previous
verification state when claimId changes, so ClaimReleaseCard can stay enabled
for the next claim before its checklist is completed. In the useEffect that runs
load(), reset verification back to initialVerification and clear selectedMatchId
as soon as a new claim is being loaded, alongside the existing
setLoading/setError handling. Keep the reset tied to the claimId-driven load
flow so each claim starts with a fresh VerificationState.
In `@foundit-ui/components/claims/ClaimMatchedItemCard.tsx`:
- Line 3: Remove the unused Text import from ClaimMatchedItemCard so the import
list only includes the Chakra components actually referenced in the component.
Update the top-level import statement in ClaimMatchedItemCard to drop Text and
keep the remaining symbols like Flex, Grid, Heading, and Stack aligned with what
the file uses.
In `@foundit-ui/components/SecurityFoundItemReportForm.tsx`:
- Around line 38-50: The submit flow in SecurityFoundItemReportForm can still
proceed when campuses is empty because the fallback effect exits early and never
corrects campusId. Update the useEffect around campuses/defaultCampusId/campusId
to handle the empty-list case by clearing or invalidating the selection, and
make the Submit disabled state in the form check for a missing or invalid
campusId in addition to isSubmitting and campusesLoading. Use the existing
setCampusId, campusId, campuses, and defaultCampusId logic to keep the selected
campus always valid before submission.
In `@foundit-ui/hooks/useReportFoundItemForm.ts`:
- Line 20: The report-found form state is inconsistent: useReportFoundItemForm
exposes campusId/setCampusId, but the report-found page still expects
campus/setCampus. Update the hook and the page to use the same field name
end-to-end, including validation and submit wiring, or remove the unused campus
state from useReportFoundItemForm if campus should remain server-derived.
Reference the useReportFoundItemForm hook and the report-found page form usage
when aligning the contract.
---
Nitpick comments:
In `@backend/src/routes/claims.ts`:
- Around line 979-982: `applyMatchConfirmation` is already returning the updated
claim with `claimDetailSelect`, so the extra `tx.claim.findUniqueOrThrow` in the
claims route is a redundant re-fetch. Update the code path that calls
`applyMatchConfirmation` to return its result directly, and remove the follow-up
query while keeping the same selected claim shape.
- Around line 1347-1385: The match-suggestion regeneration flow in the claim
scoring logic only upserts current `scoredCandidates` and leaves older
suggestions behind, so stale rows can still be returned later. Update the
transaction in the `claims.ts` scoring section to clear or mark dismissed the
claim’s existing non-qualifying pending `matchSuggestion` records before writing
the new set, then keep the existing `tx.matchSuggestion.upsert` and
`tx.claim.update` behavior for the fresh candidates.
In `@foundit-ui/app/security/claims/`[id]/page.tsx:
- Around line 87-96: The not-found handling in page.tsx currently depends on
parsing the error message text in the claim-loading catch block, which is
fragile. Update fetchClaimById to throw a typed error that includes the HTTP
status, then in the page component’s error handling use that status (for
example, checking for 404) to call setNotFoundState(true) instead of matching
message.toLowerCase().includes('not found'); keep setError for all other cases.
In `@foundit-ui/app/security/claims/page.tsx`:
- Around line 404-517: The claims list in claims/page.tsx is rendered with
Box/Flex divs instead of semantic table elements, which weakens accessibility
semantics. Update the table-like structure built around visibleClaims,
renderSortableHeader, and the row link wrapper to use proper table markup such
as table/thead/tbody/tr/th/td while preserving the existing sorting, link
navigation, and styling behavior.
- Around line 267-270: The pagination in the claims page is rendering an
unbounded button for every entry in pageNumbers, which can overwhelm the UI as
totalPages grows. Update the pagination logic in the claims page component so
the page number list is windowed/truncated around the current page instead of
using the full Array.from range, and apply the same change anywhere the same
pagination rendering is used (including the other referenced block). Keep the
existing pagination controls, but limit visible page buttons through a helper or
computed slice tied to currentPage and totalPages.
- Around line 382-387: The status filter dropdown is missing the approved option
even though matchesClaimStatusFilter already handles the approved case. Update
the select in claims/page.tsx to include an option for approved alongside the
existing statuses, and make sure the option value matches the filter value
expected by matchesClaimStatusFilter so approved claims can be filtered
directly.
In `@foundit-ui/components/claims/ClaimMatchCard.tsx`:
- Around line 33-65: The ClaimMatchCard component is invoking onSelect twice for
the same user action because both the wrapping Box onClick and the RadioInput
onChange trigger it. Update ClaimMatchCard so only one of these handlers calls
onSelect, keeping the selection behavior in a single place for clarity and to
avoid redundant event handling.
- Around line 10-12: The formatItemId helper is duplicated in ClaimMatchCard.tsx
and ClaimMatchedItemCard.tsx, so consolidate it into the shared claim display
utilities in claimDisplay.ts and update both components to import and use the
shared function. Keep the behavior unchanged (slice first 8 characters and
uppercase), and remove the local duplicate definition from ClaimMatchCard so
there is a single source of truth.
In `@foundit-ui/components/claims/ClaimReleaseCard.tsx`:
- Around line 15-22: The badge in ClaimReleaseCard does not reflect the current
canRelease state and is hardcoded to “Pending.” Update the badge rendering in
ClaimReleaseCard to derive its label (and any related styling) from canRelease,
matching the existing logic used for the button/lock icon so the UI stays
consistent when the checklist becomes complete.
In `@foundit-ui/components/claims/ClaimVerificationChecklist.tsx`:
- Around line 68-85: The Checkbox in ClaimVerificationChecklist is missing an
accessible label association, so screen readers cannot tell what each control
represents. Update the Checkbox/label pairing in the mapped item render so each
checkbox is explicitly associated with item.label and, if helpful,
item.description via an accessible name such as aria-label or a proper label/ID
linkage, and keep the toggle(item.key) behavior unchanged.
In `@foundit-ui/utils/claimDisplay.ts`:
- Around line 442-471: Narrow the filter parameter in matchesClaimStatusFilter
from a bare string to a typed union of the known status literals plus the empty
value so invalid filter values are caught at compile time. Update the function
signature in claimDisplay.ts, and make sure any callers such as the status
dropdown or related filter builders pass one of the allowed values
('needs_action', 'waiting_on_student', 'completed', 'pending', 'approved',
'rejected', or ''). Keep the current matching logic unchanged, but let
TypeScript enforce correctness instead of silently falling through to the
default case.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 2759f172-799f-4f20-9aa1-d9b66a3b2746
📒 Files selected for processing (27)
backend/src/routes/claims.tsbackend/src/routes/items.tsbackend/src/validators/claims.tsfoundit-ui/app/security/claims/[id]/page.tsxfoundit-ui/app/security/claims/page.tsxfoundit-ui/components/ClaimCard.tsxfoundit-ui/components/SecurityFoundItemReportForm.tsxfoundit-ui/components/SelectInput.tsxfoundit-ui/components/claims/ClaimAppointmentCard.tsxfoundit-ui/components/claims/ClaimCard.tsxfoundit-ui/components/claims/ClaimClaimantCard.tsxfoundit-ui/components/claims/ClaimDetailField.tsxfoundit-ui/components/claims/ClaimDetailHeader.tsxfoundit-ui/components/claims/ClaimMatchCard.tsxfoundit-ui/components/claims/ClaimMatchEmptyState.tsxfoundit-ui/components/claims/ClaimMatchPanel.tsxfoundit-ui/components/claims/ClaimMatchedItemCard.tsxfoundit-ui/components/claims/ClaimReleaseCard.tsxfoundit-ui/components/claims/ClaimStatusStepper.tsxfoundit-ui/components/claims/ClaimVerificationChecklist.tsxfoundit-ui/components/claims/ClaimedItemCard.tsxfoundit-ui/components/claims/StudentNotificationCard.tsxfoundit-ui/components/dashboard/RecentClaimsTable.tsxfoundit-ui/hooks/useReportFoundItemForm.tsfoundit-ui/lib/api/claims.tsfoundit-ui/types/claims.tsfoundit-ui/utils/claimDisplay.ts
| const updated = await prisma.$transaction(async (tx) => { | ||
| await applyMatchConfirmation(tx, claim, item.itemId, actor.userId); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect the full link endpoint guard + prior status expectations
rg -nP -C3 'CLAIM_LOCKED|applyMatchConfirmation|status === ClaimStatus' backend/src/routes/claims.tsRepository: 86unj/Foundit
Length of output: 2600
🏁 Script executed:
#!/bin/bash
sed -n '391,455p' backend/src/routes/claims.ts
printf '\n---\n'
sed -n '920,990p' backend/src/routes/claims.ts
printf '\n---\n'
sed -n '1515,1545p' backend/src/routes/claims.tsRepository: 86unj/Foundit
Length of output: 4363
🏁 Script executed:
#!/bin/bash
rg -n -C3 'claim_item_linked|CLAIM_LOCKED|under_review|relinked|applyMatchConfirmation|ClaimStatus\.(submitted|under_review|approved|rejected|picked_up)' backend test testsRepository: 86unj/Foundit
Length of output: 13491
Guard relinking to unfinalized claims
Relinking only blocks picked_up, so approved and rejected claims still reach applyMatchConfirmation. That helper always rewrites the claim to under_review and sends a new match_found notification, which can roll back a finalized review and notify the student incorrectly. Restrict this path to submitted/under_review claims, or skip the status/notification reset when the claim is already linked.
🤖 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 `@backend/src/routes/claims.ts` around lines 976 - 977, Relinking currently
allows finalized claims to flow through applyMatchConfirmation, which can reset
a claim back to under_review and emit a new match_found notification. Update the
relinking path in claims.ts around the
prisma.$transaction/applyMatchConfirmation flow to only allow submitted or
under_review claims, or make applyMatchConfirmation preserve approved/rejected
state and skip notification resets when the claim is already linked. Use the
existing claim status checks and the applyMatchConfirmation helper to locate the
logic.
| const [selectedMatchId, setSelectedMatchId] = useState<string | null>(null); | ||
| const [verification, setVerification] = | ||
| useState<VerificationState>(initialVerification); | ||
|
|
||
| useEffect(() => { | ||
| let active = true; | ||
|
|
||
| async function load() { | ||
| setLoading(true); | ||
| setError(''); | ||
|
|
||
| try { | ||
| const [claimData, campusData, matchData] = await Promise.all([ | ||
| fetchClaimById(claimId), | ||
| fetchCampuses().catch(() => [] as Campus[]), | ||
| fetchMatchSuggestions(claimId).catch(() => [] as MatchSuggestion[]), | ||
| ]); | ||
|
|
||
| if (!active) return; | ||
| setClaim(claimData); | ||
| setCampuses(campusData); | ||
| setSuggestions(matchData); | ||
| if (matchData.length > 0) { | ||
| setSelectedMatchId(matchData[0].matchId); | ||
| } | ||
| } catch (err) { | ||
| if (!active) return; | ||
| const message = | ||
| err instanceof Error ? err.message : 'Failed to load claim.'; | ||
| if (message.toLowerCase().includes('not found')) { | ||
| setNotFoundState(true); | ||
| } else { | ||
| setError(message); | ||
| } | ||
| } finally { | ||
| if (active) setLoading(false); | ||
| } | ||
| } | ||
|
|
||
| load(); | ||
| return () => { | ||
| active = false; | ||
| }; | ||
| }, [claimId]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
sed -n '1,340p' foundit-ui/app/security/claims/[id]/page.tsx | cat -nRepository: 86unj/Foundit
Length of output: 10066
🏁 Script executed:
rg -n "isVerificationComplete|canRelease|ClaimReleaseCard|setVerification\\(|initialVerification|notFoundState" foundit-ui/app foundit-ui/components -g '!**/node_modules/**'Repository: 86unj/Foundit
Length of output: 2348
🏁 Script executed:
sed -n '1,260p' foundit-ui/components/claims/ClaimMatchPanel.tsx | cat -nRepository: 86unj/Foundit
Length of output: 5815
🏁 Script executed:
sed -n '1,220p' foundit-ui/components/claims/ClaimReleaseCard.tsx | cat -nRepository: 86unj/Foundit
Length of output: 1729
Reset verification state on claim changes. verification persists across claimId navigation, so a previously completed checklist can leave ClaimReleaseCard enabled on the next claim before its checklist is filled out. Reset it when loading a new claim; selectedMatchId can be cleared there too.
🤖 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 `@foundit-ui/app/security/claims/`[id]/page.tsx around lines 62 - 105, The
claim-loading effect in page.tsx keeps the previous verification state when
claimId changes, so ClaimReleaseCard can stay enabled for the next claim before
its checklist is completed. In the useEffect that runs load(), reset
verification back to initialVerification and clear selectedMatchId as soon as a
new claim is being loaded, alongside the existing setLoading/setError handling.
Keep the reset tied to the claimId-driven load flow so each claim starts with a
fresh VerificationState.
| @@ -0,0 +1,56 @@ | |||
| 'use client'; | |||
|
|
|||
| import { Flex, Grid, Heading, Stack, Text } from '@chakra-ui/react'; | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove unused Text import.
Flagged by lint: Text is imported but never used in this file.
🧹 Fix
-import { Flex, Grid, Heading, Stack, Text } from '`@chakra-ui/react`';
+import { Flex, Grid, Heading, Stack } from '`@chakra-ui/react`';📝 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.
| import { Flex, Grid, Heading, Stack, Text } from '@chakra-ui/react'; | |
| import { Flex, Grid, Heading, Stack } from '`@chakra-ui/react`'; |
🧰 Tools
🪛 GitHub Check: Frontend (Lint + Typecheck + Test + Build)
[warning] 3-3:
'Text' is defined but never used
🤖 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 `@foundit-ui/components/claims/ClaimMatchedItemCard.tsx` at line 3, Remove the
unused Text import from ClaimMatchedItemCard so the import list only includes
the Chakra components actually referenced in the component. Update the top-level
import statement in ClaimMatchedItemCard to drop Text and keep the remaining
symbols like Flex, Grid, Heading, and Stack aligned with what the file uses.
Source: Linters/SAST tools
| useEffect(() => { | ||
| if (campusesLoading || campuses.length === 0) return; | ||
|
|
||
| const hasValidSelection = campuses.some( | ||
| (campus) => campus.campusId === campusId | ||
| ); | ||
| if (!hasValidSelection) { | ||
| const fallback = | ||
| campuses.find((campus) => campus.campusId === defaultCampusId) | ||
| ?.campusId ?? campuses[0].campusId; | ||
| setCampusId(fallback); | ||
| } | ||
| }, [campuses, campusesLoading, defaultCampusId, campusId, setCampusId]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Submit remains enabled even if campuses resolves empty.
The auto-fallback effect only runs if (campuses.length === 0) return;, so when the campuses list is genuinely empty (fetch failure or no campuses configured), campusId never gets corrected. The Submit button at Line 209 is only disabled on isSubmitting || campusesLoading, not on a missing/invalid campusId, so a submission could go out with an empty or stale campus id, only to be rejected server-side (CAMPUS_NOT_FOUND, per the linked backend handler).
🛡️ Suggested guard
<Button
variant="primary"
minW="140px"
- disabled={form.isSubmitting || campusesLoading}
+ disabled={form.isSubmitting || campusesLoading || !campusId}
loading={form.isSubmitting}Also applies to: 131-134, 209-216
🤖 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 `@foundit-ui/components/SecurityFoundItemReportForm.tsx` around lines 38 - 50,
The submit flow in SecurityFoundItemReportForm can still proceed when campuses
is empty because the fallback effect exits early and never corrects campusId.
Update the useEffect around campuses/defaultCampusId/campusId to handle the
empty-list case by clearing or invalidating the selection, and make the Submit
disabled state in the form check for a missing or invalid campusId in addition
to isSubmitting and campusesLoading. Use the existing setCampusId, campusId,
campuses, and defaultCampusId logic to keep the selected campus always valid
before submission.
Summary by CodeRabbit
New Features
Bug Fixes