Skip to content

feat: claims workflow - #101

Merged
86unj merged 3 commits into
mainfrom
Yun/security-claims
Jul 6, 2026
Merged

feat: claims workflow#101
86unj merged 3 commits into
mainfrom
Yun/security-claims

Conversation

@86unj

@86unj 86unj commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added a security claims list with search, sorting, filtering, and pagination.
    • Added a detailed claim view with workflow steps, verification checklist, match suggestions, and item/claim detail cards.
    • Added support for viewing related claim images and match-related information.
  • Bug Fixes

    • Expanded claim status handling so more review states display and process correctly.
    • Improved item expiration/disposal behavior to reject more associated claims.
    • Refined form behavior to keep campus selection valid when available options change.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@86unj, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b8c358fb-9b59-44c8-b6dd-dc76996259fb

📥 Commits

Reviewing files that changed from the base of the PR and between 0d48bce and 5278e6b.

📒 Files selected for processing (4)
  • backend/prisma/migrations/20260705180000_add_photo_upload_session/migration.sql
  • backend/src/routes/claims.ts
  • backend/src/validators/claims.ts
  • foundit-ui/app/report-found/[token]/page.tsx
📝 Walkthrough

Walkthrough

Backend 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.

Changes

Backend claims/items API

Layer / File(s) Summary
Claim detail DTO and image resolution
backend/src/routes/claims.ts
Adds studentNumber to claim mappings, extends claimDetailSelect with ordered images, and makes toClaimDetailDto async to resolve image URLs.
Cursor-based claim list pagination
backend/src/routes/claims.ts
Introduces claimListOrderBy and getClaimListCursorWhere, replacing skip/cursor Prisma paging with an AND-combined where clause.
Status transitions and match confirmation helper
backend/src/routes/claims.ts
Updates status transition rules, adds shared applyMatchConfirmation helper used by link/status endpoints, and awaits the async DTO in multiple responses.
Match suggestion generation and review
backend/src/routes/claims.ts
Computes scored candidates, upserts only qualifying suggestions, transitions claim to under_review, and reworks suggestion review to fetch parent claim and use applyMatchConfirmation.
Claim status enum and item status transition updates
backend/src/validators/claims.ts, backend/src/routes/items.ts
Adds match_found/match_confirmed to accepted claim statuses and to the item expire/dispose claim-rejection filter.

Estimated code review effort: 4 (Complex) | ~60 minutes

Security claims UI

