Skip to content

[Blade] Cleanup#418

Merged
DVidal1205 merged 11 commits intomainfrom
blade/form-fix
Mar 10, 2026
Merged

[Blade] Cleanup#418
DVidal1205 merged 11 commits intomainfrom
blade/form-fix

Conversation

@DVidal1205
Copy link
Contributor

@DVidal1205 DVidal1205 commented Mar 10, 2026

Why

There were some weird bugs that were either really old, or really exaggerated by the recent resolution of #407

What

  • Resolves mapped objects not having a keys prop
  • Makes the tRPC form connector integration more fault tolerant (whitespace / case proof) fixing a handful of weird errors
  • Fixes ZOD error presentation and ordering on forms
  • Removes the pr-check job (it was flaky)
  • Fixes a weird scroll bug on manual check in
  • Fix setting points to 0 on a n event update

Closes: #407

Test Plan

Pull and test, pretty trivial changes though.

Checklist

  • Database: No schema changes, OR I have contacted the Development Lead to run db:push before merging
  • Environment Variables: No environment variables changed, OR I have contacted the Development Lead to modify them on Coolify BEFORE merging.

Summary by CodeRabbit

  • New Features

    • Richer form validation with type-specific, user-friendly error messages and conditional error display.
  • Bug Fixes

    • Prevented invalid form submissions and improved field-matching (case-insensitive/flexible) for more reliable imports.
    • Stabilized list rendering to avoid UI inconsistencies.
  • UI Updates

    • Improved search UX with automatic scroll reset.
    • Check-in page copy updated to "Club Check-in".
    • Expanded allowed remote image sources.
  • Other

    • Points handling now respects explicit overrides and uses numeric inputs where applicable.

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Removes the PR-specific pr-checks job from CI, adds an allowed remote image host, refactors form validation and error surfacing, enhances form-to-response field matching, changes event/member points handling, adds React keys, sorts filter options, and updates several UI texts and type/const definitions across packages.

Changes

Cohort / File(s) Summary
CI & Next config
/.github/workflows/ci.yml, apps/blade/next.config.js
Deleted the pr-checks job from CI; added a new images.remotePatterns hostname entry.
Form validation & utils
packages/utils/src/forms.client.ts, packages/consts/src/forms/index.ts
Reworked validation plumbing: added per-question validation metadata and errors, new FormQuestionType types/metadata, changed getValidationError signature to accept showErrors, and mapped zod issues to user-friendly messages.
Form runner & form UX
apps/blade/src/app/_components/forms/form-runner.tsx, apps/blade/src/app/_components/forms/connection-handler.ts
Added hasAttemptedSubmit and gating of error display; changed submit flow to only call onSubmit when valid; implemented two-step trimmed + case-insensitive form-field matching and missingFields diagnostics.
Event & member points
apps/blade/src/app/_components/admin/club/events/update-event.tsx, apps/blade/src/app/_components/admin/hackathon/events/update-event.tsx, apps/blade/src/app/_components/admin/club/members/update-member.tsx, packages/api/src/routers/event.ts
Made points optional in update payloads and prioritized explicit input when present; changed member points schema from string->number and switched inputs to numeric handling and direct numeric propagation to API.
Check-in components (controlled search)
apps/blade/src/app/_components/admin/club/check-in/manual-entry-form.tsx, apps/blade/src/app/_components/admin/hackathon/check-in/manual-entry-form.tsx
Converted CommandInput to a controlled input with searchValue and commandListRef; added effect to reset command list scrollTop on search changes; removed explicit defaultValue="" on selects.
Roles list rendering keys
apps/blade/src/app/_components/admin/roles/roleassign.tsx, apps/blade/src/app/_components/admin/roles/roletable.tsx
Added missing React key props for list/table items to avoid rendering warnings and potential reordering bugs.
UI text & sorting
apps/blade/src/app/admin/club/check-in/page.tsx, apps/blade/src/app/_components/shared/filter-options.ts
Adjusted static copy from "Hackathon" → "Club" for the club check-in page; added sorting of filter options by count (desc) with lexicographic tie-break.
Miscellaneous
packages/..., apps/... (various small edits)
Minor imports/formatting and small adjustments across several files tied to the above changes.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

Major, Blade, Constants, UI, Bug

Suggested reviewers

  • DGoel1602
  • alexanderpaolini
  • must108
🚥 Pre-merge checks | ✅ 6 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title lacks a required issue number in brackets and is too vague to describe the substantive changes across multiple systems. Update title to format [#407] with a more descriptive summary, e.g. '[#407] Fix form validation, points handling, and React key warnings' to clearly indicate the scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (6 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR addresses core coding objectives from #407: resolves Zod validation display issues, fixes form connector fault tolerance, handles numeric points updates, and adds React keys to lists.
Out of Scope Changes check ✅ Passed The PR includes changes aligned with #407's package upgrade resolution: form validation fixes, connector robustness, points handling, and React warnings. Removing the flaky pr-check job is reasonable infrastructure cleanup.
No Hardcoded Secrets ✅ Passed No hardcoded secrets, API keys, passwords, tokens, or sensitive credentials found in any modified files.
Validated Env Access ✅ Passed The PR does not introduce any violations of the validated env access rule. All 16 modified files were verified and confirmed to contain no direct process.env usage outside of allowed locations.
No Typescript Escape Hatches ✅ Passed No instances of any, @ts-ignore, @ts-expect-error, or non-null assertions found in modified TypeScript files.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch blade/form-fix

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

❤️ Share

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

coderabbitai[bot]
coderabbitai bot previously requested changes Mar 10, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
apps/blade/src/app/_components/admin/hackathon/events/update-event.tsx (1)

164-169: ⚠️ Potential issue | 🟠 Major

Bug: useEffect doesn't react to tag changes.

The effect reads form.getValues("tag") but the dependency array only includes form. Since form is a stable reference, this effect only runs once on mount. The points field won't auto-update when the user changes the tag dropdown.

To fix, use form.watch() to subscribe to tag changes:

🐛 Proposed fix
+  const watchedTag = form.watch("tag");
+
   // Auto-update points when tag changes
   useEffect(() => {
-    const currentTag = form.getValues("tag");
-    const points = EVENTS.EVENT_POINTS[currentTag] || 0;
+    const points = EVENTS.EVENT_POINTS[watchedTag] || 0;
     form.setValue("points", points);
-  }, [form]);
+  }, [form, watchedTag]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/blade/src/app/_components/admin/hackathon/events/update-event.tsx`
around lines 164 - 169, The effect that auto-updates points uses
form.getValues("tag") but only depends on [form], so it runs once; change the
effect to subscribe to tag changes via form.watch("tag") (or store the watched
value in a variable returned by form.watch and use that in the dependency array)
and then compute points = EVENTS.EVENT_POINTS[tag] || 0 and call
form.setValue("points", points); update the useEffect to depend on the watched
tag value instead of the stable form reference so points update whenever the tag
changes.
🧹 Nitpick comments (1)
apps/blade/src/app/_components/admin/roles/roleassign.tsx (1)

171-178: Use the state setters instead of mutating the backing objects.

These handlers mutate filterRoles, checkedUsers, and checkedRoles in place, then rely on sUpd(!upd) to force a rerender. That pattern is brittle under React batching and makes the UI depend on a manual escape hatch instead of state changes.

♻️ Suggested cleanup
- onCheckedChange={(c) => {
-   filterRoles[v.id] = c == true;
-   sUpd(!upd);
- }}
+ onCheckedChange={(c) =>
+   _setFilterRoles((prev) => ({ ...prev, [v.id]: c === true }))
+ }

- onCheckedChange={(c) => {
-   checkedUsers[v.id] = c == true;
-   sUpd(!upd);
- }}
+ onCheckedChange={(c) =>
+   setCheckedUsers((prev) => ({ ...prev, [v.id]: c === true }))
+ }

- onCheckedChange={(c) => {
-   checkedRoles[v.id] = c == true;
-   sUpd(!upd);
- }}
+ onCheckedChange={(c) =>
+   _setCheckedRoles((prev) => ({ ...prev, [v.id]: c === true }))
+ }

Once these updates go through the setters, the upd state can be removed as well.

Also applies to: 216-227, 326-333

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

In `@apps/blade/src/app/_components/admin/roles/roleassign.tsx` around lines 171 -
178, The handlers currently mutate the backing objects (filterRoles,
checkedUsers, checkedRoles) and call sUpd(!upd) to force a rerender; replace
those in-place mutations with proper state updates via their setters (e.g.,
setFilterRoles(prev => ({ ...prev, [v.id]: Boolean(c) })), setCheckedUsers(prev
=> ({ ...prev, [userId]: Boolean(checked) })), setCheckedRoles(...)) using the
functional form to avoid stale closures, and remove the upd/sUpd forced-rerender
pattern and the upd state entirely; update all Checkbox onCheckedChange handlers
(and any similar click handlers referenced around the same areas) to use these
setters so changes are immutable and React batching-safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/blade/next.config.js`:
- Around line 29-32: Replace the hardcoded sslip.io hostname
("minio-y44gsgsskc4ko4kkwsg0csoc.135.237.97.107.sslip.io") in the Next.js
external image host entry with the stable MinIO hostname defined in your
constants (use the exported MinIO host constant from
packages/consts/src/minio.ts) or remove the sslip.io entry if it was only for
temporary local testing; if you intentionally need both hostnames, add a short
comment explaining why and move the sslip.io value into an environment-based
config so next.config.js does not hardcode an IP-derived alias.

In `@apps/blade/src/app/_components/admin/club/check-in/manual-entry-form.tsx`:
- Around line 41-42: The controlled search input (searchValue / setSearchValue)
is not cleared when the member-selection popover closes or when a member is
chosen, leaving stale queries that incorrectly filter the list; fix this by
calling setSearchValue("") in both the popover close handler and immediately
after a member is selected (where member selection logic runs), and also apply
the same clear when the alternate popover instance referenced around the other
block (lines ~183-188) closes or completes selection; locate the popover onClose
prop and the member-selection handler in manual-entry-form.tsx and add
setSearchValue("") there so the input resets on close/selection.

In
`@apps/blade/src/app/_components/admin/hackathon/check-in/manual-entry-form.tsx`:
- Around line 45-46: The controlled search state (searchValue) persists across
hackathon switches causing the popover to remain filtered; add a useEffect that
watches the hackathon context/ID prop and calls setSearchValue("") (and
optionally clears commandListRef.current if you want to reset focus/scroll) so
the search is reset whenever the hackathon changes; update the logic around
searchValue/setSearchValue and commandListRef referenced in
manual-entry-form.tsx (also apply the same reset where search state is used
around lines ~288-293).

In `@packages/utils/src/forms.client.ts`:
- Around line 93-115: The format-specific error branches inside the
isMissingField block are unreachable because isMissingField is true only when
rawMessage.includes("received undefined"), so the nested condition
!rawMessage.includes("received undefined") will never be true; remove the inner
isFormatSpecific/... question.type branches from the isMissingField handling in
forms.client.ts (the block that checks isMissingField and rawMessage.includes)
and keep a single "Answer required" return there, since format-specific messages
are already handled by the invalid_format/invalid_type logic elsewhere.

---

Outside diff comments:
In `@apps/blade/src/app/_components/admin/hackathon/events/update-event.tsx`:
- Around line 164-169: The effect that auto-updates points uses
form.getValues("tag") but only depends on [form], so it runs once; change the
effect to subscribe to tag changes via form.watch("tag") (or store the watched
value in a variable returned by form.watch and use that in the dependency array)
and then compute points = EVENTS.EVENT_POINTS[tag] || 0 and call
form.setValue("points", points); update the useEffect to depend on the watched
tag value instead of the stable form reference so points update whenever the tag
changes.

---

Nitpick comments:
In `@apps/blade/src/app/_components/admin/roles/roleassign.tsx`:
- Around line 171-178: The handlers currently mutate the backing objects
(filterRoles, checkedUsers, checkedRoles) and call sUpd(!upd) to force a
rerender; replace those in-place mutations with proper state updates via their
setters (e.g., setFilterRoles(prev => ({ ...prev, [v.id]: Boolean(c) })),
setCheckedUsers(prev => ({ ...prev, [userId]: Boolean(checked) })),
setCheckedRoles(...)) using the functional form to avoid stale closures, and
remove the upd/sUpd forced-rerender pattern and the upd state entirely; update
all Checkbox onCheckedChange handlers (and any similar click handlers referenced
around the same areas) to use these setters so changes are immutable and React
batching-safe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: ac2bcf8f-22e7-471a-a656-18f4a5f4317c

📥 Commits

Reviewing files that changed from the base of the PR and between 881ae73 and 7a6a957.

📒 Files selected for processing (15)
  • .github/workflows/ci.yml
  • apps/blade/next.config.js
  • apps/blade/src/app/_components/admin/club/check-in/manual-entry-form.tsx
  • apps/blade/src/app/_components/admin/club/events/update-event.tsx
  • apps/blade/src/app/_components/admin/club/members/update-member.tsx
  • apps/blade/src/app/_components/admin/hackathon/check-in/manual-entry-form.tsx
  • apps/blade/src/app/_components/admin/hackathon/events/update-event.tsx
  • apps/blade/src/app/_components/admin/roles/roleassign.tsx
  • apps/blade/src/app/_components/admin/roles/roletable.tsx
  • apps/blade/src/app/_components/forms/connection-handler.ts
  • apps/blade/src/app/_components/forms/form-runner.tsx
  • apps/blade/src/app/_components/shared/filter-options.ts
  • apps/blade/src/app/admin/club/check-in/page.tsx
  • packages/api/src/routers/event.ts
  • packages/utils/src/forms.client.ts
💤 Files with no reviewable changes (1)
  • .github/workflows/ci.yml

Copy link
Contributor

@DGoel1602 DGoel1602 left a comment

Choose a reason for hiding this comment

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

Almost everything looks perfect except I just don't wanna repeat our form question types in a million places and go hunting when we need to add one

};

// Transform raw Zod error messages into user-friendly messages
const getUserFriendlyError = (
Copy link
Contributor

Choose a reason for hiding this comment

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

Look into error maps in zod instead, I think it makes it cleaner and would avoid redundant repeating yourself

https://zod.dev/error-customization?id=per-parse-error-customization

Copy link
Contributor

Choose a reason for hiding this comment

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

I second this

Copy link
Contributor

@DGoel1602 DGoel1602 left a comment

Choose a reason for hiding this comment

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

LGTM

coderabbitai[bot]
coderabbitai bot previously requested changes Mar 10, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/consts/src/forms/index.ts`:
- Around line 338-343: The validator currently uses the in operator against
FORM_QUESTION_TYPE_BY_VALUE which accepts inherited keys (e.g., "toString"),
causing invalid types to pass; update the check so it only accepts own
properties by replacing the in-based test with
Object.prototype.hasOwnProperty.call(FORM_QUESTION_TYPE_BY_VALUE, value) or,
preferably, replace the ad-hoc validator with a Zod enum built from
FORM_QUESTION_TYPES (e.g., z.enum(FORM_QUESTION_TYPES.map(t => t.value))) and
use that enum wherever FORM_QUESTION_TYPE_BY_VALUE is validated so downstream
code referencing FormQuestionTypeMetadata (e.g., validationErrors) only ever
receives real keys.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 159dbacd-9aed-470f-a8fd-ca4caa7145d2

📥 Commits

Reviewing files that changed from the base of the PR and between 7a6a957 and 96c06f0.

📒 Files selected for processing (2)
  • packages/consts/src/forms/index.ts
  • packages/utils/src/forms.client.ts

@DVidal1205 DVidal1205 added this pull request to the merge queue Mar 10, 2026
Merged via the queue into main with commit 82faebd Mar 10, 2026
7 checks passed
@DVidal1205 DVidal1205 deleted the blade/form-fix branch March 10, 2026 20:26
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.

3 participants