Conversation
- Bump eslint from ^9.12.0 to ^9.22.0 - Upgrade tailwindcss from ^3.4.14 to ^4.2.1 - Update React and related types from version 18.x to 19.x - Upgrade @next/eslint-plugin-next from ^14.2.15 to ^15.5.12 - Add @tailwindcss/postcss as a dependency - Remove tailwindcss-animate plugin from Tailwind configuration
Updates tRPC to 11.10, TanStack Query to 5.90.21, superjson to 2.2.6, cmdk to 1.1.1, @auth/drizzle-adapter to 1.11.1, and runs pnpm update -r to pull all other packages to their latest compatible versions.
…→6, @react-email/render 1→2
- next@16.1.6 across all apps and packages/auth
- eslint-config-next@16 in apps/2025
- stripe@20.4.0: remove deprecated { typescript: true } constructor option
- drizzle-kit@0.31.9 in packages/db
- resend@6.0.0 in packages/api, apps/tk, apps/cron
- @react-email/render@2.0.0 in apps/blade
Fix Next.js 16 / Turbopack 1.0 breaking changes:
- tailwind.config.ts: switch from named to default import for
tailwindcss/defaultTheme (fontFamily no longer a named export in v4)
- next.config.js: remove deprecated eslint.ignoreDuringBuilds key
(Next.js 16 no longer runs ESLint during builds)
- Upgraded @eslint/compat from ^1.4.1 to ^2.0.2 - Upgraded @next/eslint-plugin-next from ^15.5.12 to ^16.1.6 - Upgraded eslint-plugin-react-hooks from ^5.2.0 to ^7.0.1 - Upgraded prettier-plugin-tailwindcss from ^0.6.14 to ^0.7.2
- @stripe/react-stripe-js 3→5, @stripe/stripe-js 5→8
- @hookform/resolvers 3→5 (simplify useForm generic for v5 zod overloads)
- date-fns 3→4, sonner 1→2, react-markdown 9→10
- recharts 2→3 (rewrite chart.tsx prop types removed in v3 API)
- google-auth-library 9→10, googleapis 144→171
(convert JWT constructor from positional args to options object)
- node-cron 3→4
- jimp 0.22→1 (default export → named export { Jimp })
- Replace deprecated legacyBehavior/passHref <Link> pattern with modern Next.js <Link> usage in apps/2025 (sponsorPosters.tsx, partnerPosters.tsx) - Modernize Tailwind v4 class names across apps/club (9 files), apps/blade, and apps/2025: bg-gradient-* → bg-linear-*, [&_*]: → **, arbitrary px values → named shorthands, mask-image syntax update - Fix conflicting utility classes in club (text-transparent/text-white, block/hidden) - Fix JSX SVG attribute casing in apps/gemiknights (fill-rule → fillRule, clip-rule → clipRule, stop-color → stopColor, stroke-width → strokeWidth)
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughMonorepo-wide upgrades and compatibility changes: dependency bumps (Next 16, React 19, Tailwind, Recharts, Stripe, Jimp), Tailwind/PostCSS rewiring, many JSX SVG prop fixes, async route params/headers/cookies now awaited, Recharts typing workarounds, UI class and public API typing adjustments. No large new features added. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant NextPage as Page Component
participant Loader as Server/Data Loaders
participant TRPC as createContext/TRPC
Client->>NextPage: Request (route with params promise)
NextPage->>NextPage: await props.params / props.searchParams
NextPage->>Loader: call data fetchers / API
Loader->>TRPC: await headers() / await cookies()
TRPC->>Loader: return context (session, headers)
Loader->>NextPage: return data
NextPage->>Client: render/SSR response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 6 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (6 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
…adability and maintainability
…nd adjust delay calculation in GuildMembersDisplay
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
apps/blade/src/app/_components/dashboard/member/member-application-form.tsx (1)
191-195:⚠️ Potential issue | 🟡 MinorUse
origin: "file"instead oforigin: "number"for file size validation.In Zod v4's
too_bigissue format, theoriginproperty should reflect the semantic type being validated. Whilefile.sizeis technically a number, validating file uploads should useorigin: "file"to indicate a file-specific constraint. This matches Zod's design whereorigindescribes the validation context (e.g., "string" for string length, "array" for array length, "file" for file properties).Change both occurrences from
origin: "number"toorigin: "file".Also applies to: line 236
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/dashboard/member/member-application-form.tsx` around lines 191 - 195, The Zod too_big error objects in the member application resume validation are using origin: "number" but should use origin: "file"; update the two places in member-application-form.tsx where the resume file size validation constructs a too_big/validation error (look for the object with properties origin/maximum/inclusive/message and MINIO.MAX_RESUME_SIZE) to set origin: "file" instead of "number" so the error context correctly reflects a file-size validation.packages/ui/src/sheet.tsx (1)
92-92:⚠️ Potential issue | 🟡 MinorPre-existing typo:
focus: ring-offset-2has an errant space.Not introduced in this PR, but worth fixing while you're here. The space after
focus:breaks the variant binding, soring-offset-2applies unconditionally instead of only on focus.🔧 Suggested fix
- "focus: ring-offset-2 disabled:pointer-events-none", + "focus:ring-offset-2 disabled:pointer-events-none",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ui/src/sheet.tsx` at line 92, The class list contains a typo: the token "focus: ring-offset-2 disabled:pointer-events-none" has an extra space after "focus:" so the variant binding breaks; locate that string in packages/ui/src/sheet.tsx (e.g., where the Sheet/trigger classNames are defined) and change "focus: ring-offset-2" to "focus:ring-offset-2" so ring-offset-2 only applies on focus while keeping "disabled:pointer-events-none" as-is.apps/club/src/app/_components/landing/sponsors-assets/sponsor-marquee.tsx (1)
53-54:⚠️ Potential issue | 🟡 MinorImprove alt text for better accessibility.
While the second
<ul>is correctlyaria-hidden="true"(since it duplicates content for the infinite scroll animation), the alt attributes could be more descriptive:
- Line 53:
alt="logo"→ Use the company name for screen readers- Line 69:
alt="s"→ Appears to be a typoSince the second list is hidden from assistive tech, focus on fixing the first list. The second can use empty alt (
alt="") since it's decorative/duplicate.♿ Suggested fix
<Image src={`/logos/${logo.name}.svg`} height={0} width={0} - alt={"logo"} + alt={`${logo.name} logo`} className="xs:w-20 xs:h-20 h-20 w-20 scale-90 rounded-md object-contain sm:h-24 sm:w-24 md:h-28 md:w-28 lg:h-32 lg:w-32 xl:h-40 xl:w-40" />For the aria-hidden duplicate list:
<Image src={`/logos/${logo.name}.svg`} height={0} width={0} - alt={"s"} + alt="" className="xs:w-20 xs:h-20 h-20 w-20 scale-90 rounded-md object-contain sm:h-24 sm:w-24 md:h-28 md:w-28 lg:h-32 lg:w-32 xl:h-40 xl:w-40" />Also applies to: 69-70
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/club/src/app/_components/landing/sponsors-assets/sponsor-marquee.tsx` around lines 53 - 54, In the SponsorMarquee component, replace non-descriptive alt values on the rendered <img> in the visible sponsor list with the sponsor's actual name (e.g., use the sponsor.name or sponsor.title used in the sponsors.map that renders the first <ul>), fix the typo alt="s" to the correct name where that image is part of the accessible list, and set alt="" for images in the aria-hidden duplicate <ul> (the infinite-scroll copy) so those decorative duplicates are ignored by assistive tech; ensure you update the JSX that renders the <img> elements used by the visible list and by the duplicated aria-hidden list (refer to the sponsor mapping/rendering code inside SponsorMarquee).apps/guild/src/app/page.tsx (1)
16-29:⚠️ Potential issue | 🟠 MajorHandle
searchParamsasstring | string[] | undefinedto prevent runtime crashes.The current type only allows
string, but Next.js repeats query parameters as arrays—e.g.,?q=hello&q=worldproduces{ q: ["hello", "world"] }. Calling.trim()on an array throws at runtime.✅ Safer parsing patch
export default async function GuildPage(props: { searchParams: Promise<{ - q?: string; - page?: string; - ps?: string; - tag?: string; + q?: string | string[]; + page?: string | string[]; + ps?: string | string[]; + tag?: string | string[]; }>; }) { const searchParams = await props.searchParams; - const query = searchParams.q?.trim() ?? undefined; + const first = (v: string | string[] | undefined) => + Array.isArray(v) ? v[0] : v; + + const query = first(searchParams.q)?.trim() ?? undefined; + const ps = first(searchParams.ps); + const pageRaw = first(searchParams.page); + const tag = first(searchParams.tag); const pageSize: PageSize = - PAGE_SIZE_OPTIONS.find((n) => String(n) === searchParams.ps) ?? + PAGE_SIZE_OPTIONS.find((n) => String(n) === ps) ?? DEFAULT_PAGE_SIZE; - const currentPage = Number(searchParams.page ?? 0); - if (Number.isNaN(currentPage) || currentPage < 0) notFound(); + const currentPage = pageRaw ? Number.parseInt(pageRaw, 10) : 0; + if (!Number.isInteger(currentPage) || currentPage < 0) notFound(); const selectedTag: GUILD.GuildTag | undefined = - GUILD.GUILD_TAG_OPTIONS.find((t) => t === searchParams.tag) ?? undefined; + GUILD.GUILD_TAG_OPTIONS.find((t) => t === tag) ?? undefined;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/guild/src/app/page.tsx` around lines 16 - 29, The handler assumes searchParams.q/ps/page are strings and calls .trim() / String(...) directly, which will throw if Next.js supplies string[]; update the parsing in page.tsx (around searchParams, query, pageSize, PAGE_SIZE_OPTIONS, DEFAULT_PAGE_SIZE, currentPage) to first normalize each param to a single string (e.g., if Array.isArray(value) take value[0]) or undefined, then call .trim() and parse; do the same safe-normalization for searchParams.ps and searchParams.page before comparing to PAGE_SIZE_OPTIONS or calling Number(), and keep the existing NaN/negative check for currentPage.apps/tk/src/commands/dog.ts (1)
43-49:⚠️ Potential issue | 🟠 MajorReturn a user-facing error message in the catch path.
The catch block currently logs only, so users can end up with a silent failure/timeout. Reply with an ephemeral fallback message when command execution fails.
Proposed fix
} catch (err: unknown) { + const content = "I couldn't fetch a dog right now. Please try again in a moment."; + if (interaction.deferred || interaction.replied) { + await interaction.followUp({ content, ephemeral: true }); + } else { + await interaction.reply({ content, ephemeral: true }); + } if (err instanceof Error) { console.error(err.message); } else { console.error("An unknown error occurred: ", err); } }As per coding guidelines
apps/tk/**: Graceful error handling with user-friendly messages.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/tk/src/commands/dog.ts` around lines 43 - 49, The catch block catching "err" (catch (err: unknown)) currently only logs the error; update it to also send a user-facing ephemeral fallback reply (use the command's interaction/response object used in this command's execute/run handler, e.g., interaction.reply or interaction.followUp) with a short friendly message like "Something went wrong — please try again later" and mark it ephemeral so only the user sees it; keep the existing console.error logging (include err) and guard against sending a second reply if a response was already sent (check interaction.replied or equivalent) before replying.packages/ui/package.json (1)
68-74:⚠️ Potential issue | 🟠 MajorFix misnamed React catalog in pnpm-workspace.yaml.
The
react18catalog actually references React 19.2.4, not React 18. Rename the catalog to avoid confusion and align with the actual versions:Proposed fix
In
pnpm-workspace.yaml, rename the catalog:catalogs: - react18: + react19: react: 19.2.4 react-dom: 19.2.4Then update
packages/ui/package.json:peerDependencies: - "react": "catalog:react18", + "react": "catalog:react19",The current naming mismatch can cause confusion for maintainers and doesn't accurately reflect the versions being used.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ui/package.json` around lines 68 - 74, Update the catalog name used for React so it accurately reflects the actual version: rename the catalog identifier currently called "react18" to something like "react19" in pnpm-workspace.yaml, then update the react peerDependency in package.json (the "react": "catalog:react18" entry) to reference the new catalog name; ensure the dependency entry with actual package version ("react": "^19.2.4") and the peerDependencies entry stay consistent by using the same catalog identifier.
🧹 Nitpick comments (24)
apps/blade/src/app/_components/admin/club/members/update-member.tsx (3)
118-125: RedundantNumber()conversion.After the Zod
.transform()runs during validation,values.pointsis already a number. The explicitNumber(values.points)on line 118 is harmless but unnecessary—it's justNumber(aNumber).If you refactor to
z.coerce.number(), this entire intermediate variable can be removed. Otherwise, a quick cleanup:- const points = Number(values.points); - updateMember.mutate({ id: member.id, firstName: values.firstName, lastName: values.lastName, email: values.email, - points: points, + points: values.points,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/admin/club/members/update-member.tsx` around lines 118 - 125, The explicit Number conversion is redundant because values.points is already a number after Zod validation; remove the intermediate points variable and pass values.points directly into updateMember.mutate (or, if you switch to z.coerce.number(), also remove the Number() call wherever it appears). Update the call around updateMember.mutate to use points: values.points (or just points: values.points) and delete the const points = Number(values.points) line.
202-218: Consider addingtype="number"for the points input.A text input works but provides a suboptimal mobile experience (no numeric keyboard) and allows non-numeric entry that only fails at validation time. Using
type="number"gives users immediate feedback and the appropriate keyboard on mobile devices.<FormControl> - <Input placeholder="1000" {...field} /> + <Input type="number" placeholder="1000" {...field} /> </FormControl>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/admin/club/members/update-member.tsx` around lines 202 - 218, The Points input should use a numeric input type to improve mobile UX and prevent non-numeric entry: update the FormField rendering for name="points" so the Input (inside FormControl) has type="number" (optionally add inputMode="numeric" or a pattern if desired) and ensure the existing {...field} spread remains so value/onChange bindings in the FormField/FormControl flow are preserved; also verify any form parsing (e.g., in the submit handler or validation schema referenced by form) still converts the field to a number if required.
82-83: Flagged:@ts-expect-errorusage violates coding guidelines.The comment accurately explains why the suppression is needed (Zod
.transform()makes input≠output), but there's a cleaner path that avoids the escape hatch entirely.Since this PR upgrades to Zod 4, consider using
z.coerce.number()instead ofz.string().transform(...). Coercion schemas have unified input/output types, eliminating the type mismatch that forces this suppression.♻️ Refactor to eliminate `@ts-expect-error`
}).extend({ firstName: z.string().min(1, "Required"), lastName: z.string().min(1, "Required"), email: z.string().email("Invalid email").min(1, "Required"), - points: z.string().transform((points) => Number(points)), + points: z.coerce.number().int().min(0), phoneNumber: z .string() .regex(/^\d{10}|\d{3}-\d{3}-\d{4}$|^$/, "Invalid phone number"), }); const form = useForm({ - // `@ts-expect-error` -- schema uses .transform() so input≠output; ZodType<TIn,TIn> in useForm can't represent this schema: UpdateMemberSchema,Then on the input field (line 212), add
type="number"for better UX:- <Input placeholder="1000" {...field} /> + <Input type="number" placeholder="1000" {...field} />And remove the now-redundant conversion (line 118):
- const points = Number(values.points); updateMember.mutate({ id: member.id, ... - points: points, + points: values.points,As per coding guidelines:
**/*.{ts,tsx}- Flag usage of@ts-expect-error; suggest proper typing instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/admin/club/members/update-member.tsx` around lines 82 - 83, Replace the `@ts-expect-error` suppression by changing the numeric field in UpdateMemberSchema from z.string().transform(...) to z.coerce.number() so input/output types match Zod v4; then remove the manual conversion code that casts the field (the explicit Number/parseInt conversion near where the form values are processed) and update the corresponding input component to include type="number" (the input referenced in the form where the member age/id is entered) to improve UX; finally delete the `@ts-expect-error` line so TypeScript error is resolved by correct typing.packages/api/src/routers/forms.ts (1)
424-427: Extract duplicated Zod error formatting into one helper.Both branches now build nearly identical issue messages. Consolidating this avoids message drift and keeps API validation output consistent.
♻️ Suggested refactor
+const formatZodIssues = (issues: z.ZodIssue[], prefix: string) => { + const errorMessages = issues.map((err) => { + const path = err.path.join("."); + return path ? `${path}: ${err.message}` : err.message; + }); + return errorMessages.length > 0 + ? `${prefix}: ${errorMessages.join("; ")}` + : prefix; +}; ... -const errorMessages = response.error.issues.map((err) => { - const path = err.path.join("."); - return path ? `${path}: ${err.message}` : err.message; -}); -const errorMessage = - errorMessages.length > 0 - ? `Form response failed form validation: ${errorMessages.join("; ")}` - : "Form response failed form validation"; +const errorMessage = formatZodIssues( + response.error.issues, + "Form response failed form validation", +); ... -const errorMessages = validationResult.error.issues.map((err) => { - const path = err.path.join("."); - return path ? `${path}: ${err.message}` : err.message; -}); +const errorMessage = formatZodIssues( + validationResult.error.issues, + "Form response failed validation", +); throw new TRPCError({ - message: `Form response failed validation: ${errorMessages.join("; ")}`, + message: errorMessage, code: "BAD_REQUEST", });Also applies to: 505-508
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/api/src/routers/forms.ts` around lines 424 - 427, The Zod error formatting logic is duplicated where response.error.issues.map builds path-prefixed messages (seen in the errorMessages assignment and again around lines 505-508); extract that logic into a single helper (e.g., formatZodError or formatZodErrors) and replace both usages with calls to that helper so both branches produce identical messages; the helper should accept an array of Zod issues or a single issue and return the same `${path ? `${path}: ` : ""}${err.message}` output used currently by the errorMessages mapping, then import/declare and use that helper wherever response.error.issues.map was used.apps/2025/src/app/_components/partners/partnerPosters.tsx (1)
115-120: Add explicit link label for accessibility consistency.The image
altgives an accessible name today, but an explicitaria-labelon the link keeps semantics stable if internals change later.♿ Suggested tweak
<Link href={partner.link} + aria-label={`${partner.alt} partner link`} target="_blank" rel="noopener noreferrer" className="group relative flex h-full items-center justify-center rounded-none focus:outline-4 focus:outline-offset-2 focus:outline-[`#FBB03B`]" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/2025/src/app/_components/partners/partnerPosters.tsx` around lines 115 - 120, Add an explicit accessible name to the partner link by adding an aria-label to the Link component that uses the partner's identifying text (e.g., partner.name or partner.title) so the link's purpose remains clear even if the internal image alt changes; update the Link element (the one rendering href={partner.link}) to include aria-label={`Visit ${partner.name}`} (or similar) ensuring it matches the image alt semantics.apps/2025/src/app/_components/sponsors/sponsorPosters.tsx (1)
263-295: Avoid usingalttext as rendering logic keys.
Line 263andLine 287couple behavior to display copy. If alt text changes, layout logic can break unexpectedly. Use explicit config fields instead.🧩 Suggested refactor
interface Sponsor { src: string; alt: string; link: string; category: Tier; + renderVariant?: "default" | "googleDual"; + imageClassName?: string; // Grid positioning for sm+ screens (6-column layout) gridPosition: string; // Grid positioning for mobile (4-column layout) mobilePosition: string; ariaLabel: string; } - {sponsor.alt === "GOOGLE" ? ( + {sponsor.renderVariant === "googleDual" ? ( ... ) : ( <Image src={sponsor.src} alt={sponsor.alt} fill - className={`object-contain drop-shadow-sm ${ - sponsor.alt === "NVIDIA" - ? "p-2 sm:p-4 md:scale-125 md:p-6" - : "p-4 md:p-8" - } ${sponsor.alt === "GITHUB" ? "p-2 sm:p-4 md:scale-125 md:p-6" : ""} ${ - sponsor.alt === "SHINIES PROPS" ? "brightness-0" : "" - }`} + className={`object-contain drop-shadow-sm ${sponsor.imageClassName ?? "p-4 md:p-8"}`} sizes="(max-width: 640px) 45vw, (max-width: 1024px) 16vw, 12vw" draggable={false} /> )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/2025/src/app/_components/sponsors/sponsorPosters.tsx` around lines 263 - 295, The rendering logic in sponsorPosters relies on sponsor.alt (e.g., checks like sponsor.alt === "GOOGLE", "NVIDIA", "GITHUB", "SHINIES PROPS") which couples display/layout to copy; add an explicit config field on the sponsor objects (e.g., sponsor.variant or sponsor.key) and update the JSX in sponsorPosters to switch only on that field for selecting images and classes, replacing all sponsor.alt comparisons (including the GOOGLE branch that picks google-mobile.svg vs google.svg and the conditional className adjustments for NVIDIA/GITHUB/SHINIES PROPS) so alt remains solely the Image alt text and layout logic uses the new variant/key.apps/blade/src/app/_components/settings/member-profile-form.tsx (2)
252-252: Default value type inconsistency with schema.Same issue as
member-application-form.tsx: the schema expects a string forgradYear(before transform), but the default is a number.♻️ Suggested fix
- gradYear: Number(initTermYear.year), + gradYear: initTermYear.year, // already a string from line 98Note:
initTermYear.yearis already a string (see line 98:year: String(d.getUTCFullYear())), so theNumber()conversion here actually introduces the mismatch.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/settings/member-profile-form.tsx` at line 252, The default for gradYear in the member-profile-form should be a string to match the schema: change the assignment in the default object (the gradYear property set from initTermYear) to use the existing string value from initTermYear.year instead of wrapping it with Number(), i.e., remove the Number(...) conversion so gradYear === initTermYear.year; this mirrors the fix applied in member-application-form and keeps the pre-transform type consistent with the schema.
117-117: TypeScript escape hatch — same pattern as member-application-form.The suppression is well-documented. See the earlier comment in
member-application-form.tsxfor context on potential long-term improvements.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/settings/member-profile-form.tsx` at line 117, Keep the TypeScript suppression on the line with "// `@ts-expect-error` -- schema uses .transform() so input≠output; ZodType<TIn,TIn> in useForm can't represent this" as the same escape hatch used in member-application-form.tsx; update or copy the longer explanatory comment from member-application-form.tsx near the suppression to document why useForm + Zod transform requires the ts-expect-error and mention this as a TODO for future refactor (reference symbols: useForm, ZodType, .transform()).apps/blade/src/app/_components/dashboard/member/member-application-form.tsx (2)
93-93: TypeScript escape hatch noted — consider documenting or revisiting.The
@ts-expect-errorcomment is well-explained: Zod transforms create schemas where input ≠ output types, anduseForm's typing can't represent this directly. This is a known limitation when using.transform()with strongly-typed form libraries.For future maintainability, you might explore creating a custom type helper or checking if
@forge/ui/form(or your version ofreact-hook-form) supports separate input/output generics. That said, this suppression is reasonable given the constraint.As per coding guidelines:
**/*.{ts,tsx}— Flag@ts-expect-errorusage and suggest proper typing.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/dashboard/member/member-application-form.tsx` at line 93, The `@ts-expect-error` on the useForm call (due to Zod schema .transform() producing input≠output) should not be left undocumented: either replace the suppression with a small, explicit typed workaround (e.g., a custom helper type/cast that expresses the form input type) or add a clear inline TODO comment referencing the exact limitation and the affected symbol (useForm and the Zod schema variable in member-application-form.tsx), and add a brief note about revisiting when react-hook-form / `@forge/ui/form` gains separate input/output generics; ensure the change removes the generic `@ts-expect-error` flag or documents why it remains and adds a lint-exempt comment if required by project policy.
259-259: Default value type mismatch with schema input type.The schema defines
gradYearas:gradYear: z.string().regex(/^\d{4}$/).transform(Number)This expects a string input (for regex validation) that transforms to a number. Setting the default as a number (
new Date().getFullYear() + 1) works at runtime because HTML inputs coerce values, but it's semantically inconsistent.♻️ Suggested fix for type consistency
- gradYear: new Date().getFullYear() + 1, + gradYear: String(new Date().getFullYear() + 1),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/dashboard/member/member-application-form.tsx` at line 259, The default for gradYear in the form is a number but the schema (gradYear: z.string().regex(/^\d{4}$/).transform(Number)) expects a string input; change the default to a four-digit string (e.g., String(new Date().getFullYear() + 1) or .toString()) where gradYear is set in the member application form component so the initial/default value matches the schema's input type and regex validation.apps/club/src/app/_components/links/button.tsx (1)
14-59: Reduce duplicated JSX between button and anchor branches.The two branches duplicate class strings and inner content, which makes style updates easy to miss in one branch.
Refactor sketch
export default function Button({ icon: Icon, href, ...props }: Props) { const innerText = props.children; + const baseClassName = + "font-pragati transition-colors bg-linear-to-br group relative inline-flex transform items-center gap-2 rounded-[200px] border border-[`#D8B4FE`] bg-transparent px-14 py-3 text-[12px] font-normal leading-normal tracking-[1px] text-white duration-100 hover:bg-[`#D8B4FE`] hover:text-[`#0F172A`] md:px-24 md:text-[13px] md:font-bold md:tracking-[.8px]"; + + const content = ( + <> + {Icon && ( + <Icon + className="**:transition-colors **:duration-100 group-hover:**:fill-[`#0F172A`] absolute left-6 h-4 w-4 fill-current md:left-14 md:h-5 md:w-5" + aria-hidden="true" + /> + )} + {innerText} + <MenuHorizontalSVG + className="md:right-15 **:transition-colors **:duration-100 **:stroke-[`#D7B4FE`] group-hover:**:stroke-[`#0F172A`] absolute right-6 h-4 w-4 md:h-5 md:w-5" + aria-hidden="true" + /> + </> + ); if (!href) { - return (/* duplicated button JSX */); + return ( + <button className={baseClassName} {...props}> + {content} + </button> + ); } else { - return (/* duplicated anchor JSX */); + return ( + <a href={href} className={baseClassName} {...props}> + {content} + </a> + ); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/club/src/app/_components/links/button.tsx` around lines 14 - 59, The button and anchor branches duplicate the same className and inner content; refactor by extracting the shared class string (used on both the <button> and <a>), the shared children (Icon, innerText, MenuHorizontalSVG), and rendering a single element determined by href (e.g., const Tag = href ? 'a' : 'button') so you only set href when present and spread {...props} on Tag; update usages of Icon and MenuHorizontalSVG and keep aria-hidden props intact while removing the duplicated JSX blocks.apps/blade/src/app/_components/admin/club/data/member-data/ShirtSizePie.tsx (1)
154-155: Refactor Pie components to use theshapecallback instead ofactiveIndexfor Recharts v3 compatibility.Recharts v3 removed the
activeIndexprop entirely (not just from types). While the current code works at runtime with@ts-expect-error, this suppression violates TypeScript guidelines and masks deprecated behavior.The proper migration uses Recharts'
shapecallback with React state—supported in v3.7.0:Example refactor pattern
// Before: activeIndex with `@ts-expect-error` const [activeSize, setActiveSize] = useState(shirtSizeData[0]?.name ?? null); const activeIndex = useMemo( () => shirtSizeData.findIndex((item) => item.name === activeSize), [activeSize, shirtSizeData], ); <Pie data={shirtSizeData} // `@ts-expect-error` -- recharts v3 removed activeIndex from Pie TS types activeIndex={activeIndex} activeShape={...} /> // After: shape callback (type-safe) <Pie data={shirtSizeData} shape={(props) => { const isActive = shirtSizeData[props.index]?.name === activeSize; return ( <Sector {...props} outerRadius={isActive ? (props.outerRadius ?? 0) + 10 : props.outerRadius} /> ); }} onClick={(_, index) => setActiveSize(shirtSizeData[index]?.name ?? null)} />This pattern appears in 8 Pie components across the codebase. Consolidate the refactor into a shared hook or wrapper component to reduce duplication.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/admin/club/data/member-data/ShirtSizePie.tsx` around lines 154 - 155, Replace the deprecated activeIndex usage in ShirtSizePie (and the other 7 Pie components) with a type-safe shape callback and click handler: remove the `@ts-expect-error` and activeIndex prop, keep the existing active state (e.g., activeSize and setActiveSize), add onClick to setActiveSize using the clicked index, and implement shape={(props) => { const isActive = shirtSizeData[props.index]?.name === activeSize; return <Sector ... outerRadius={isActive ? (props.outerRadius ?? 0) + 10 : props.outerRadius} /> }} (or equivalent JSX) to render the active slice; consolidate duplication by extracting this pattern into a shared hook/wrapper (e.g., useActivePie or PieWithActiveShape) so all Pie components reuse the same shape callback, onClick logic, and active-state comparison.apps/blade/src/app/_components/admin/club/data/member-data/YearOfStudyPie.tsx (1)
216-217: Migrate fromactiveIndexto modern Recharts v3shapeprop pattern.The
@ts-expect-errorsuppresses a real type error—activeIndexwas removed in Recharts v3.0. However, since the repo uses v3.7.0 (well beyond v3.5.0+), the proper solution is now available: use theshapeprop with theisActiveflag instead.The
shapeprop in modern v3 provides equivalent per-slice customization without needing the escape hatch. Migrate theactiveIndexandactiveShapepattern to v3's native approach to eliminate the error and follow current library conventions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/admin/club/data/member-data/YearOfStudyPie.tsx` around lines 216 - 217, The Pie uses the removed activeIndex prop; remove the ts-expect-error and activeIndex usage and migrate to Recharts v3's shape prop pattern: create/rename the existing renderActiveShape (or custom slice renderer) into a component/function that accepts Recharts slice props and uses the provided isActive boolean to apply the active styling, then pass that component to the Pie via shape={CustomSlice} (or shape={(props) => <CustomSlice {...props} />}) and stop using activeShape/activeIndex; update any local state references (e.g., activeIndex) to instead determine isActive inside your CustomSlice by comparing index if you still need index-based logic, and remove the `@ts-expect-error` line so TypeScript validates the new shape usage in YearOfStudyPie.apps/club/src/app/_components/landing/about.tsx (1)
16-16: Same pattern:gsap.registerPluginat component level.Similar to
impact.tsx, consider moving the registration to module level for consistency and minor optimization. This is a recurring pattern across landing components.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/club/src/app/_components/landing/about.tsx` at line 16, gsap.registerPlugin(ScrollTrigger) is being called inside the component; move that plugin registration to module-level so it runs once when the module loads rather than on each render. Remove the gsap.registerPlugin(ScrollTrigger) call from inside the component (where you currently call it) and add a single call to gsap.registerPlugin(ScrollTrigger) at the top of the module (above the component definition/imports) so the plugin is registered once for the module.apps/club/src/app/_components/landing/impact.tsx (1)
12-13: Consider movinggsap.registerPluginoutside the component.Calling
gsap.registerPlugin(useGSAP, ScrollTrigger)inside the component body means it runs on every render. While GSAP handles duplicate registrations gracefully, it's more idiomatic to register plugins once at the module level:+gsap.registerPlugin(ScrollTrigger); + export default function Impact() { - gsap.registerPlugin(useGSAP, ScrollTrigger);This is a minor optimization—GSAP won't break, but module-level registration is cleaner.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/club/src/app/_components/landing/impact.tsx` around lines 12 - 13, Move the gsap.registerPlugin call out of the Impact React component and into module scope so it runs once at import time instead of on every render; locate the Impact function and remove the line calling gsap.registerPlugin(useGSAP, ScrollTrigger) from inside it, then add a single module-level registration statement that calls gsap.registerPlugin(useGSAP, ScrollTrigger) near the top of the file (before the Impact export) so the plugin registration happens once.apps/club/src/app/_components/landing/discover.tsx (1)
106-108: UpdateCoolButton2to use Tailwind v4 gradient syntax for consistency.The component currently uses
bg-gradient-to-r from-purple-800 to-purple-900(Tailwind v3 syntax), but the rest of the codebase has already migrated tobg-linear-to-r(v4 syntax). Update the class to match:Suggested change
- className={`group relative z-10 overflow-hidden rounded-full bg-gradient-to-r from-purple-800 to-purple-900 px-6 py-3 font-semibold text-white ...`} + className={`group relative z-10 overflow-hidden rounded-full bg-linear-to-r from-purple-800 to-purple-900 px-6 py-3 font-semibold text-white ...`}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/club/src/app/_components/landing/discover.tsx` around lines 106 - 108, Update the Tailwind gradient syntax used on the CoolButton2 instance: locate the CoolButton2 component usage (prop className on CoolButton2) and replace the old Tailwind v3 gradient class (bg-gradient-to-r from-purple-800 to-purple-900) with the v4 equivalent (bg-linear-to-r while keeping the same from- and to- color classes) so the className uses bg-linear-to-r along with from-purple-800 and to-purple-900 for consistency with the codebase.apps/tk/package.json (1)
25-25: Verify cron typings after thenode-cronv4 upgrade.
node-cronwas bumped on Line 25, but@types/node-cronis still v3 on Line 35. That can leave stale typings against v4 APIs; consider removing@types/node-cron(if v4 ships its own types) or aligning versions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/tk/package.json` at line 25, package.json lists "node-cron": "^4.2.1" but still has "@types/node-cron" at v3; verify and fix the mismatch by checking whether node-cron v4 includes its own TypeScript declarations—if it does, remove the "@types/node-cron" dependency; otherwise update or align "@types/node-cron" to a version compatible with node-cron v4; update package.json accordingly and run a type check to ensure no stale typings remain (references: dependency names "node-cron" and "@types/node-cron").packages/api/src/routers/dues-payment.ts (1)
80-81: Reuse the shared Stripe client instead of re-instantiating it.Line 80 shadows the imported
stripeclient and creates a new instance per request. Reusing the shared client from Line 13 keeps config centralized.Proposed fix
- const stripe = new Stripe(env.STRIPE_SECRET_KEY); - const session = await stripe.checkout.sessions.retrieve(input); + const session = await stripe.checkout.sessions.retrieve(input);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/api/src/routers/dues-payment.ts` around lines 80 - 81, Remove the per-request Stripe instantiation that shadows the module-scoped client: delete the line "const stripe = new Stripe(env.STRIPE_SECRET_KEY);" and call the existing imported/shared stripe client (the module-level "stripe" variable referenced earlier) when invoking stripe.checkout.sessions.retrieve(input), ensuring you don't re-declare or shadow the "stripe" identifier in the dues-payment handler.packages/ui/src/chart.tsx (1)
108-133: Avoid hand-maintaining Recharts payload prop shapes.The custom tooltip/legend payload interfaces can drift from upstream Recharts types over time. Prefer deriving these from
RechartsPrimitive.Tooltip/RechartsPrimitive.Legendprop types to keep wrappers upgrade-safe.Also applies to: 284-294
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/ui/src/chart.tsx` around lines 108 - 133, Replace the hand-maintained tooltip/legend prop shapes with types derived from Recharts' official prop types: import the appropriate Recharts primitive prop types (e.g., TooltipProps and LegendProps or RechartsPrimitive.Tooltip / RechartsPrimitive.Legend) and use those (or their payload/member types like TooltipProps['payload'] and LegendProps['payload']) instead of the inline React.ComponentProps<"div"> & { ... } definition found in the custom tooltip/legend prop typings (the inline union around active, payload, label, formatter, color, hideLabel, hideIndicator, indicator, nameKey, labelKey) and make the same replacement at the other occurrence around lines 284-294 so your wrappers derive their payload shapes from upstream Recharts types and stay upgrade-safe.apps/2025/package.json (1)
18-34: Dependency upgrades look well-coordinated.The major version bumps (Next.js 16, React 19, tRPC 11.10, TanStack Query 5.90) align with the PR objectives. One minor note:
Line 32:
superjsonis pinned exactly (2.2.6) while other dependencies use caret ranges (^). Consider using^2.2.6for consistency unless there's a specific reason for the exact pin.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/2025/package.json` around lines 18 - 34, Update the exact-pinned dependency for superjson to use a caret range for consistency: change the "superjson" entry in package.json (the dependency key "superjson" currently set to "2.2.6") to "^2.2.6" so it matches the versioning style used by the other dependencies.pnpm-workspace.yaml (1)
17-22: Catalog namereact18is now misleading.The
react18catalog now contains React 19 versions. Consider renaming toreact19or justreactto avoid confusion for future maintainers.♻️ Suggested rename
catalogs: - react18: + react19: react: 19.2.4 react-dom: 19.2.4 "@types/react": ^19.2.14 "@types/react-dom": ^19.2.3Note: This would require updating references across all
package.jsonfiles usingcatalog:react18.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@pnpm-workspace.yaml` around lines 17 - 22, Rename the misleading catalog key "react18" to a name that reflects the actual versions (e.g., "react19" or "react") in pnpm-workspace.yaml (the catalogs: react18 block) and update all package.json entries that reference "catalog:react18" to the new catalog name; ensure you search the repo for the exact string "catalog:react18" and replace it consistently so package resolution remains correct.apps/tk/src/commands/cat.ts (1)
53-59: Consider adding user feedback on error (consistent with other commands).Same recommendation as the other animal commands—reply to the interaction so users know something went wrong.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/tk/src/commands/cat.ts` around lines 53 - 59, The catch block in the command handler currently only logs errors to console; update the error handling in the catch (err: unknown) of the cat command so it also sends a user-facing reply via the interaction (e.g., interaction.reply or interaction.followUp) consistent with other animal commands, include a brief friendly message like "Something went wrong while fetching the cat" and, if err is an Error, optionally log err.message to the console as before while ensuring the interaction reply is always sent to the user to avoid leaving them without feedback.apps/tk/src/commands/duck.ts (1)
45-51: Consider replying to the user on failure.When an error occurs, the interaction is never replied to—Discord will show "Application did not respond" after 3 seconds. Per coding guidelines, Discord bot commands should provide user-friendly error messages.
🛠️ Suggested fix
} catch (err: unknown) { if (err instanceof Error) { console.error(err.message); } else { console.error("An unknown error occurred: ", err); } + void interaction.reply({ + content: "🦆 Quack! Something went wrong fetching a duck image.", + ephemeral: true, + }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/tk/src/commands/duck.ts` around lines 45 - 51, The catch block in the duck command currently only logs errors to console and never replies to Discord, causing "Application did not respond"; update the catch in the command handler (the catch for the duck command's execution) to send a user-friendly reply to the interaction (use interaction.reply or interaction.followUp) indicating an error occurred, and include a brief message like "Something went wrong — please try again later"; also ensure you handle already-replied interactions (check interaction.replied or use deferred reply) and still log the full error for diagnostics (include the caught err when calling your logger).apps/tk/src/commands/fox.ts (1)
42-48: Same error handling gap as other animal commands.Consider adding a user-facing reply on failure to avoid the "Application did not respond" timeout. This applies to all similar commands (cat, dog, capybara, duck, fox).
🛠️ Suggested fix
} catch (err: unknown) { if (err instanceof Error) { console.error(err.message); } else { console.error("An unknown error occurred: ", err); } + void interaction.reply({ + content: "🦊 Ring-ding-ding! Something went wrong.", + ephemeral: true, + }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/tk/src/commands/fox.ts` around lines 42 - 48, The catch block in the fox command's execute handler currently only logs errors (the console.error lines) and never sends a user-facing reply, causing "Application did not respond" timeouts; update the catch to both log the error and send a safe reply to the user (e.g., via interaction.reply or interaction.followUp depending on whether a reply was already sent) indicating the command failed, and ensure you guard with interaction.replied/interaction.deferred checks so you don't attempt a second reply; apply the same pattern used in the other animal command handlers (cat, dog, capybara, duck) to keep behavior consistent.
ℹ️ Review info
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yaml
📒 Files selected for processing (130)
.vscode/settings.jsonapps/2025/next.config.jsapps/2025/package.jsonapps/2025/src/app/_components/navbar/FloatingNav.tsxapps/2025/src/app/_components/partners/partnerPosters.tsxapps/2025/src/app/_components/sponsors/sponsorPosters.tsxapps/2025/src/app/layout.tsxapps/blade/next.config.jsapps/blade/package.jsonapps/blade/postcss.config.cjsapps/blade/src/app/_components/admin/charts/FirstTimeHackersPie.tsxapps/blade/src/app/_components/admin/charts/GenderPie.tsxapps/blade/src/app/_components/admin/charts/RaceOrEthnicityPie.tsxapps/blade/src/app/_components/admin/charts/SchoolYearPie.tsxapps/blade/src/app/_components/admin/club/data/event-data/AttendancesBarChart.tsxapps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsxapps/blade/src/app/_components/admin/club/data/event-data/TypePie.tsxapps/blade/src/app/_components/admin/club/data/member-data/GenderPie.tsxapps/blade/src/app/_components/admin/club/data/member-data/ShirtSizePie.tsxapps/blade/src/app/_components/admin/club/data/member-data/YearOfStudyPie.tsxapps/blade/src/app/_components/admin/club/members/update-member.tsxapps/blade/src/app/_components/admin/forms/responses/ResponseHorizontalBarChart.tsxapps/blade/src/app/_components/admin/hackathon/data/LevelOfStudyPie.tsxapps/blade/src/app/_components/admin/hackathon/data/ShirtSizePie.tsxapps/blade/src/app/_components/dashboard/hacker/hacker-application-form.tsxapps/blade/src/app/_components/dashboard/member/member-application-form.tsxapps/blade/src/app/_components/forms/form-runner.tsxapps/blade/src/app/_components/judge/results-table.tsxapps/blade/src/app/_components/settings/hacker-profile-form.tsxapps/blade/src/app/_components/settings/member-profile-form.tsxapps/blade/src/app/admin/forms/[slug]/page.tsxapps/blade/src/app/admin/forms/[slug]/responses/page.tsxapps/blade/src/app/api/membership/route.tsapps/blade/src/app/forms/[formName]/[responseId]/page.tsxapps/blade/src/app/forms/[formName]/page.tsxapps/blade/src/app/globals.cssapps/blade/src/app/hacker/application/[hackathon-id]/page.tsxapps/blade/src/trpc/server.tsapps/blade/tailwind.config.tsapps/club/next.config.jsapps/club/package.jsonapps/club/postcss.config.cjsapps/club/src/app/_components/landing/about.tsxapps/club/src/app/_components/landing/calendar.tsxapps/club/src/app/_components/landing/discover.tsxapps/club/src/app/_components/landing/hero.tsxapps/club/src/app/_components/landing/impact-assets/wave-reveal.tsxapps/club/src/app/_components/landing/impact.tsxapps/club/src/app/_components/landing/sponsors-assets/sponsor-card.tsxapps/club/src/app/_components/landing/sponsors-assets/sponsor-marquee.tsxapps/club/src/app/_components/links/assets/abstract-shape-left-1.tsxapps/club/src/app/_components/links/assets/abstract-shape-left-2.tsxapps/club/src/app/_components/links/assets/abstract-shape-right-1.tsxapps/club/src/app/_components/links/assets/abstract-shape-right-2.tsxapps/club/src/app/_components/links/assets/binary-icon.tsxapps/club/src/app/_components/links/assets/blank-calendar.tsxapps/club/src/app/_components/links/assets/chat-bubble.tsxapps/club/src/app/_components/links/assets/facebook.tsxapps/club/src/app/_components/links/assets/instagram.tsxapps/club/src/app/_components/links/assets/kh-logo.tsxapps/club/src/app/_components/links/assets/knighthacks-text.tsxapps/club/src/app/_components/links/assets/laptop-charging.tsxapps/club/src/app/_components/links/assets/linkedin.tsxapps/club/src/app/_components/links/assets/menu-horizontal.tsxapps/club/src/app/_components/links/assets/outline.tsxapps/club/src/app/_components/links/assets/terminal-icon.tsxapps/club/src/app/_components/links/assets/tk-neon-sign.tsxapps/club/src/app/_components/links/assets/tk-neon.tsxapps/club/src/app/_components/links/assets/twitter.tsxapps/club/src/app/_components/links/assets/youtube.tsxapps/club/src/app/_components/links/button.tsxapps/club/src/app/_components/links/links-header.tsxapps/club/src/app/globals.cssapps/club/tailwind.config.tsapps/cron/package.jsonapps/gemiknights/next.config.jsapps/gemiknights/package.jsonapps/gemiknights/postcss.config.cjsapps/gemiknights/src/app/_components/graphics/gemini.tsxapps/gemiknights/src/app/_components/graphics/mlhW.tsxapps/gemiknights/src/app/_components/navbar/Navbar.tsxapps/gemiknights/src/app/_components/ui/background-gradient-animation.tsxapps/gemiknights/src/app/globals.cssapps/gemiknights/tailwind.config.tsapps/guild/next.config.jsapps/guild/package.jsonapps/guild/postcss.config.cjsapps/guild/src/app/_components/guild-member-display.tsxapps/guild/src/app/globals.cssapps/guild/src/app/page.tsxapps/guild/src/trpc/server.tsapps/guild/tailwind.config.tsapps/tk/package.jsonapps/tk/src/commands/capybara.tsapps/tk/src/commands/cat.tsapps/tk/src/commands/dog.tsapps/tk/src/commands/duck.tsapps/tk/src/commands/fox.tspackage.jsonpackages/api/package.jsonpackages/api/src/routers/dues-payment.tspackages/api/src/routers/email.tspackages/api/src/routers/forms.tspackages/api/src/routers/judge.tspackages/api/src/utils.tspackages/auth/package.jsonpackages/auth/src/config.tspackages/auth/src/index.rsc.tspackages/consts/package.jsonpackages/db/package.jsonpackages/email/package.jsonpackages/ui/package.jsonpackages/ui/src/calendar.tsxpackages/ui/src/chart.tsxpackages/ui/src/dialog.tsxpackages/ui/src/dropdown-menu.tsxpackages/ui/src/form.tsxpackages/ui/src/navigation-menu.tsxpackages/ui/src/popover.tsxpackages/ui/src/select.tsxpackages/ui/src/sheet.tsxpackages/ui/src/tooltip.tsxpackages/validators/package.jsonpnpm-workspace.yamltooling/eslint/package.jsontooling/prettier/package.jsontooling/tailwind/base.tstooling/tailwind/package.jsontooling/tailwind/web.tsturbo.json
💤 Files with no reviewable changes (9)
- apps/blade/src/app/_components/admin/club/data/event-data/AttendancesBarChart.tsx
- tooling/tailwind/web.ts
- apps/2025/src/app/layout.tsx
- apps/2025/next.config.js
- apps/gemiknights/next.config.js
- apps/guild/next.config.js
- apps/club/next.config.js
- apps/blade/next.config.js
- apps/blade/src/app/_components/admin/forms/responses/ResponseHorizontalBarChart.tsx
| // @ts-expect-error -- recharts v3 removed activeIndex from Pie TS types | ||
| activeIndex={activeIndex} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, examine the file structure and locate GenderPie.tsx
fd "GenderPie.tsx" --type fRepository: KnightHacks/forge
Length of output: 191
🏁 Script executed:
# Read the relevant section of GenderPie.tsx
if [ -f "apps/blade/src/app/_components/admin/charts/GenderPie.tsx" ]; then
wc -l "apps/blade/src/app/_components/admin/charts/GenderPie.tsx"
fiRepository: KnightHacks/forge
Length of output: 122
🏁 Script executed:
# Get the context around lines 140-160 to see the full picture
if [ -f "apps/blade/src/app/_components/admin/charts/GenderPie.tsx" ]; then
cat -n "apps/blade/src/app/_components/admin/charts/GenderPie.tsx" | sed -n '140,160p'
fiRepository: KnightHacks/forge
Length of output: 985
🌐 Web query:
Recharts v3 activeIndex prop Pie chart removed replacement shape render
💡 Result:
In Recharts v3, <Pie activeIndex=... /> was removed. The “active” slice is now driven by <Tooltip /> interaction state, not by a Pie prop. [1][4]
Replacement for activeIndex
Use Tooltip props to control what’s active:
defaultIndex= initial active slicetrigger="hover" | "click"= how a slice becomes activeactive= keep tooltip (and thus “active” state) visible once activatedcontent={() => null}if you want highlighting without rendering a tooltip UI
[1][3]
<PieChart>
<Tooltip defaultIndex={0} trigger="hover" />
<Pie data={data} dataKey="value" />
</PieChart>Replacement for activeShape / “render active slice”
In v3, activeShape is deprecated. Use shape and branch on the injected isActive flag to render the active slice differently. [2][4]
import { Sector } from "recharts";
<Pie
data={data}
dataKey="value"
shape={(props) => (
<Sector
{...props}
outerRadius={props.isActive ? props.outerRadius + 8 : props.outerRadius}
/>
)}
/>If you need the slice index inside shape
Recharts added the missing Sector index to the Pie shape callback (so you can style a specific slice similarly to old activeIndex patterns). Make sure you’re on a version that includes that fix. [4]
Sources: [1] [2] [3] [4]
🏁 Script executed:
# Check how activeIndex and activeLevel are defined/used in GenderPie.tsx
cat -n "apps/blade/src/app/_components/admin/charts/GenderPie.tsx" | head -n 100Repository: KnightHacks/forge
Length of output: 3525
🏁 Script executed:
# Search for activeIndex and activeLevel definitions
grep -n "activeIndex\|activeLevel" "apps/blade/src/app/_components/admin/charts/GenderPie.tsx"Repository: KnightHacks/forge
Length of output: 610
Remove @ts-expect-error and migrate activeIndex/activeShape from Recharts v2 to v3 patterns.
Lines 149–150 violate the TypeScript guidelines by suppressing a real API change: Recharts v3 removed the activeIndex prop and deprecated activeShape. The current code is using v2 APIs that no longer exist.
Use the shape callback with conditional rendering based on the activeLevel state to highlight the selected gender slice:
Suggested fix
- // `@ts-expect-error` -- recharts v3 removed activeIndex from Pie TS types
- activeIndex={activeIndex}
- activeShape={({
- outerRadius = 0,
- ...props
- }: PieSectorDataItem) => (
- <g>
- <Sector {...props} outerRadius={outerRadius + 10} />
- <Sector
- {...props}
- outerRadius={outerRadius + 25}
- innerRadius={outerRadius + 12}
- />
- </g>
- )}
+ shape={(props: PieSectorDataItem) => {
+ const { outerRadius = 0 } = props;
+ const payload = props.payload as { name?: string } | undefined;
+ const isSelected = payload?.name === activeLevel;
+
+ if (!isSelected) return <Sector {...props} />;
+
+ return (
+ <g>
+ <Sector {...props} outerRadius={outerRadius + 10} />
+ <Sector
+ {...props}
+ outerRadius={outerRadius + 25}
+ innerRadius={outerRadius + 12}
+ />
+ </g>
+ );
+ }}In Recharts v3, shape receives each sector's data and allows you to conditionally render based on external state—here, comparing the sector's name to activeLevel to determine highlighting. This removes the type error, eliminates the deprecated API usage, and properly achieves the same visual effect.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/blade/src/app/_components/admin/charts/GenderPie.tsx` around lines 149 -
150, The component is using the removed Recharts v3 props activeIndex and
activeShape (suppressed with `@ts-expect-error`); remove the ts-expect-error and
the activeIndex prop on the Pie and instead supply a shape callback that
inspects each sector's data (e.g., sector.name) and compares it to the
component's activeLevel state to decide whether to render the highlighted
variant. Update the Pie in GenderPie to use shape={customShape} (or an inline
function) that receives the sector payload and returns the highlighted or normal
sector markup based on payload.name === activeLevel, and remove any references
to activeIndex/activeShape.
| // @ts-expect-error -- recharts v3 removed activeIndex from Pie TS types | ||
| activeIndex={activeIndex} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the file and check its structure
fd "HowFound.tsx" --type fRepository: KnightHacks/forge
Length of output: 131
🏁 Script executed:
# Check Recharts version in the codebase
find . -name "package.json" -type f -exec grep -l "recharts" {} \; | head -5Repository: KnightHacks/forge
Length of output: 113
🏁 Script executed:
# Once we locate the file, examine lines around 138-139
cat -n apps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsx | sed -n '130,150p'Repository: KnightHacks/forge
Length of output: 976
🌐 Web query:
Recharts v3 activeIndex prop removal Pie component migration guide
💡 Result:
What changed in Recharts v3
activeIndexwas removed (including forPie) because Recharts already manages “active” state internally via<Tooltip />, and having both led to unpredictable behavior. [1][2]
Recommended migration (Pie)
1) Delete activeIndex usage on <Pie />
Before (v2.x):
<Pie activeIndex={activeIndex} activeShape={renderActiveShape} ... />After (v3.x): remove activeIndex, and let <Tooltip /> drive what’s “active”. [1][2]
2) Use <Tooltip /> to control the active slice
Key replacements:
defaultIndex: initial highlighted slice (replacement for “initial activeIndex”) [1]active: keep tooltip/active state “locked on” (if you need persistent highlight) [1]content={() => null}and/orcursor={false}: keep highlight behavior while hiding tooltip UI [1]
Example:
<PieChart>
<Pie data={data} dataKey="value" /* no activeIndex */ />
<Tooltip defaultIndex={0} />
</PieChart>[1]
3) If you used activeShape to render an expanded slice
In v3, Pie is moving toward a unified shape API:
activeShape/inactiveShapeare deprecated in favor ofshape, using theisActiveflag passed to the callback. [3]- Recent v3 releases also added missing info (like sector index) to make “active-slice” customization easier again. [4]
Pattern:
<Pie
data={data}
dataKey="value"
shape={(props) => {
return props.isActive ? /* render active sector */ : /* normal sector */;
}}
/>[3][4]
Sources: Recharts “Active Index” guide [1], Recharts v3 migration guide [2], Pie API docs (deprecation note) [3], Recharts release notes (Pie shape/index fixes) [4].
🏁 Script executed:
# Read the full file to understand activeLevel and component structure
wc -l apps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsxRepository: KnightHacks/forge
Length of output: 135
🏁 Script executed:
# Get full file content with line numbers
cat -n apps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsxRepository: KnightHacks/forge
Length of output: 7556
🏁 Script executed:
# Check imports and understand PieSectorDataItem type
head -50 apps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsxRepository: KnightHacks/forge
Length of output: 1441
🏁 Script executed:
# Search for activeLevel usage in the file
grep -n "activeLevel" apps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsxRepository: KnightHacks/forge
Length of output: 378
🌐 Web query:
Recharts v3 Pie shape prop callback signature what properties are passed
💡 Result:
In Recharts v3, Pie’s shape prop can be a ReactNode or a function with this signature:
shape?: ReactNode | ((props: PieSectorShapeProps, index: number) => ReactElement)where:
type PieSectorShapeProps = PieSectorDataItem & { isActive: boolean; index: number };[1]
What’s in props (PieSectorShapeProps)
It’s essentially all the computed sector geometry + your presentation props + data info, including (non-exhaustive but the main ones):
- Geometry / positioning:
cx,cy,innerRadius,outerRadius,startAngle,endAngle,cornerRadius, plus other sector-geometry fields coming fromGeometrySector[1] - Data-related:
value,name?,percent?,payload?(original data item),dataKey?[1] - Tooltip-related:
tooltipPayload?,tooltipPosition[1] - Interaction state:
isActive: boolean[1] - Slice index:
index: number(also passed as the 2nd function argument) [1]
Note about index
Recharts added the sector index to the shape callback props in v3.5.1 (it was missing in v3.5.0). [2]
Sources: Pie type definitions [1], v3.5.1 release notes mentioning the added index [2].
🏁 Script executed:
# Search for other Pie chart usages in the codebase to see if there are examples of v3 patterns
rg "shape=\{" apps/blade --type tsx -A 5 -B 2Repository: KnightHacks/forge
Length of output: 88
🏁 Script executed:
# Search for other Pie chart usages to see if there are v3 pattern examples
rg "shape=" apps/blade -A 5 -B 2 --type typescriptRepository: KnightHacks/forge
Length of output: 95
🏁 Script executed:
# Check Recharts version in package.json to confirm v3
cat apps/blade/package.json | grep -A 2 -B 2 rechartsRepository: KnightHacks/forge
Length of output: 205
🏁 Script executed:
# Check if there are other similar patterns or if this is the only place using activeIndex
rg "activeIndex" apps/blade -B 5 -A 5Repository: KnightHacks/forge
Length of output: 39886
🏁 Script executed:
# Verify the full context of line 169 and ensure activeIndex is only used there
sed -n '160,180p' apps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsxRepository: KnightHacks/forge
Length of output: 893
Migrate to Recharts v3 shape prop instead of using activeIndex prop.
Line 138 suppresses a type error for a removed Recharts v3 prop. The activeIndex prop no longer exists in v3; use the shape callback instead. Keep the activeIndex computation since it's still used at line 169 for the label.
💡 Suggested fix
data={foundData}
dataKey="amount"
nameKey="name"
innerRadius={60}
strokeWidth={5}
- // `@ts-expect-error` -- recharts v3 removed activeIndex from Pie TS types
- activeIndex={activeIndex}
- activeShape={({
- outerRadius = 0,
- ...props
- }: PieSectorDataItem) => (
- <g>
- <Sector {...props} outerRadius={outerRadius + 10} />
- <Sector
- {...props}
- outerRadius={outerRadius + 25}
- innerRadius={outerRadius + 12}
- />
- </g>
- )}
+ shape={({ outerRadius = 0, index, ...props }: PieSectorDataItem & { index: number }) => {
+ const isSelected = index === activeIndex;
+ if (!isSelected) return <Sector {...props} />;
+ return (
+ <g>
+ <Sector {...props} outerRadius={outerRadius + 10} />
+ <Sector
+ {...props}
+ outerRadius={outerRadius + 25}
+ innerRadius={outerRadius + 12}
+ />
+ </g>
+ );
+ }}📝 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.
| // @ts-expect-error -- recharts v3 removed activeIndex from Pie TS types | |
| activeIndex={activeIndex} | |
| data={foundData} | |
| dataKey="amount" | |
| nameKey="name" | |
| innerRadius={60} | |
| strokeWidth={5} | |
| shape={({ outerRadius = 0, index, ...props }: PieSectorDataItem & { index: number }) => { | |
| const isSelected = index === activeIndex; | |
| if (!isSelected) return <Sector {...props} />; | |
| return ( | |
| <g> | |
| <Sector {...props} outerRadius={outerRadius + 10} /> | |
| <Sector | |
| {...props} | |
| outerRadius={outerRadius + 25} | |
| innerRadius={outerRadius + 12} | |
| /> | |
| </g> | |
| ); | |
| }} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsx`
around lines 138 - 139, The Pie component is still passing the removed Recharts
v3 prop activeIndex (suppressed by ts-expect-error); remove that prop and
instead provide a shape={(sliceProps) => ...} callback that uses the existing
activeIndex computation to render the highlighted/active slice (call your
existing renderActiveShape or implement the highlight logic inside the shape
callback). Keep the activeIndex variable because it’s still used for the label
rendering, ensure the shape callback receives the correct slice props type
(adjust typings if needed), and delete the ts-expect-error line so the code
compiles cleanly with Recharts v3.
| lastName: member.lastName || "", | ||
| email: member.email || "", | ||
| points: (member.points ?? 0).toString(), | ||
| points: member.points ?? 0, |
There was a problem hiding this comment.
Type mismatch: numeric default vs. string schema input.
The schema on line 75 expects z.string() input, but this default provides a number (member.points ?? 0). While HTML inputs coerce values to strings at runtime, this creates a TypeScript inconsistency that contributes to the @ts-expect-error above.
If you adopt the z.coerce.number() refactor suggested above, this line becomes type-correct as-is. Otherwise, for consistency with the current string-based schema:
- points: member.points ?? 0,
+ points: String(member.points ?? 0),📝 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.
| points: member.points ?? 0, | |
| points: String(member.points ?? 0), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/blade/src/app/_components/admin/club/members/update-member.tsx` at line
88, The default value for points (points: member.points ?? 0) is a number but
the form schema uses z.string(), causing a TS mismatch; either change the schema
to accept a numeric input using z.coerce.number() (so points can remain numeric)
or keep the string schema and convert the default to a string (points:
String(member.points ?? 0)); update the matching form field and remove the
now-unnecessary `@ts-expect-error` accordingly (refer to the points property and
the z.string() schema in this component).
| // @ts-expect-error -- recharts v3 removed activeIndex from Pie TS types | ||
| activeIndex={activeIndex} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check if the file exists and read the relevant section
fd "LevelOfStudyPie.tsx" --type fRepository: KnightHacks/forge
Length of output: 132
🏁 Script executed:
# Also search for similar patterns in the codebase
rg "@ts-expect-error|@ts-ignore" --type tsx --type ts apps/blade/ -A 2 -B 2 | head -100Repository: KnightHacks/forge
Length of output: 88
🏁 Script executed:
# Look for any package.json to confirm Recharts version
fd "package.json" -x cat {} \; | grep -A 5 -B 5 "recharts" || trueRepository: KnightHacks/forge
Length of output: 657
🏁 Script executed:
# Read the LevelOfStudyPie.tsx file to see the actual code
cat -n apps/blade/src/app/_components/admin/hackathon/data/LevelOfStudyPie.tsx | head -210 | tail -50Repository: KnightHacks/forge
Length of output: 2074
🏁 Script executed:
# Also check the full context around lines 192-193
cat -n apps/blade/src/app/_components/admin/hackathon/data/LevelOfStudyPie.tsxRepository: KnightHacks/forge
Length of output: 9416
🌐 Web query:
Recharts v3 Pie component shape callback props signature activeIndex removed
💡 Result:
-
activeIndexwas removed in Recharts v3.0 (for Pie/Bar/Scatter/etc.). The Recharts docs recommend controlling “what’s active” via<Tooltip />interaction (e.g.defaultIndex,active, etc.) instead. [1], [2] -
Pie “active slice” customization in v3 moved away from
activeShape/inactiveShape(now deprecated) toward theshapeprop, where you checkprops.isActiveinside the callback. Thisshapeapproach forPiewas introduced in v3.5.0. [3], [4] -
Pieshapecallback signature (v3.7.x types):
shape?: ReactNode | ((props: PieSectorShapeProps, index: number) => ReactElement)
wherePieSectorShapePropsincludes the sector geometry/data plus:isActive: booleanindex: number[4]
-
About the “index missing” / “activeIndex removed” confusion:
WhenPie.shapeshipped in v3.5.0, the release notes note they “forgot to include the current Sector index”; it was added in v3.5.1 (“add missingSectorindex toPieshapeprop”), enabling per-slice customization similar to what people usedactiveIndexfor in v2.x. [3]
Sources:
[1] https://recharts.github.io/en-US/guide/activeIndex/
[2] https://github.com/recharts/recharts/wiki/3.0-migration-guide
[3] https://github.com/recharts/recharts/releases (v3.5.0 / v3.5.1 notes)
[4] https://www.jsdocs.io/package/recharts (v3.7.0 type aliases: PieShape, PieSectorShapeProps)
🏁 Script executed:
# Check if there are similar `@ts-expect-error` patterns in other pie/chart components
rg "@ts-expect-error" apps/blade --type ts --type tsx -B 2 -A 2Repository: KnightHacks/forge
Length of output: 88
🏁 Script executed:
# Check for any other uses of activeIndex in recharts components
rg "activeIndex" apps/blade --type ts --type tsx -B 2 -A 2Repository: KnightHacks/forge
Length of output: 88
🏁 Script executed:
# Search for `@ts-expect-error` in blade directory with simpler approach
rg "@ts-expect-error" apps/blade -B 2 -A 2Repository: KnightHacks/forge
Length of output: 7960
🏁 Script executed:
# Search for activeIndex usage in blade directory
rg "activeIndex" apps/blade -B 2 -A 2Repository: KnightHacks/forge
Length of output: 19250
Refactor pie charts off deprecated activeIndex API; affects 11 components.
Recharts v3 removed activeIndex from the Pie component types. This pattern is repeated across 11 pie chart files in the codebase and violates the TS escape-hatch guideline.
Migrate to the shape prop with isActive (available in Recharts v3.5+, current version 3.7.0). The suggested approach is sound, but the Label component also needs refactoring since it currently accesses levelData[activeIndex]—track the active item reference instead:
💡 Suggested approach
Replace activeIndex and activeShape with a shape renderer:
- // `@ts-expect-error` -- recharts v3 removed activeIndex from Pie TS types
- activeIndex={activeIndex}
- activeShape={({
- outerRadius = 0,
- ...props
- }: PieSectorDataItem) => (
- <g>
- <Sector {...props} outerRadius={outerRadius + 10} />
- <Sector
- {...props}
- outerRadius={outerRadius + 25}
- innerRadius={outerRadius + 12}
- />
- </g>
- )}
+ shape={(props: PieSectorDataItem) => {
+ const { outerRadius = 0, isActive } = props;
+ if (!isActive) return <Sector {...props} />;
+ return (
+ <g>
+ <Sector {...props} outerRadius={outerRadius + 10} />
+ <Sector
+ {...props}
+ outerRadius={outerRadius + 25}
+ innerRadius={outerRadius + 12}
+ />
+ </g>
+ );
+ }}Then update the Label renderer to reference activeLevel directly instead of indexing with activeIndex:
- {levelData[activeIndex]?.amount.toLocaleString()}
+ {levelData.find(item => item.name === activeLevel)?.amount.toLocaleString()}Apply this pattern across all 11 pie chart files.
🤖 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/data/LevelOfStudyPie.tsx`
around lines 192 - 193, Replace the deprecated Pie prop usage by removing the //
`@ts-expect-error` activeIndex={activeIndex} pattern and implement a custom shape
renderer for the Pie component that uses the renderer's isActive flag to set an
activeLevel reference; inside the shape renderer detect isActive and assign the
corresponding datum to activeLevel (instead of relying on activeIndex) and pass
that activeLevel into the Label renderer, then refactor the Label component
(currently accessing levelData[activeIndex]) to consume activeLevel directly;
apply the same replacement (use Pie's shape+isActive and Label activeLevel)
across all 11 pie chart components, keeping function/class names like Pie,
activeShape, Label, levelData, and activeIndex as the guide for locations to
change.
| // @ts-expect-error -- recharts v3 removed activeIndex from Pie TS types | ||
| activeIndex={activeIndex} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the file at the specified lines
cat -n apps/blade/src/app/_components/admin/hackathon/data/ShirtSizePie.tsx | sed -n '145,165p'Repository: KnightHacks/forge
Length of output: 988
🌐 Web query:
Recharts v3 Pie component activeIndex prop removal API changes
💡 Result:
In Recharts v3.0, the activeIndex prop was removed (including from <Pie />) because “active index” is already managed internally by <Tooltip />, and having both led to unpredictable behavior. Use Tooltip-driven interaction instead. [1], [2]
What to use instead of activeIndex (v3)
Use <Tooltip /> props to control what’s “active” / highlighted:
defaultIndex: set the initially-highlighted sliceactive: keep tooltip/active state “stuck”content={() => null}: keep interaction but hide the tooltip UI- (also relevant depending on chart)
trigger,cursor[1]
Pie “active slice” rendering changed in v3.5.x
If you were using activeIndex mainly to render a custom active slice:
- v3.5.0 added
Pieshape(callback) and deprecatedactiveShape/inactiveShape; use the callback’sisActiveto render an “active” sector. [3] - v3.5.1 added the missing sector
indexto theshapecallback, explicitly noting it enables per-slice customization similar toactiveIndexin 2.x. [4]
Sources: ActiveIndex guide [1], v3 migration guide [2], v3.5.0 release notes (shape + deprecations) [3], v3.5.1 release notes (index in shape callback) [4].
🏁 Script executed:
# Check Recharts version in the project
find . -name "package.json" -o -name "package-lock.json" | xargs grep -l "recharts" 2>/dev/null | head -3Repository: KnightHacks/forge
Length of output: 113
🏁 Script executed:
# Check Recharts version in the project
cat apps/blade/package.json | grep -A 2 -B 2 "recharts"Repository: KnightHacks/forge
Length of output: 205
🏁 Script executed:
# Also check if there are other uses of activeIndex or `@ts-expect-error` in Pie/chart components
rg "activeIndex|@ts-expect-error.*Pie" apps/blade/src/app/_components/admin/hackathon/data/ -A 2 -B 2Repository: KnightHacks/forge
Length of output: 3591
🏁 Script executed:
# Check the full component to understand activeSize variable scope
cat -n apps/blade/src/app/_components/admin/hackathon/data/ShirtSizePie.tsx | head -50Repository: KnightHacks/forge
Length of output: 1703
🏁 Script executed:
# Check the rest of ShirtSizePie.tsx to see how activeIndex is used elsewhere
cat -n apps/blade/src/app/_components/admin/hackathon/data/ShirtSizePie.tsx | sed -n '50,100p'Repository: KnightHacks/forge
Length of output: 1914
🏁 Script executed:
# Check if LevelOfStudyPie has the same structure
cat -n apps/blade/src/app/_components/admin/hackathon/data/LevelOfStudyPie.tsx | head -70Repository: KnightHacks/forge
Length of output: 2517
🏁 Script executed:
# Let's see how activeIndex is used in the label rendering
cat -n apps/blade/src/app/_components/admin/hackathon/data/ShirtSizePie.tsx | sed -n '100,180p'Repository: KnightHacks/forge
Length of output: 3313
🏁 Script executed:
# Let's see the rest of the Label rendering
cat -n apps/blade/src/app/_components/admin/hackathon/data/ShirtSizePie.tsx | sed -n '170,210p'Repository: KnightHacks/forge
Length of output: 1820
Remove the TypeScript suppression by refactoring to use Recharts v3.7.0's shape callback instead of the removed activeIndex prop.
Line 154 violates the no-escape-hatches guideline. Recharts v3 removed activeIndex from the Pie API, so using @ts-expect-error masks a real migration issue. Instead, keep activeIndex as a computed variable (needed for the Label's data access) but render the active sector visually using the shape callback with the isActive parameter (available since v3.5.0+).
Suggested refactor
innerRadius={60}
strokeWidth={5}
- // `@ts-expect-error` -- recharts v3 removed activeIndex from Pie TS types
- activeIndex={activeIndex}
activeShape={({
outerRadius = 0,
...props
- }: PieSectorDataItem) => (
+ }: PieSectorDataItem & { isActive?: boolean }) => (
<g>
- <Sector {...props} outerRadius={outerRadius + 10} />
- <Sector
- {...props}
- outerRadius={outerRadius + 25}
- innerRadius={outerRadius + 12}
- />
+ {props.isActive && (
+ <>
+ <Sector {...props} outerRadius={outerRadius + 10} />
+ <Sector
+ {...props}
+ outerRadius={outerRadius + 25}
+ innerRadius={outerRadius + 12}
+ />
+ </>
+ )}
</g>
)}Keep the activeIndex variable as-is for the Label component (line 185)—it only needs the computed value for data access, not to pass to the Pie component.
🤖 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/data/ShirtSizePie.tsx` around
lines 154 - 155, The Pie usage in ShirtSizePie.tsx is silenced with a
ts-expect-error because Recharts v3 removed the activeIndex prop; remove the
ts-expect-error and the activeIndex prop on the <Pie> and instead pass a custom
shape function (shape={renderSector} or inline) that uses the isActive parameter
to draw the highlighted sector, while keeping the computed activeIndex variable
unchanged for the Label component’s data access; update the render function name
(e.g., renderActiveShape/renderSector) and ensure it accepts (props, isActive)
or the v3 signature so the active visual state is driven by isActive rather than
the removed prop.
| { | ||
| <MenuHorizontalSVG | ||
| className="md:right-15 absolute right-6 h-4 w-4 fill-current md:h-5 md:w-5 [&_*]:transition-colors [&_*]:duration-100 group-hover:[&_*]:fill-[#0F172A]" | ||
| className="md:right-15 **:transition-colors **:duration-100 group-hover:**:fill-[#0F172A] absolute right-6 h-4 w-4 fill-current md:h-5 md:w-5" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd "button.tsx" | head -20Repository: KnightHacks/forge
Length of output: 1503
🏁 Script executed:
# Search for MenuHorizontalSVG definition across the repo
rg "MenuHorizontalSVG" -A 5 -B 2Repository: KnightHacks/forge
Length of output: 2679
🏁 Script executed:
cat apps/club/src/app/_components/links/assets/menu-horizontal.tsxRepository: KnightHacks/forge
Length of output: 1437
🏁 Script executed:
cat -n apps/club/src/app/_components/links/button.tsxRepository: KnightHacks/forge
Length of output: 2850
Update SVG styling to target stroke instead of fill.
The MenuHorizontalSVG paths are rendered with stroke attributes, not fill. The current group-hover:**:fill-[#0F172A] and fill-current utilities won't affect them. Change these to target stroke so the hover effect works as intended.
Proposed fix
- className="md:right-15 **:transition-colors **:duration-100 group-hover:**:fill-[`#0F172A`] absolute right-6 h-4 w-4 fill-current md:h-5 md:w-5"
+ className="md:right-15 **:transition-colors **:duration-100 **:stroke-[`#D7B4FE`] group-hover:**:stroke-[`#0F172A`] absolute right-6 h-4 w-4 md:h-5 md:w-5"Apply to both lines 30 and 53.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/club/src/app/_components/links/button.tsx` at line 30, The hover color
utilities are targeting fill but the MenuHorizontalSVG uses stroke attributes;
update the className on the button/icon instances (the class containing
"group-hover:**:fill-[`#0F172A`]" and "fill-current") to use stroke utilities
instead—replace "group-hover:**:fill-[`#0F172A`]" with
"group-hover:**:stroke-[`#0F172A`]" and "fill-current" with "stroke-current";
apply the same replacements for both occurrences in button.tsx (the lines around
the MenuHorizontalSVG render).
| @import "tailwindcss"; | ||
| @source "../../packages/ui/src"; | ||
| @config "../../tailwind.config.ts"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find Stylelint configuration files
find . -type f \( -name ".stylelintrc*" -o -name "stylelint.config.*" -o -name ".stylelintignore" \) 2>/dev/null | head -20
# Also check package.json for stylelint config
find . -name "package.json" -type f -exec grep -l "stylelint" {} \; 2>/dev/null | head -5Repository: KnightHacks/forge
Length of output: 80
🏁 Script executed:
#!/bin/bash
# Search for at-rule-no-unknown or ignore patterns in common config locations
rg -n "at-rule-no-unknown|ignoreAtRules|allowUnknown" --type json --type js --type yaml 2>/dev/null | head -30Repository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Check the globals.css file to confirm the content
cat -n apps/club/src/app/globals.css 2>/dev/null || echo "File not found, searching for it..."
fd -H "globals.css" .Repository: KnightHacks/forge
Length of output: 5145
🏁 Script executed:
cat -n ./.stylelintrc.jsonRepository: KnightHacks/forge
Length of output: 1277
🏁 Script executed:
# Check if there are any other stylelint configurations in the monorepo
fd -H "\.stylelintrc" . 2>/dev/nullRepository: KnightHacks/forge
Length of output: 80
Update Stylelint config to allow Tailwind v4 at-rules.
The .stylelintrc.json extends stylelint-config-standard-scss, which has scss/at-rule-no-unknown enabled by default. Since @import "tailwindcss", @source, @config, @custom-variant, and @utility are Tailwind v4 directives (not standard SCSS), Stylelint will flag them as errors without explicit configuration.
Add this to .stylelintrc.json under "rules":
Stylelint config fix
"scss/at-rule-no-unknown": [true, {
"ignoreAtRules": ["tailwind", "source", "config", "custom-variant", "utility"]
}]This allows Stylelint to recognize Tailwind v4's custom at-rules and prevents CI build failures.
🧰 Tools
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/club/src/app/globals.css` around lines 3 - 5, Update the Stylelint rules
to allow Tailwind v4 at-rules: in your .stylelintrc.json add/modify the
"scss/at-rule-no-unknown" rule to keep it enabled but ignore the
Tailwind-specific directives used in globals.css (referencing `@import`
"tailwindcss", `@source`, `@config`, `@custom-variant`, `@utility`). Specifically, set
"scss/at-rule-no-unknown" to an array form with true and an ignoreAtRules list
containing "tailwind", "source", "config", "custom-variant", and "utility" so
Stylelint no longer flags those at-rules during CI.
| "node-cron": "^3.0.3", | ||
| "postgres": "^3.4.3", | ||
| "resend": "^3.2.0", | ||
| "node-cron": "^4.2.1", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
node-cron v4 TypeScript type definitions built-in
💡 Result:
node-cron v4 ships with built-in TypeScript type declarations, so you do not need @types/node-cron when using v4+. [1], [2]
If you previously had @types/node-cron installed (typically for v3 and earlier), remove it to avoid API/type mismatches with v4’s rewritten/changed TypeScript-based API. [2], [1]
🌐 Web query:
node-cron v4.2.1 types package.json
💡 Result:
For node-cron@4.2.1, TypeScript types are built in (no separate @types/node-cron package needed). The published package.json declares the .d.ts files via conditional exports:
main:./dist/cjs/node-cron.jsmodule:./dist/esm/node-cron.jstype:commonjsexports["."]:require.types:./dist/cjs/node-cron.d.tsimport.types:./dist/esm/node-cron.d.ts[1]
npm also indicates the package “contains built-in TypeScript declarations.” [2]
Sources: [1] [2]
Remove @types/node-cron — it's obsolete with node-cron v4.
node-cron v4.2.1 ships with built-in TypeScript type declarations (via conditional exports in its package.json). Keeping @types/node-cron v3 alongside it causes type conflicts and stale type signatures. Remove line 36 from package.json:
Expected change
- "@types/node-cron": "^3.0.11",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/cron/package.json` at line 26, Remove the obsolete `@types/node-cron`
dependency from apps/cron package.json: locate the dependencies/devDependencies
entry for "@types/node-cron" (v3) and delete that line so only "node-cron":
"^4.2.1" remains, then run npm/yarn install to refresh lockfile and ensure
TypeScript uses the built-in types from node-cron v4.
| const img = await JIMP.read(data.data.url); | ||
| const { width, height } = img; | ||
| const color = img.getPixelColor( | ||
| Math.floor(width / 2), | ||
| Math.floor(height / 2), | ||
| ); |
There was a problem hiding this comment.
Validate API-provided image URLs and always reply on failure
Line 29 trusts data.data.url directly. If the API returns an invalid/unsupported URL, image parsing fails and users get no friendly response. Validate the URL (http/https only) before JIMP.read, and return an ephemeral fallback message in the error path.
🔧 Suggested fix
export async function execute(interaction: CommandInteraction) {
try {
const res = await fetch(url);
const data = (await res.json()) as CapybaraProps;
+ const imageUrl = data?.data?.url;
+ if (!imageUrl) {
+ throw new Error("Capybara API returned no image URL");
+ }
+
+ const parsedUrl = new URL(imageUrl);
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
+ throw new Error("Unsupported image URL protocol");
+ }
// get the average color of the img, make it the embed color
- const img = await JIMP.read(data.data.url);
+ const img = await JIMP.read(parsedUrl.toString());
const { width, height } = img;
const color = img.getPixelColor(
Math.floor(width / 2),
Math.floor(height / 2),
);
@@
const embed = new EmbedBuilder()
- .setImage(data.data.url)
+ .setImage(parsedUrl.toString())
.setColor(`#${hexString}`);
void interaction.reply({ embeds: [embed] });
// catch any errors
} catch (err: unknown) {
+ const message = "I couldn't fetch a capybara right now. Please try again in a moment.";
+ if (interaction.replied || interaction.deferred) {
+ await interaction.followUp({ content: message, ephemeral: true });
+ } else {
+ await interaction.reply({ content: message, ephemeral: true });
+ }
+
if (err instanceof Error) {
console.error(err.message);
} else {
console.error("An unknown error occurred: ", err);
}
}
}As per coding guidelines, "apps/tk/: Command handlers validate input" and "apps/tk/: Graceful error handling with user-friendly messages".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/tk/src/commands/capybara.ts` around lines 29 - 34, Validate the
API-provided image URL before calling JIMP.read and return an ephemeral fallback
message on failure: check that data.data.url is a non-empty string with an http
or https scheme, then wrap the JIMP.read(...) call (which produces img and later
color via img.getPixelColor(...)) in a try/catch; on any validation failure or
exception, send the user a friendly ephemeral error reply and abort further
image processing, otherwise continue using img and color as before.
| const img = await JIMP.read(data.message); | ||
| const { width, height } = img; | ||
| const color = img.getPixelColor( | ||
| Math.floor(width / 2), | ||
| Math.floor(height / 2), | ||
| ); |
There was a problem hiding this comment.
Validate the API image URL before passing it to JIMP.read.
Line 24 trusts data.message directly from an external API. Add validation (shape + URL/protocol) before image decoding to prevent malformed payload crashes.
Proposed fix
- const img = await JIMP.read(data.message);
+ if (typeof data.message !== "string") {
+ throw new Error("Dog API returned an invalid image URL");
+ }
+ const imageUrl = new URL(data.message);
+ if (imageUrl.protocol !== "https:") {
+ throw new Error("Dog API returned a non-HTTPS image URL");
+ }
+ const img = await JIMP.read(imageUrl.toString());As per coding guidelines apps/tk/**: Command handlers validate input.
📝 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.
| const img = await JIMP.read(data.message); | |
| const { width, height } = img; | |
| const color = img.getPixelColor( | |
| Math.floor(width / 2), | |
| Math.floor(height / 2), | |
| ); | |
| if (typeof data.message !== "string") { | |
| throw new Error("Dog API returned an invalid image URL"); | |
| } | |
| const imageUrl = new URL(data.message); | |
| if (imageUrl.protocol !== "https:") { | |
| throw new Error("Dog API returned a non-HTTPS image URL"); | |
| } | |
| const img = await JIMP.read(imageUrl.toString()); | |
| const { width, height } = img; | |
| const color = img.getPixelColor( | |
| Math.floor(width / 2), | |
| Math.floor(height / 2), | |
| ); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/tk/src/commands/dog.ts` around lines 24 - 29, The handler currently
passes data.message directly into JIMP.read (see JIMP.read and subsequent use of
img.getPixelColor, width, height); add input validation before decoding: verify
data.message exists and is a string, parse it as a URL and ensure the protocol
is http or https, and optionally check it matches the expected host/shape or a
simple regex for image URLs (jpg/png/webp); if validation fails, return or throw
a clear error instead of calling JIMP.read so malformed payloads cannot crash
the image processing code.
There was a problem hiding this comment.
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/dashboard/member-dashboard/payment/payment-button.tsx (1)
36-41:⚠️ Potential issue | 🟠 MajorAdd error handling and loading state to the payment flow.
Per the coding guidelines, Stripe payment flows require thorough review. Currently:
- No error handling — if
createCheckoutUrl()fails, the user gets no feedback- No loading state — the button remains clickable during the async call, risking duplicate requests
🛡️ Suggested fix: add loading state and error handling
+ const [isLoading, setIsLoading] = useState(false); + const handleCheckout = async () => { - const { checkoutUrl } = await createCheckoutUrl(); - if (checkoutUrl) { - router.push(checkoutUrl); + setIsLoading(true); + try { + const { checkoutUrl } = await createCheckoutUrl(); + if (checkoutUrl) { + router.push(checkoutUrl); + } + } catch (error) { + // Consider using a toast notification or error state + console.error("Failed to create checkout session:", error); + } finally { + setIsLoading(false); } }; return ( <Button onClick={handleCheckout} - disabled={disableButton} + disabled={disableButton || isLoading} className="w-full" > - Pay Dues + {isLoading ? "Loading..." : "Pay Dues"} </Button>Why this matters: Users should receive clear feedback when something goes wrong with payment initialization, and preventing double-clicks avoids potential duplicate Stripe sessions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/dashboard/member-dashboard/payment/payment-button.tsx` around lines 36 - 41, The handleCheckout function lacks error handling and a loading state; update the component to add a local loading boolean (e.g., useState called isLoading) and guard the handler so if isLoading is true it returns early, set isLoading=true before calling createCheckoutUrl(), wrap the await createCheckoutUrl() call in try/catch to handle and surface errors (e.g., set an error state or call the existing toast/error UI) and in finally set isLoading=false; also pass isLoading to the payment button to disable it and show a spinner/disabled UI while the async request is in-flight so duplicate requests and silent failures are prevented (refer to handleCheckout and createCheckoutUrl).
🧹 Nitpick comments (7)
apps/blade/src/app/_components/dashboard/member-dashboard/payment/payment-button.tsx (1)
18-34: Consider computing disabled state inline instead of using useEffect.The lint suppression is valid here since this is an initialization pattern, but you can eliminate both the
useEffectand the suppression by computing the value directly. This is more idiomatic React—derive state from props when possible rather than syncing via effects.♻️ Suggested refactor: compute inline
- const [disableButton, setDisableButton] = useState<boolean>(false); - - useEffect(() => { - const month = new Date().getMonth(); - - if ( - (member.school !== "University of Central Florida" && - member.school !== "Valencia College" && - member.school !== "Seminole State College of Florida" && - member.school !== "Rollins College" && - member.school !== "DeVry University Orlando" && - member.school !== "Johnson University Florida" && - member.school !== "Full Sail University") || - (month > 3 && month < 7) // disable during summer months - ) { - // eslint-disable-next-line react-hooks/set-state-in-effect - setDisableButton(true); - } - }, [member.school]); + const ELIGIBLE_SCHOOLS = [ + "University of Central Florida", + "Valencia College", + "Seminole State College of Florida", + "Rollins College", + "DeVry University Orlando", + "Johnson University Florida", + "Full Sail University", + ] as const; + + const month = new Date().getMonth(); + const isIneligibleSchool = !ELIGIBLE_SCHOOLS.includes(member.school as typeof ELIGIBLE_SCHOOLS[number]); + const isSummerMonth = month > 3 && month < 7; + const disableButton = isIneligibleSchool || isSummerMonth;Why this is better: Derived state avoids the extra render cycle that
useEffect+setStatecauses, and the extractedELIGIBLE_SCHOOLSarray makes future maintenance easier.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/dashboard/member-dashboard/payment/payment-button.tsx` around lines 18 - 34, Replace the effect-based state sync in useEffect that calls setDisableButton with a derived boolean computed inline: create a constant array ELIGIBLE_SCHOOLS = [...] listing the allowed school names, compute const isDisabled = !ELIGIBLE_SCHOOLS.includes(member.school) || (new Date().getMonth() > 3 && new Date().getMonth() < 7) and use that value directly where you currently read disableButton (or initialize state once from that expression if you truly need state); remove the useEffect, its eslint-disable comment, and the setDisableButton call so the disabled state is derived from member.school and current month instead of being synced.apps/blade/src/app/_components/forms/form-responder-client.tsx (1)
148-154: Good cleanup, butanycast remains — consider typing the mutation input.Removing the unnecessary
no-unsafe-assignmentsuppression is a nice refinement. However, per coding guidelines,anyusage should still be flagged. The cast on line 152 bypasses type safety for the mutation payload.A cleaner approach is to type the mutation's expected input so
FormResponsePayloadaligns directly, eliminating the need for the cast entirely.♻️ Suggested improvement
If the tRPC mutation expects a specific shape, you can use a type assertion to that shape (or
unknownwith a guard) instead ofany:const onSubmit = (payload: FormResponsePayload) => { submitResponse.mutate({ form: formId, - // eslint-disable-next-line `@typescript-eslint/no-explicit-any` - responseData: payload as any, + responseData: payload as Record<string, unknown>, }); };Alternatively, if the mutation's input type can be updated in your tRPC router to accept
FormResponsePayloaddirectly, you can remove the cast altogether.As per coding guidelines: "No TypeScript escape hatches: Flag usage of 'any' type... suggest 'unknown' with type guards, optional chaining (?.), or proper typing instead."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/forms/form-responder-client.tsx` around lines 148 - 154, The submit handler uses an any cast for mutation input — update the mutation typing so FormResponsePayload is accepted without casting: adjust the type of submitResponse (the tRPC mutation hook) to accept the correct input shape (or export the mutation input type from the router and use it as the generic for submitResponse) and then call submitResponse.mutate({ form: formId, responseData: payload }) with payload typed as FormResponsePayload; alternatively, if you cannot change the router, cast payload to the router's exact mutation input type (or to unknown and validate via a guard) instead of using any so type safety is preserved in onSubmit and the submitResponse.mutate call.apps/blade/src/app/_components/admin/forms/editor/client.tsx (2)
479-484: UsesaveFormRef.current()instead ofhandleSaveFormin setTimeout.The codebase establishes a ref pattern (lines 272–275) specifically to always invoke the latest
handleSaveFormwith fresh state. However, thissetTimeoutcallshandleSaveFormdirectly, which captures state at the timereorderItemsis defined—potentially stale after rapid reordering.Recommended fix
setQuestions(newQuestions); setInstructions(newInstructions); - setTimeout(() => handleSaveForm(), 100); + setTimeout(() => saveFormRef.current(), 100); }, - [questions, instructions, handleSaveForm], + [questions, instructions], );The same issue exists at line 513 in
handleDragEnd:setQuestions(newQuestions); setInstructions(newInstructions); - setTimeout(() => handleSaveForm(), 100); + setTimeout(() => saveFormRef.current(), 100);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/admin/forms/editor/client.tsx` around lines 479 - 484, The setTimeout call inside reorderItems uses the captured handleSaveForm (which can be stale); change it to call the ref wrapper so the latest function is invoked: replace the setTimeout(() => handleSaveForm(), 100) call with setTimeout(() => saveFormRef.current(), 100) and do the same replacement for the analogous setTimeout in handleDragEnd so both use saveFormRef.current() after setQuestions/setInstructions to ensure fresh state is saved; references: reorderItems, handleDragEnd, handleSaveForm, saveFormRef.current, setQuestions, setInstructions.
277-288: Selective ESLint suppression may be incomplete.The suppression on line 281 silences
react-hooks/set-state-in-effectforsetFormTitle, but the same effect block immediately callssetFormDescriptionandsetIsLoadingwithout suppression. If the linter flags one, it likely flags all—meaning additional warnings may still appear.Consider one of these approaches:
- Suppress at the effect level if the pattern is intentional for this test mock path
- Extract to a separate function called by the effect to centralize state hydration
- Use the query's
selectorplaceholderDataoptions to avoid the effect entirelyExample: Suppressing at the block level
if (slug === "test-form") { - // eslint-disable-next-line react-hooks/set-state-in-effect + /* eslint-disable react-hooks/set-state-in-effect */ setFormTitle("Test Form (Mock)"); setFormDescription( "This is a mock form description for testing UI components.", ); setIsLoading(false); + /* eslint-enable react-hooks/set-state-in-effect */ return; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/admin/forms/editor/client.tsx` around lines 277 - 288, The eslint suppression currently only targets setFormTitle inside the useEffect mock branch but setFormDescription and setIsLoading are also called and may still trigger the react-hooks/set-state-in-effect rule; fix by either moving the eslint-disable comment to cover the entire effect (place /* eslint-disable react-hooks/set-state-in-effect */ immediately before the useEffect and re-enable after), or extract the mock hydration into a separate function (e.g., hydrateMockForm called from useEffect) and keep the single-line suppression on that function, or eliminate the effect entirely by providing the mock via the query's select/placeholderData—locate useEffect and the calls to setFormTitle, setFormDescription, and setIsLoading to apply one of these changes.apps/blade/src/app/_components/dashboard/hackathon-dashboard/hackathon-data.tsx (1)
61-65: Remove derived state and compute status values directlyLines 37–38 declare state for
hackerStatusandhackerStatusColor, which are purely derived fromhacker?.status. TheuseEffectat lines 61–65 updates these values wheneverhackerchanges, requiring a lint suppression to hide the warning.Since
getStatusName()andgetStatusColor()are pure functions with no side effects, compute these values directly during render instead. This eliminates the extra render cycle, removes the need for state and the lint suppression, and keeps related logic together.Refactor
-import { useEffect, useState } from "react"; +// Remove unused React hooks @@ - const [hackerStatus, setHackerStatus] = useState<string | null>(""); - const [hackerStatusColor, setHackerStatusColor] = useState<string>(""); @@ - useEffect(() => { - // eslint-disable-next-line react-hooks/set-state-in-effect - setHackerStatus(getStatusName(hacker?.status)); - setHackerStatusColor(getStatusColor(hacker?.status)); - }, [hacker]); + const hackerStatus = getStatusName(hacker?.status); + const hackerStatusColor = getStatusColor(hacker?.status);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/dashboard/hackathon-dashboard/hackathon-data.tsx` around lines 61 - 65, The state variables hackerStatus and hackerStatusColor and the useEffect updating them are unnecessary because they are derived from hacker?.status; remove the state declarations and the useEffect (and the eslint-disable comment) and instead compute values during render by calling getStatusName(hacker?.status) and getStatusColor(hacker?.status) where needed (e.g., replace uses of hackerStatus and hackerStatusColor with these direct expressions), keeping hacker (the prop/variable) as the source of truth.apps/blade/src/app/_components/dashboard/hacker-dashboard/hacker-data.tsx (1)
139-142: Remove the lint suppression by adopting optimistic updates via tRPC cache manipulationThe
react-hooks/set-state-in-effectlint warning masks a real pattern:hackerStatusandhackerStatusColorare set from two sources (query data via effect, optimistic updates in mutation handlers). Rather than removing the state entirely, useupdateQueryDatain the mutation'sonSuccessto update the cache optimistically, then remove the manualsetStatecalls.Suggested refactor
const confirmHacker = api.hackerMutation.confirmHacker.useMutation({ async onSuccess() { + // Optimistically update the cache instead of manual setState + await utils.hackerQuery.getHacker.setData({}, (prev) => + prev ? { ...prev, status: "confirmed" } : prev + ); setIsConfirmOpen(true); - setHackerStatus("Confirmed"); - setHackerStatusColor(getStatusColor("confirmed")); await utils.hackerQuery.getHacker.invalidate(); }, }); const withdrawHacker = api.hackerMutation.withdrawHacker.useMutation({ async onSuccess() { + // Optimistically update the cache instead of manual setState + await utils.hackerQuery.getHacker.setData({}, (prev) => + prev ? { ...prev, status: "withdrawn" } : prev + ); toast.success("You have withdrawn from the hackathon!"); - setHackerStatus("Withdrawn"); - setHackerStatusColor(getStatusColor("withdrawn")); await utils.hackerQuery.getHacker.invalidate(); }, });The
useEffectstays (removing the lint suppression is safe once state is no longer manually mutated in handlers), and the UI updates optimistically via the cache before re-fetching server truth.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/blade/src/app/_components/dashboard/hacker-dashboard/hacker-data.tsx` around lines 139 - 142, The effect currently calls setHackerStatus(getStatusName(hacker?.status)) and setHackerStatusColor(getStatusColor(hacker?.status)) while mutation handlers also mutate state, triggering the lint suppression; instead remove direct state updates from mutation handlers and perform optimistic updates via tRPC cache by calling trpcClient.<mutationKey>.useMutation(...).onSuccess => utils.hacker.getById.updateQueryData(id, draft => { draft.status = newStatus; }) (or equivalent) so that the useEffect can safely derive hackerStatus/hackerStatusColor from hacker using getStatusName/getStatusColor; remove the eslint-disable comment and any setHackerStatus/setHackerStatusColor calls in mutation handlers, ensuring only the cache is updated optimistically in the mutation onSuccess to reflect UI immediately.apps/blade/src/app/_components/admin/hackathon/hackers/food-restrictions.tsx (1)
20-26: Remove derived state and the eslint suppression.
foodStringis computed fromhacker.foodAllergiesand stored in state, requiring auseEffecthook and a lint suppression. Instead, compute it directly in the render phase.Proposed refactor
-import { useEffect, useState } from "react"; +import { useState } from "react"; @@ const [isOpen, setIsOpen] = useState<boolean>(false); - const [foodString, setFoodString] = useState(""); - - useEffect(() => { - // eslint-disable-next-line react-hooks/set-state-in-effect - setFoodString(hacker.foodAllergies?.replace(/,/g, ", ") || ""); - }, [hacker.foodAllergies]); + const foodString = + hacker.foodAllergies + ?.split(",") + .map((item) => item.trim()) + .filter(Boolean) + .join(", ") ?? "";This removes an extra render cycle and eliminates the need for the lint suppression.
🤖 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/hackers/food-restrictions.tsx` around lines 20 - 26, Remove the derived state and eslint suppression by deleting the useState for foodString, the setFoodString call, and the useEffect (and its eslint-disable comment); instead compute the value inline in render like: const foodString = hacker.foodAllergies?.replace(/,/g, ", ") || ""; also remove any usages of setFoodString and keep isOpen state as-is.
🤖 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/src/app/_components/forms/form-view-edit-client.tsx`:
- Around line 95-96: Remove the eslint-disable and the unsafe cast on the
responseData assignment (responseData: payload as any); instead construct
payload to match the mutation input type and assert it with TypeScript's
satisfies operator (e.g., const payload = { ... } satisfies UpdateClientInput or
the specific mutation input type used by your update client mutation), so
responseData: payload is fully typed; update the variable declarations/fields
that feed into payload to conform to that input type so the compiler catches
mismatches.
In `@apps/blade/src/app/_components/settings/hacker-profile-form.tsx`:
- Around line 248-252: Add an else branch to the conditional handling
hacker.foodAllergies so that when hacker.foodAllergies is empty or falsy you
clear both the state and ref: call setSelectedAllergies([]) and set
allergiesRef.current = [] in the else. Update the block around
setSelectedAllergies and allergiesRef.current (the code using
hacker.foodAllergies) to handle the empty case so stale badges are removed.
In `@apps/blade/src/app/_components/settings/member-profile-form.tsx`:
- Line 117: Remove the "@ts-expect-error" and fix the schema so its input types
match the form values: drop the .transform() calls on the schema fields for
gradYear and dob (keep them as string inputs), update the useForm generic/types
to use the schema's input type, change the initial value for gradYear to a
string (use String(initTermYear.year) instead of Number(initTermYear.year)) and
ensure member?.dob is passed as a string to the form, and keep the explicit
conversion logic in the submission handler (handleSubmit/onSubmit) where you
already defensively convert to numbers/dates.
In `@apps/blade/src/app/_components/shared/ToggleEditInput.tsx`:
- Around line 33-38: This component (ToggleEditInput) uses React hooks (useState
and useEffect) so add the Next.js App Router client directive 'use client' as
the first line of the file and then import useEffect/useState normally; remove
the incorrect eslint-disable comment referencing react-hooks/set-state-in-effect
(it's not a valid rule) and keep the effect that closes the toggle when submit
changes (setToggle(false) inside the useEffect that depends on submit) so
behavior is preserved.
---
Outside diff comments:
In
`@apps/blade/src/app/_components/dashboard/member-dashboard/payment/payment-button.tsx`:
- Around line 36-41: The handleCheckout function lacks error handling and a
loading state; update the component to add a local loading boolean (e.g.,
useState called isLoading) and guard the handler so if isLoading is true it
returns early, set isLoading=true before calling createCheckoutUrl(), wrap the
await createCheckoutUrl() call in try/catch to handle and surface errors (e.g.,
set an error state or call the existing toast/error UI) and in finally set
isLoading=false; also pass isLoading to the payment button to disable it and
show a spinner/disabled UI while the async request is in-flight so duplicate
requests and silent failures are prevented (refer to handleCheckout and
createCheckoutUrl).
---
Nitpick comments:
In `@apps/blade/src/app/_components/admin/forms/editor/client.tsx`:
- Around line 479-484: The setTimeout call inside reorderItems uses the captured
handleSaveForm (which can be stale); change it to call the ref wrapper so the
latest function is invoked: replace the setTimeout(() => handleSaveForm(), 100)
call with setTimeout(() => saveFormRef.current(), 100) and do the same
replacement for the analogous setTimeout in handleDragEnd so both use
saveFormRef.current() after setQuestions/setInstructions to ensure fresh state
is saved; references: reorderItems, handleDragEnd, handleSaveForm,
saveFormRef.current, setQuestions, setInstructions.
- Around line 277-288: The eslint suppression currently only targets
setFormTitle inside the useEffect mock branch but setFormDescription and
setIsLoading are also called and may still trigger the
react-hooks/set-state-in-effect rule; fix by either moving the eslint-disable
comment to cover the entire effect (place /* eslint-disable
react-hooks/set-state-in-effect */ immediately before the useEffect and
re-enable after), or extract the mock hydration into a separate function (e.g.,
hydrateMockForm called from useEffect) and keep the single-line suppression on
that function, or eliminate the effect entirely by providing the mock via the
query's select/placeholderData—locate useEffect and the calls to setFormTitle,
setFormDescription, and setIsLoading to apply one of these changes.
In
`@apps/blade/src/app/_components/admin/hackathon/hackers/food-restrictions.tsx`:
- Around line 20-26: Remove the derived state and eslint suppression by deleting
the useState for foodString, the setFoodString call, and the useEffect (and its
eslint-disable comment); instead compute the value inline in render like: const
foodString = hacker.foodAllergies?.replace(/,/g, ", ") || ""; also remove any
usages of setFoodString and keep isOpen state as-is.
In
`@apps/blade/src/app/_components/dashboard/hackathon-dashboard/hackathon-data.tsx`:
- Around line 61-65: The state variables hackerStatus and hackerStatusColor and
the useEffect updating them are unnecessary because they are derived from
hacker?.status; remove the state declarations and the useEffect (and the
eslint-disable comment) and instead compute values during render by calling
getStatusName(hacker?.status) and getStatusColor(hacker?.status) where needed
(e.g., replace uses of hackerStatus and hackerStatusColor with these direct
expressions), keeping hacker (the prop/variable) as the source of truth.
In `@apps/blade/src/app/_components/dashboard/hacker-dashboard/hacker-data.tsx`:
- Around line 139-142: The effect currently calls
setHackerStatus(getStatusName(hacker?.status)) and
setHackerStatusColor(getStatusColor(hacker?.status)) while mutation handlers
also mutate state, triggering the lint suppression; instead remove direct state
updates from mutation handlers and perform optimistic updates via tRPC cache by
calling trpcClient.<mutationKey>.useMutation(...).onSuccess =>
utils.hacker.getById.updateQueryData(id, draft => { draft.status = newStatus; })
(or equivalent) so that the useEffect can safely derive
hackerStatus/hackerStatusColor from hacker using getStatusName/getStatusColor;
remove the eslint-disable comment and any setHackerStatus/setHackerStatusColor
calls in mutation handlers, ensuring only the cache is updated optimistically in
the mutation onSuccess to reflect UI immediately.
In
`@apps/blade/src/app/_components/dashboard/member-dashboard/payment/payment-button.tsx`:
- Around line 18-34: Replace the effect-based state sync in useEffect that calls
setDisableButton with a derived boolean computed inline: create a constant array
ELIGIBLE_SCHOOLS = [...] listing the allowed school names, compute const
isDisabled = !ELIGIBLE_SCHOOLS.includes(member.school) || (new Date().getMonth()
> 3 && new Date().getMonth() < 7) and use that value directly where you
currently read disableButton (or initialize state once from that expression if
you truly need state); remove the useEffect, its eslint-disable comment, and the
setDisableButton call so the disabled state is derived from member.school and
current month instead of being synced.
In `@apps/blade/src/app/_components/forms/form-responder-client.tsx`:
- Around line 148-154: The submit handler uses an any cast for mutation input —
update the mutation typing so FormResponsePayload is accepted without casting:
adjust the type of submitResponse (the tRPC mutation hook) to accept the correct
input shape (or export the mutation input type from the router and use it as the
generic for submitResponse) and then call submitResponse.mutate({ form: formId,
responseData: payload }) with payload typed as FormResponsePayload;
alternatively, if you cannot change the router, cast payload to the router's
exact mutation input type (or to unknown and validate via a guard) instead of
using any so type safety is preserved in onSubmit and the submitResponse.mutate
call.
ℹ️ Review info
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (42)
apps/blade/src/app/_components/admin/charts/FirstTimeHackersPie.tsxapps/blade/src/app/_components/admin/charts/GenderPie.tsxapps/blade/src/app/_components/admin/charts/MajorBarChart.tsxapps/blade/src/app/_components/admin/charts/RaceOrEthnicityPie.tsxapps/blade/src/app/_components/admin/charts/SchoolBarChart.tsxapps/blade/src/app/_components/admin/charts/SchoolYearPie.tsxapps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsxapps/blade/src/app/_components/admin/club/data/event-data/TypePie.tsxapps/blade/src/app/_components/admin/club/data/member-data/GenderPie.tsxapps/blade/src/app/_components/admin/club/data/member-data/SchoolBarChart.tsxapps/blade/src/app/_components/admin/club/data/member-data/ShirtSizePie.tsxapps/blade/src/app/_components/admin/club/data/member-data/YearOfStudyPie.tsxapps/blade/src/app/_components/admin/club/members/update-member.tsxapps/blade/src/app/_components/admin/forms/editor/client.tsxapps/blade/src/app/_components/admin/forms/homepage.tsxapps/blade/src/app/_components/admin/forms/responses/FileUploadResponsesTable.tsxapps/blade/src/app/_components/admin/forms/responses/ResponseHorizontalBarChart.tsxapps/blade/src/app/_components/admin/forms/responses/ResponsesTable.tsxapps/blade/src/app/_components/admin/hackathon/check-in/manual-entry-form.tsxapps/blade/src/app/_components/admin/hackathon/data/HackathonDataContent.tsxapps/blade/src/app/_components/admin/hackathon/data/LevelOfStudyPie.tsxapps/blade/src/app/_components/admin/hackathon/data/ShirtSizePie.tsxapps/blade/src/app/_components/admin/hackathon/hackers/food-restrictions.tsxapps/blade/src/app/_components/admin/hackathon/hackers/hackers-table.tsxapps/blade/src/app/_components/admin/hackathon/hackers/update-hacker.tsxapps/blade/src/app/_components/admin/roles/roleedit.tsxapps/blade/src/app/_components/dashboard/hackathon-dashboard/hackathon-data.tsxapps/blade/src/app/_components/dashboard/hackathon-dashboard/point-leaderboard.tsxapps/blade/src/app/_components/dashboard/hackathon-dashboard/upcoming-events.tsxapps/blade/src/app/_components/dashboard/hacker-dashboard/hacker-data.tsxapps/blade/src/app/_components/dashboard/hacker/hacker-application-form.tsxapps/blade/src/app/_components/dashboard/member-dashboard/payment/payment-button.tsxapps/blade/src/app/_components/forms/_hooks/useSubmissionSuccess.tsapps/blade/src/app/_components/forms/form-responder-client.tsxapps/blade/src/app/_components/forms/form-view-edit-client.tsxapps/blade/src/app/_components/forms/shared/instruction-edit-card.tsxapps/blade/src/app/_components/forms/shared/question-edit-card.tsxapps/blade/src/app/_components/judge/projects-table.tsxapps/blade/src/app/_components/settings/hacker-profile-form.tsxapps/blade/src/app/_components/settings/member-profile-form.tsxapps/blade/src/app/_components/shared/ToggleEditInput.tsxapps/blade/src/app/api/membership/route.ts
✅ Files skipped from review due to trivial changes (5)
- apps/blade/src/app/_components/admin/roles/roleedit.tsx
- apps/blade/src/app/_components/dashboard/hackathon-dashboard/upcoming-events.tsx
- apps/blade/src/app/_components/admin/hackathon/check-in/manual-entry-form.tsx
- apps/blade/src/app/_components/dashboard/hackathon-dashboard/point-leaderboard.tsx
- apps/blade/src/app/_components/admin/hackathon/data/HackathonDataContent.tsx
🚧 Files skipped from review as they are similar to previous changes (11)
- apps/blade/src/app/_components/admin/charts/FirstTimeHackersPie.tsx
- apps/blade/src/app/_components/admin/club/data/member-data/ShirtSizePie.tsx
- apps/blade/src/app/_components/admin/club/data/event-data/HowFound.tsx
- apps/blade/src/app/_components/admin/club/data/event-data/TypePie.tsx
- apps/blade/src/app/_components/dashboard/hacker/hacker-application-form.tsx
- apps/blade/src/app/_components/admin/charts/SchoolYearPie.tsx
- apps/blade/src/app/_components/admin/club/members/update-member.tsx
- apps/blade/src/app/_components/admin/charts/GenderPie.tsx
- apps/blade/src/app/_components/admin/club/data/member-data/GenderPie.tsx
- apps/blade/src/app/_components/admin/hackathon/data/ShirtSizePie.tsx
- apps/blade/src/app/api/membership/route.ts
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| responseData: payload as any, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, examine the file at the flagged lines
cat -n apps/blade/src/app/_components/forms/form-view-edit-client.tsx | sed -n '85,105p'Repository: KnightHacks/forge
Length of output: 682
🏁 Script executed:
# Find the editResponse mutation import/definition
rg -n 'editResponse|api\.forms\.editResponse' apps/blade/src/app/_components/forms/form-view-edit-client.tsx -B2 -A2Repository: KnightHacks/forge
Length of output: 578
🏁 Script executed:
# Search for any TypeScript escape hatches in this file
rg -n '@ts-ignore|@ts-expect-error|as any|![\.\)]' apps/blade/src/app/_components/forms/form-view-edit-client.tsxRepository: KnightHacks/forge
Length of output: 99
🏁 Script executed:
# Locate the api/router files to understand the mutation type signature
fd -t f 'forms' --path '*/server/*' --path '*/api/*' | head -20Repository: KnightHacks/forge
Length of output: 294
🏁 Script executed:
# Find the forms router/trpc definition
find apps -name '*.ts' -type f | xargs rg 'editResponse' | grep -E '(router|procedure|createTRPCRouter)' -B2 -A2Repository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
# Search for api/forms files
fd -type f 'forms' apps/Repository: KnightHacks/forge
Length of output: 231
🏁 Script executed:
# Look for trpc router definitions
rg -n 'editResponse' --type ts --type tsx -l | head -10Repository: KnightHacks/forge
Length of output: 88
🏁 Script executed:
# Examine the api structure
find apps -type f -name '*.ts' | xargs grep -l 'createTRPCRouter\|router\(' | grep -i form | head -5Repository: KnightHacks/forge
Length of output: 84
🏁 Script executed:
# Find files containing editResponse
rg 'editResponse' -l apps/Repository: KnightHacks/forge
Length of output: 123
🏁 Script executed:
# Look for trpc router structure
find apps -type d -name 'api' -o -type d -name 'server'Repository: KnightHacks/forge
Length of output: 83
🏁 Script executed:
# Search for forms router/procedure
rg -n 'editResponse' apps/ -B3 -A3 | head -40Repository: KnightHacks/forge
Length of output: 2075
🏁 Script executed:
# Look in api directory
find apps/blade/src/app/api -type f -name '*.ts'Repository: KnightHacks/forge
Length of output: 237
🏁 Script executed:
# Search for where api is imported in the file
rg -n 'import.*api' apps/blade/src/app/_components/forms/form-view-edit-client.tsxRepository: KnightHacks/forge
Length of output: 98
🏁 Script executed:
# Look for forms procedure/router
rg -n 'forms.*editResponse|editResponse.*procedure' apps/Repository: KnightHacks/forge
Length of output: 186
🏁 Script executed:
# Find trpc directory
find apps -type d -name 'trpc'Repository: KnightHacks/forge
Length of output: 151
🏁 Script executed:
# Look for router files that might define forms
find apps -type f -name '*.ts' | xargs rg 'router.*forms|forms.*router' | head -10Repository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
# Search for procedure definitions with editResponse
rg -n 'procedure.*editResponse|editResponse.*mutation' apps/ -B2 -A5Repository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
# Look for all files in trpc directory
find apps -path '*/trpc/*' -type fRepository: KnightHacks/forge
Length of output: 329
🏁 Script executed:
# Look at the server.ts file for router structure
cat apps/blade/src/trpc/server.tsRepository: KnightHacks/forge
Length of output: 974
🏁 Script executed:
# Search for router directory with forms
find apps/blade -type d -name 'router' -o -type d -name 'routers'Repository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
# Search for files containing 'forms' as a router
rg -n 'forms:' apps/ -B2 -A5Repository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
# Find packages directory
find . -type d -name 'packages' -o -type d -name 'api' | head -10Repository: KnightHacks/forge
Length of output: 111
🏁 Script executed:
# Look for `@forge/api` in package.json or tsconfig
rg '@forge/api' --type json | head -5Repository: KnightHacks/forge
Length of output: 343
🏁 Script executed:
# Find where `@forge/api` points to
find . -name 'tsconfig.json' -o -name 'turbo.json' | head -5Repository: KnightHacks/forge
Length of output: 186
🏁 Script executed:
# Search for forms router definition across all ts files
rg 'forms.*:.*procedure|editResponse' . --type ts -l | head -10Repository: KnightHacks/forge
Length of output: 161
🏁 Script executed:
# Examine the forms router
cat packages/api/src/routers/forms.tsRepository: KnightHacks/forge
Length of output: 38154
Remove the as any escape hatch and use type-safe input construction.
Line 96 bypasses type safety with payload as any, and the eslint suppression at line 95 masks the actual typing issue. Use satisfies with the mutation input type to catch type mismatches at compile time instead of hiding them.
✅ Typed fix
const onSubmit = (payload: FormResponsePayload) => {
- editResponse.mutate({
- id: responseId,
- // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
- responseData: payload as any,
- });
+ const input = {
+ id: responseId,
+ responseData: payload,
+ } satisfies Parameters<typeof editResponse.mutate>[0];
+ editResponse.mutate(input);
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/blade/src/app/_components/forms/form-view-edit-client.tsx` around lines
95 - 96, Remove the eslint-disable and the unsafe cast on the responseData
assignment (responseData: payload as any); instead construct payload to match
the mutation input type and assert it with TypeScript's satisfies operator
(e.g., const payload = { ... } satisfies UpdateClientInput or the specific
mutation input type used by your update client mutation), so responseData:
payload is fully typed; update the variable declarations/fields that feed into
payload to conform to that input type so the compiler catches mismatches.
| if (hacker.foodAllergies) { | ||
| // eslint-disable-next-line react-hooks/set-state-in-effect | ||
| setSelectedAllergies(hacker.foodAllergies.split(",")); | ||
| allergiesRef.current = hacker.foodAllergies.split(","); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
wc -l apps/blade/src/app/_components/settings/hacker-profile-form.tsxRepository: KnightHacks/forge
Length of output: 128
🏁 Script executed:
sed -n '200,300p' apps/blade/src/app/_components/settings/hacker-profile-form.tsx | cat -nRepository: KnightHacks/forge
Length of output: 4117
🏁 Script executed:
# Get more context around the target lines and the useEffect hook
sed -n '220,280p' apps/blade/src/app/_components/settings/hacker-profile-form.tsx | cat -nRepository: KnightHacks/forge
Length of output: 2514
🏁 Script executed:
# Look for other setSelectedAllergies or allergiesRef usage
rg 'setSelectedAllergies|allergiesRef|foodAllergies' apps/blade/src/app/_components/settings/hacker-profile-form.tsx -nRepository: KnightHacks/forge
Length of output: 797
🏁 Script executed:
# Check the effect dependencies and component structure
sed -n '1,80p' apps/blade/src/app/_components/settings/hacker-profile-form.tsx | cat -nRepository: KnightHacks/forge
Length of output: 2924
Reset allergy state when backend foodAllergies is empty.
When hacker.foodAllergies transitions from a value to empty, the form field resets but selectedAllergies state and allergiesRef don't. This causes stale badges to remain rendered. Add an else block to reset both when the data is empty.
💡 Proposed fix
if (hacker.foodAllergies) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setSelectedAllergies(hacker.foodAllergies.split(","));
allergiesRef.current = hacker.foodAllergies.split(",");
+ } else {
+ // eslint-disable-next-line react-hooks/set-state-in-effect
+ setSelectedAllergies([]);
+ allergiesRef.current = [];
}📝 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.
| if (hacker.foodAllergies) { | |
| // eslint-disable-next-line react-hooks/set-state-in-effect | |
| setSelectedAllergies(hacker.foodAllergies.split(",")); | |
| allergiesRef.current = hacker.foodAllergies.split(","); | |
| } | |
| if (hacker.foodAllergies) { | |
| // eslint-disable-next-line react-hooks/set-state-in-effect | |
| setSelectedAllergies(hacker.foodAllergies.split(",")); | |
| allergiesRef.current = hacker.foodAllergies.split(","); | |
| } else { | |
| // eslint-disable-next-line react-hooks/set-state-in-effect | |
| setSelectedAllergies([]); | |
| allergiesRef.current = []; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/blade/src/app/_components/settings/hacker-profile-form.tsx` around lines
248 - 252, Add an else branch to the conditional handling hacker.foodAllergies
so that when hacker.foodAllergies is empty or falsy you clear both the state and
ref: call setSelectedAllergies([]) and set allergiesRef.current = [] in the
else. Update the block around setSelectedAllergies and allergiesRef.current (the
code using hacker.foodAllergies) to handle the empty case so stale badges are
removed.
| }; | ||
|
|
||
| const form = useForm({ | ||
| // @ts-expect-error -- schema uses .transform() so input≠output; ZodType<TIn,TIn> in useForm can't represent this |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/blade/src/app/_components/settings/member-profile-form.tsx | head -270 | tail -160Repository: KnightHacks/forge
Length of output: 6605
🏁 Script executed:
cat -n apps/blade/src/app/_components/settings/member-profile-form.tsx | sed -n '280,350p'Repository: KnightHacks/forge
Length of output: 2827
Remove the TypeScript suppression and align schema input types with form values.
The @ts-expect-error at line 117 hides a real type mismatch: the schema expects string inputs for gradYear and dob, but line 252 provides Number(initTermYear.year) and line 250 provides the raw member?.dob (likely already a string/date). This is a direct violation of the coding guideline flagging TypeScript escape hatches.
The fix is to remove .transform() from the schema (keeping inputs as-is) and handle conversions explicitly at submission time, where line 338 already does defensive conversion. This makes types transparent and prevents future mismatches.
Suggested changes
- // `@ts-expect-error` -- schema uses .transform() so input≠output; ZodType<TIn,TIn> in useForm can't represent this
const form = useForm({
schema: InsertMemberSchema.omit({
@@
- dob: z
- .string()
- .pipe(z.coerce.date())
- .refine(
- (date) => {
- const today = new Date();
- const age = calculateAge(date, today);
- return age >= 16;
- },
- {
- message:
- "You must be at least 16 years old to be a Knight Hacks member",
- },
- )
- .transform((date) => date.toISOString()),
+ dob: z.string().refine(
+ (value) => {
+ const date = new Date(value);
+ if (Number.isNaN(date.getTime())) return false;
+ return calculateAge(date, new Date()) >= 16;
+ },
+ {
+ message:
+ "You must be at least 16 years old to be a Knight Hacks member",
+ },
+ ),
@@
- gradYear: z
- .string()
- .regex(/^\d{4}$/, "Enter a 4‑digit year")
- .transform(Number),
+ gradYear: z.string().regex(/^\d{4}$/, "Enter a 4‑digit year"),
@@
- gradYear: Number(initTermYear.year),
+ gradYear: initTermYear.year,
@@
updateMember.mutate({
id: member.id,
...values,
+ dob: new Date(values.dob).toISOString(),
resumeUrl,
profilePictureUrl,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/blade/src/app/_components/settings/member-profile-form.tsx` at line 117,
Remove the "@ts-expect-error" and fix the schema so its input types match the
form values: drop the .transform() calls on the schema fields for gradYear and
dob (keep them as string inputs), update the useForm generic/types to use the
schema's input type, change the initial value for gradYear to a string (use
String(initTermYear.year) instead of Number(initTermYear.year)) and ensure
member?.dob is passed as a string to the form, and keep the explicit conversion
logic in the submission handler (handleSubmit/onSubmit) where you already
defensively convert to numbers/dates.
| useEffect(() => { | ||
| if (submit) { | ||
| // eslint-disable-next-line react-hooks/set-state-in-effect | ||
| setToggle(false); | ||
| } | ||
| }, [submit]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Read the file to see the full context and check for 'use client'
head -50 apps/blade/src/app/_components/shared/ToggleEditInput.tsxRepository: KnightHacks/forge
Length of output: 1116
🏁 Script executed:
# Search for the ESLint rule in config files and rule definitions
rg "set-state-in-effect" -g "*.{js,ts,json,yml,yaml}"Repository: KnightHacks/forge
Length of output: 194
🏁 Script executed:
# Check ESLint configuration files
fd -e js -e ts -e json -e yaml -e yml -g "*eslint*" -type f | head -20Repository: KnightHacks/forge
Length of output: 231
🏁 Script executed:
# Look at the ESLint config for any custom rules or plugins
find . -maxdepth 3 -type f \( -name "eslint.config.*" -o -name ".eslintrc*" \) | head -10Repository: KnightHacks/forge
Length of output: 370
🏁 Script executed:
# Read the full ToggleEditInput.tsx file
wc -l apps/blade/src/app/_components/shared/ToggleEditInput.tsxRepository: KnightHacks/forge
Length of output: 122
🏁 Script executed:
# Check the full file to see if 'use client' exists
cat apps/blade/src/app/_components/shared/ToggleEditInput.tsxRepository: KnightHacks/forge
Length of output: 3173
🏁 Script executed:
# Read the blade ESLint config to understand the rule
cat apps/blade/eslint.config.jsRepository: KnightHacks/forge
Length of output: 520
🏁 Script executed:
# Search for where this rule might be defined or come from
rg "react-hooks" apps/blade/eslint.config.js -A 5 -B 5Repository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
# Find the `@forge/eslint-config` package location
fd -type d -name "eslint-config" 2>/dev/null | head -5Repository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
# List files in the eslint-config package
ls -la packages/eslint-config/ 2>/dev/null || find . -path "*eslint-config*" -name "*.js" | head -10Repository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
# Check if the rule is from a known plugin
rg "set-state-in-effect" . --type jsRepository: KnightHacks/forge
Length of output: 43
🏁 Script executed:
# Search for plugin configurations
rg "react-hooks" packages/eslint-config/ --type js -A 3 -B 3 2>/dev/null || rg "eslint-plugin-react-hooks" . --type js --type jsonRepository: KnightHacks/forge
Length of output: 212
🏁 Script executed:
# Read the react.js config to see how the plugin is configured
cat ./tooling/eslint/react.jsRepository: KnightHacks/forge
Length of output: 569
🏁 Script executed:
# Check the eslint-plugin-react-hooks package.json to understand what it provides
cat ./tooling/eslint/package.json | jq '.dependencies, .devDependencies' 2>/dev/null || grep -A 5 -B 5 "react-hooks" ./tooling/eslint/package.jsonRepository: KnightHacks/forge
Length of output: 544
🏁 Script executed:
# Search online documentation - the standard rules for eslint-plugin-react-hooks are well-known
# Let me check if there are any custom rule definitions or if this might be a typo
rg "set-state" . --type js --type ts | head -20Repository: KnightHacks/forge
Length of output: 2628
Add 'use client' directive—this component uses React hooks in a Next.js App Router.
This file uses useState and useEffect, which only work in client components. Add 'use client' at the top:
'use client';
import { useEffect, useState } from "react";
// ... rest of importsAlso, the eslint-disable comment references react-hooks/set-state-in-effect, which is not a standard rule in eslint-plugin-react-hooks. Remove the suppression comment once the 'use client' directive is added—it's likely suppressing a non-existent rule.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/blade/src/app/_components/shared/ToggleEditInput.tsx` around lines 33 -
38, This component (ToggleEditInput) uses React hooks (useState and useEffect)
so add the Next.js App Router client directive 'use client' as the first line of
the file and then import useEffect/useState normally; remove the incorrect
eslint-disable comment referencing react-hooks/set-state-in-effect (it's not a
valid rule) and keep the effect that closes the toggle when submit changes
(setToggle(false) inside the useEffect that depends on submit) so behavior is
preserved.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
apps/club/src/app/_components/links/button.tsx (2)
33-33:⚠️ Potential issue | 🟡 MinorReplace
transition-colorwithtransition-colors.Line 33 and Line 56 use a non-standard Tailwind class, so the intended color transition won’t apply.
Proposed fix
- className="font-pragati transition-color bg-linear-to-br group relative inline-flex transform items-center gap-2 rounded-[200px] border border-[`#D8B4FE`] bg-transparent px-14 py-3 text-[12px] font-normal leading-normal tracking-[1px] text-white duration-100 hover:bg-[`#D8B4FE`] hover:text-[`#0F172A`] md:px-24 md:text-[13px] md:font-bold md:tracking-[.8px]" + className="font-pragati transition-colors bg-linear-to-br group relative inline-flex transform items-center gap-2 rounded-[200px] border border-[`#D8B4FE`] bg-transparent px-14 py-3 text-[12px] font-normal leading-normal tracking-[1px] text-white duration-100 hover:bg-[`#D8B4FE`] hover:text-[`#0F172A`] md:px-24 md:text-[13px] md:font-bold md:tracking-[.8px]"Tailwind CSS v4 docs: Is `transition-color` a valid utility class, and is `transition-colors` the correct utility for color property transitions?Also applies to: 56-56
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/club/src/app/_components/links/button.tsx` at line 33, Update the Tailwind transition utility in the button component: replace the nonstandard class "transition-color" with the correct "transition-colors" wherever it appears in the className strings in apps/club/src/app/_components/links/button.tsx (both occurrences around the inline className in the Button component) so the color transition utilities are applied correctly.
45-45:⚠️ Potential issue | 🟡 Minor
MenuHorizontalSVGhover styles should targetstroke, notfill.The referenced SVG paths are stroke-based, so
group-hover:**:fill[...]andfill-currentwon’t drive the intended color change.Proposed fix
- className="md:right-15 **:transition-colors **:duration-100 group-hover:**:fill-[`#0F172A`] absolute right-6 h-4 w-4 fill-current md:h-5 md:w-5" + className="md:right-15 **:transition-colors **:duration-100 **:stroke-[`#D7B4FE`] group-hover:**:stroke-[`#0F172A`] absolute right-6 h-4 w-4 md:h-5 md:w-5"#!/bin/bash # Verify class usage in the button component rg -n 'MenuHorizontalSVG|group-hover:\*\*:fill|fill-current|group-hover:\*\*:stroke|stroke-current' apps/club/src/app/_components/links/button.tsx # Verify stroke/fill attributes in the SVG asset rg -n 'stroke=|fill=' apps/club/src/app/_components/links/assets/menu-horizontal.tsxAlso applies to: 68-68
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/club/src/app/_components/links/button.tsx` at line 45, The hover color classes on MenuHorizontalSVG are using fill utilities but the SVG uses strokes; update the className on the MenuHorizontalSVG instances to use stroke utilities instead of fill (replace occurrences like "group-hover:**:fill-[`#0F172A`]" with "group-hover:**:stroke-[`#0F172A`]" and change "fill-current" to "stroke-current"), ensuring any size/position classes remain unchanged so hover color is driven by stroke rather than fill.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@apps/club/src/app/_components/links/button.tsx`:
- Line 33: Update the Tailwind transition utility in the button component:
replace the nonstandard class "transition-color" with the correct
"transition-colors" wherever it appears in the className strings in
apps/club/src/app/_components/links/button.tsx (both occurrences around the
inline className in the Button component) so the color transition utilities are
applied correctly.
- Line 45: The hover color classes on MenuHorizontalSVG are using fill utilities
but the SVG uses strokes; update the className on the MenuHorizontalSVG
instances to use stroke utilities instead of fill (replace occurrences like
"group-hover:**:fill-[`#0F172A`]" with "group-hover:**:stroke-[`#0F172A`]" and
change "fill-current" to "stroke-current"), ensuring any size/position classes
remain unchanged so hover color is driven by stroke rather than fill.
ℹ️ Review info
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
apps/2025/src/app/_components/navbar/Navbar.tsxapps/club/src/app/_components/landing/calendar.tsxapps/club/src/app/_components/landing/hero-assets/typing-text.tsxapps/club/src/app/_components/links/button.tsxpackages/api/src/routers/csv-importer.tspackages/api/src/routers/email.tspackages/api/src/routers/hackers/mutations.tspackages/api/src/utils.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/club/src/app/_components/landing/calendar.tsx
- packages/api/src/routers/email.ts
- packages/api/src/utils.ts
Why
All of our dependencies were really behind, so updating them was important for performance improvements, security patches, etc
What
Closes: #380
Bumped all outdated dependencies across the monorepo catalog and individual packages. Major version upgrades: Next.js 14 → 16, React 18 → 19, Tailwind CSS v3 → v4, ESLint 9 → 10, Zod 3 → 4, tRPC 11.0-rc → 11.10.0 (stable), @tanstack/react-query 5.59 → 5.90, react-day-picker 8 → 9, drizzle-zod 0.5.1 → 0.8.3, and rsuite 5 → 6.
Test Plan
Ran pnpm install successfully (exit 0)
Ran pnpm build successfully — all 13/13 tasks passed (exit 0)
Ran pnpm dev and verified all apps load and render correctly on their respective ports
Visually confirmed all pages are identical to pre-update appearance
Checklist
db:pushbefore mergingSummary by CodeRabbit
New Features
Improvements
Bug Fixes
Chores