Layer / File(s) Summary
Claim types and API client updates
foundit-ui/types/claims.ts, foundit-ui/lib/api/claims.ts
Extends ApiClaimStatus/SecurityClaimDetail, adds MatchSuggestion types, and adds fetchMatchSuggestions.
Claim display/workflow utilities
foundit-ui/utils/claimDisplay.ts
Adds functions for status queue/badges, detail mode, workflow steps, item name/date formatting, manual search links, and status filtering.
Reusable claim detail sub-components
foundit-ui/components/claims/*
Adds ClaimCard, ClaimDetailField, ClaimDetailHeader, ClaimMatchCard/Panel/EmptyState, claimant/claimed/matched item cards, appointment, release, verification checklist, and student notification cards.
Claim detail page
foundit-ui/app/security/claims/[id]/page.tsx
Loads claim/campuses/suggestions concurrently, derives workflow state, and renders matching vs pre/post-match layouts.
Claims list page
foundit-ui/app/security/claims/page.tsx
Loads campuses/claims, filters/searches/sorts client-side, paginates, and renders a sortable claims table.
ClaimCard and RecentClaimsTable refactor
foundit-ui/components/ClaimCard.tsx, foundit-ui/components/dashboard/RecentClaimsTable.tsx
Refactors both components to derive status/name/action via shared claimDisplay utilities instead of local logic.
Campus selection form fixes
foundit-ui/components/SecurityFoundItemReportForm.tsx, foundit-ui/hooks/useReportFoundItemForm.ts, foundit-ui/components/SelectInput.tsx
Replaces default-campus preselection with campusId auto-selection logic and moves the id attribute to NativeSelect.Field with htmlFor on labels.

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
Loading
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)
Loading

Possibly related PRs

  • 86unj/Foundit#55: Overlaps with this PR's ClaimCard.tsx refactor, both touching the same props/status model changes.
  • 86unj/Foundit#77: Builds on the same claim workflow endpoints in backend/src/routes/claims.ts further reworked here.
  • 86unj/Foundit#93: Introduced RecentClaimsTable.tsx, which this PR refactors to use shared claimDisplay utilities.

Suggested reviewers: renv39, hnam10, humbeatbox

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and correctly points to the main change area: claims workflow updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Yun/security-claims

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

🧹 Nitpick comments (11)
backend/src/routes/claims.ts (2)

979-982: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant re-fetch. applyMatchConfirmation already returns the updated claim selected with claimDetailSelect, so the findUniqueOrThrow re-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 win

Remove stale match suggestions during regeneration. score >= 60 candidates are upserted, but older rows for the same claim are never cleared, so reruns can keep returning outdated suggestions from GET /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 tradeoff

Table 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 win

Unbounded pagination button list.

pageNumbers renders 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 value

Status filter dropdown omits "approved".

matchesClaimStatusFilter supports an 'approved' filter case, but this select only offers needs_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 value

Double onSelect invocation on click.

The wrapping Box fires onSelect via onClick, and the radio's onChange also fires onSelect for 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 win

Duplicate formatItemId helper.

Identical function is redefined in ClaimMatchedItemCard.tsx. Consider moving it to foundit-ui/utils/claimDisplay.ts alongside 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 win

Consider a typed union for filter instead of string.

matchesClaimStatusFilter accepts a bare string and silently falls through to default: return true for any unrecognized value (same as the empty-filter case). Narrowing filter to 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 win

Badge doesn't reflect canRelease state.

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 win

Not-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 fetchClaimById surface the HTTP status (e.g., throw a typed error with status), and check err.status === 404 here 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 win

Checkbox lacks an associated label for screen readers.

The checkbox at Line 69 has no htmlFor/aria-label tying it to item.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

📥 Commits

Reviewing files that changed from the base of the PR and between ddbd3c2 and 0d48bce.

📒 Files selected for processing (27)
  • backend/src/routes/claims.ts
  • backend/src/routes/items.ts
  • backend/src/validators/claims.ts
  • foundit-ui/app/security/claims/[id]/page.tsx
  • foundit-ui/app/security/claims/page.tsx
  • foundit-ui/components/ClaimCard.tsx
  • foundit-ui/components/SecurityFoundItemReportForm.tsx
  • foundit-ui/components/SelectInput.tsx
  • foundit-ui/components/claims/ClaimAppointmentCard.tsx
  • foundit-ui/components/claims/ClaimCard.tsx
  • foundit-ui/components/claims/ClaimClaimantCard.tsx
  • foundit-ui/components/claims/ClaimDetailField.tsx
  • foundit-ui/components/claims/ClaimDetailHeader.tsx
  • foundit-ui/components/claims/ClaimMatchCard.tsx
  • foundit-ui/components/claims/ClaimMatchEmptyState.tsx
  • foundit-ui/components/claims/ClaimMatchPanel.tsx
  • foundit-ui/components/claims/ClaimMatchedItemCard.tsx
  • foundit-ui/components/claims/ClaimReleaseCard.tsx
  • foundit-ui/components/claims/ClaimStatusStepper.tsx
  • foundit-ui/components/claims/ClaimVerificationChecklist.tsx
  • foundit-ui/components/claims/ClaimedItemCard.tsx
  • foundit-ui/components/claims/StudentNotificationCard.tsx
  • foundit-ui/components/dashboard/RecentClaimsTable.tsx
  • foundit-ui/hooks/useReportFoundItemForm.ts
  • foundit-ui/lib/api/claims.ts
  • foundit-ui/types/claims.ts
  • foundit-ui/utils/claimDisplay.ts

Comment on lines +976 to +977
const updated = await prisma.$transaction(async (tx) => {
await applyMatchConfirmation(tx, claim, item.itemId, actor.userId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ 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.ts

Repository: 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.ts

Repository: 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 tests

Repository: 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.

Comment on lines +62 to +105
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]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,340p' foundit-ui/app/security/claims/[id]/page.tsx | cat -n

Repository: 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 -n

Repository: 86unj/Foundit

Length of output: 5815


🏁 Script executed:

sed -n '1,220p' foundit-ui/components/claims/ClaimReleaseCard.tsx | cat -n

Repository: 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';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 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`';
As per static analysis hints, `'Text' is defined but never used`.
📝 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
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

Comment on lines +38 to +50
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]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 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.

Comment thread foundit-ui/hooks/useReportFoundItemForm.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant