Migrate to shadcn and implement username password login#1018
Migrate to shadcn and implement username password login#1018
Conversation
|
Important Review skippedToo many files! This PR contains 299 files, which is 149 over the limit of 150. ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (299)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (17)
frontend/src/common-components/WalletButton.tsx (1)
25-32:⚠️ Potential issue | 🟠 MajorAccessibility issue: interactive
imgis not keyboard-accessible.The
imgelement withonClickis not focusable and cannot be activated via keyboard. This blocks keyboard-only users from adding the pass to their wallet.Since clicking opens a URL, an anchor element is more semantic and provides keyboard accessibility by default.
♿ Proposed fix using an anchor element
return ( - <img - className="cursor-pointer max-w-full w-48 mb-3" - src={type === 'apple' ? AppleWalletImage : GoogleWalletImage} - alt={type === 'apple' ? 'Apple Wallet' : 'Google Wallet'} - onClick={() => { - window.open(url) - }} - /> + <a href={url} target="_blank" rel="noopener noreferrer"> + <img + className="max-w-full w-48 mb-3" + src={type === 'apple' ? AppleWalletImage : GoogleWalletImage} + alt={type === 'apple' ? 'Apple Wallet' : 'Google Wallet'} + /> + </a> )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/common-components/WalletButton.tsx` around lines 25 - 32, Replace the interactive img+onClick in the WalletButton component with a semantic anchor so keyboard users can activate it: wrap or replace the <img> with an <a href={url} target="_blank" rel="noopener noreferrer" aria-label={type === 'apple' ? 'Add to Apple Wallet' : 'Add to Google Wallet'}> and keep the current img inside (preserve className and alt), removing the onClick; this ensures the link is focusable and opens the URL securely in a new tab.frontend/src/api/hooks/form/useFormSubmit.tsx (1)
10-31:⚠️ Potential issue | 🔴 CriticalMissing
setLoading(true)at the start of the submit function.The
loadingstate is initialized tofalseand onlysetLoading(false)is called in the.finally()blocks. This meanssubmitLoadingwill always remainfalse, rendering the loading state useless for consumers of this hook.🐛 Proposed fix to set loading state correctly
const submit = (formBody: object, edit?: boolean) => { + setLoading(true) if (edit) { axios🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/api/hooks/form/useFormSubmit.tsx` around lines 10 - 31, The submit function never sets loading to true, so setLoading(true) at the start of the submit(formBody, edit?) function (before the if (edit) branch) so both the axios.put and axios.post paths flip loading on; keep the existing .finally(() => setLoading(false)) in both branches and ensure you call setLoading(true) before invoking axios (referencing the submit function and setLoading state updater).frontend/src/api/hooks/team/actions/useTeamJoin.ts (1)
10-11:⚠️ Potential issue | 🟡 MinorRemove the
//todocomment on line 10.This
//todocomment is a leftover development artifact. Verification confirms this appears across all 10 team action hooks in the directory—ensure these are cleaned up project-wide before merging.Proposed fix
- axios //todo + axios🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/api/hooks/team/actions/useTeamJoin.ts` around lines 10 - 11, Remove the stray “//todo” inline comment from the axios.post call in useTeamJoin (the axios.post<TeamResponses>(`/api/team/join`, { id }) expression) and similarly remove that same leftover comment from the other nine team action hooks in the directory so no development artifact comments remain; search for "//todo" within frontend/src/api/hooks/team/actions and delete those occurrences, then run a quick lint/format pass to ensure no trailing whitespace changes.frontend/src/pages/riddle/riddle.page.tsx (1)
90-115:⚠️ Potential issue | 🟡 MinorAdd explicit handling for failed skip status to prevent silent failures.
The skip success handler doesn't account for all possible response statuses. When
result.nextRiddles.lengthis 0 and the server returnsCANNOT_SKIP(due to a race condition whereskipPermittedchanged after the UI rendered), the handler silently does nothing with no feedback to the user. Additionally, the first condition at line 94 checks only fornextRiddles.lengthwithout verifying the status, which could navigate incorrectly if the skip failed.Add explicit status validation to handle unexpected statuses:
Suggested improvement
const skipSolution = () => { if (riddleConfig.skipEnabled && data.skipPermitted) { skipMutation.mutate(id, { onSuccess: (result) => { + if (result.status === RiddleSubmissionStatus.CANNOT_SKIP) { + toast({ title: l('riddle-cannot-skip'), variant: 'destructive' }) + return + } if (result.nextRiddles.length) { navigate(`${AbsolutePaths.RIDDLE}/solve/${result.nextRiddles[0].id}`) if (solutionInput.current) solutionInput.current.value = '' toast({ title: l('riddle-skipped-title'), description: l('riddle-skipped-description') }) } if (result.status === RiddleSubmissionStatus.CORRECT && !result.nextRiddles.length) { navigate(AbsolutePaths.RIDDLE) toast({ title: l('riddle-completed-title'), description: l('riddle-completed-description') }) } },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/riddle/riddle.page.tsx` around lines 90 - 115, The success handler in skipSolution (inside skipMutation.mutate) must validate the returned result.status before acting: only navigate to result.nextRiddles[0] and clear solutionInput when the status indicates the skip actually succeeded (i.e., not RiddleSubmissionStatus.CANNOT_SKIP); if result.status === RiddleSubmissionStatus.CANNOT_SKIP or any other unexpected status, call toast with a descriptive failure message instead of silently doing nothing; also update the CORRECT branch to only trigger when status === RiddleSubmissionStatus.CORRECT and nextRiddles.length === 0, and add a default fallback toast for any unhandled statuses so the user always receives feedback.frontend/src/pages/race/components/RaceBoard.tsx (1)
20-20:⚠️ Potential issue | 🔴 CriticalGuard
boardbefore calling.some()to prevent render-time crash.Line 20 uses
data?.board.some(...), butPropsacceptsdata: RaceView | undefined. Whendatais undefined, the useMemo callback executes before the guard on line 21, causing a runtime error. The optional chaining needs to extend to.some().Proposed fix
- const showDescription = useMemo(() => data?.board.some((i) => !!i.description), [data?.board]) + const showDescription = useMemo( + () => data?.board?.some((i) => Boolean(i.description)) ?? false, + [data?.board] + )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/race/components/RaceBoard.tsx` at line 20, The useMemo for showDescription in RaceBoard currently calls data?.board.some(...) which can throw when data is undefined; update the guard so you call .some only if board exists (e.g., change the expression to check data?.board?.some(...) or use a boolean guard like data?.board && data.board.some(...)) and keep the same dependency array [data?.board]; adjust the showDescription computation accordingly to avoid render-time crashes.frontend/src/pages/profile/profile.groupChange.page.tsx (1)
61-103:⚠️ Potential issue | 🟠 MajorEnter-key submission is blocked; form semantic structure is broken.
Line 72 prevents default submission without calling the
onSubmit()handler, making keyboard submission impossible. Only the click handler at line 102 triggers the mutation.Fix the form submission flow:
- Restore form submission by calling
onSubmit()in the form's onSubmit handler (after preventDefault)- Add
type="submit"to the save button (line 102)- Add
type="button"to secondary action buttons to prevent accidental form submissionProposed fix
- <form onSubmit={(e) => e.preventDefault()}> + <form + onSubmit={(e) => { + e.preventDefault() + onSubmit() + }} + > @@ - <Button + <Button + type="button" variant="ghost" onClick={() => { setValue(profile.fallbackGroup?.toString() || '') }} > @@ - <Button onClick={onSubmit} disabled={isPending}> + <Button type="submit" disabled={isPending}> {isPending ? 'Mentés...' : 'Mentés'} </Button> - <LinkButton href={AbsolutePaths.PROFILE} variant="outline"> + <LinkButton href={AbsolutePaths.PROFILE} variant="outline" type="button"> Mégse </LinkButton>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/profile/profile.groupChange.page.tsx` around lines 61 - 103, The form currently prevents default submission but never invokes onSubmit(), blocking Enter-key submits; update the form's onSubmit handler to call onSubmit() after e.preventDefault(), change the primary save Button (the one using onClick={onSubmit} and isPending) to type="submit" and keep its onClick optional, and ensure any other Buttons inside the form (e.g., the "Vendég vagyok" ghost Button) are marked type="button" to avoid accidental submissions; reference the onSubmit function, the form element's onSubmit, the primary save Button (uses isPending and onClick={onSubmit}), and the guest Button (calls setValue) when applying these changes.frontend/src/pages/extra/extra.page.tsx (1)
20-23:⚠️ Potential issue | 🔴 CriticalAuthorization check can be bypassed for guests
Line 20 short-circuits on
authInfo, so unauthenticated users skip the role comparison and can see protected content. Compare roles unconditionally with a guest fallback.🔐 Proposed fix
- if (RoleType[data.minRole] > RoleType.GUEST && authInfo && RoleType[authInfo?.role ?? RoleTypeString.GUEST] < RoleType[data.minRole]) { + const requiredRole = RoleType[data.minRole] + const currentRole = RoleType[authInfo?.role ?? RoleTypeString.GUEST] + if (requiredRole > RoleType.GUEST && currentRole < requiredRole) { sendMessage(l('no-permission')) return <Navigate replace to={AbsolutePaths.ERROR} /> }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/extra/extra.page.tsx` around lines 20 - 23, The authorization check currently short-circuits on authInfo, letting unauthenticated users bypass the role comparison; change the conditional to always compare numeric role values by using a guest fallback for missing authInfo (e.g., use (authInfo?.role ?? RoleTypeString.GUEST) or equivalent) so the comparison RoleType[authInfo?.role ?? RoleTypeString.GUEST] < RoleType[data.minRole] is evaluated unconditionally; update the if in extra.page.tsx (references: RoleType, authInfo, data.minRole, RoleTypeString.GUEST) to remove the authInfo short-circuit and keep the existing sendMessage(l('no-permission')) and Navigate to AbsolutePaths.ERROR behavior.frontend/src/common-components/PageStatus.tsx (2)
17-25:⚠️ Potential issue | 🔴 CriticalLogic error: error message sent when page loads successfully.
The current logic sends
'page-load-failed-contact-developers'message whenisErrorisfalse(line 24). This means a successful page load triggers an error notification, which appears to be inverted logic.The
returnstatement on line 21 exits the effect when there IS an error, then line 24 executes for the non-error case.🐛 Proposed fix
useEffect(() => { if (isLoading) return if (isError) { - sendMessage(l('page-load-failed', { title })) - return + sendMessage(l('page-load-failed-contact-developers', { title })) } - - sendMessage(l('page-load-failed-contact-developers', { title })) }, [isLoading, isError, sendMessage, title])Alternatively, if both messages are intentional for different scenarios, the current logic needs to be inverted so the "contact developers" message is sent for errors and a different handling exists for success.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/common-components/PageStatus.tsx` around lines 17 - 25, The effect in PageStatus's useEffect uses inverted logic causing a success load to send the 'page-load-failed-contact-developers' message; update the conditional flow so that when isLoading is true it returns, when isError is true it sends the more urgent 'page-load-failed-contact-developers' (using sendMessage(l(..., { title }))) and otherwise sends the normal success/failed fallback message (or no message) — adjust the order/conditions around isLoading, isError, sendMessage, l and title in the useEffect callback accordingly.
31-35:⚠️ Potential issue | 🟠 MajorRedundant fallback case navigates to same destination.
Lines 31-33 handle
isErrorby navigating toAbsolutePaths.ERROR, and line 35 does the exact same thing as a fallback. This fallback case would only be reached when!isLoading && !isError, which suggests either:
- The fallback should handle a different scenario (e.g., return
nullor render children)- The component's purpose is unclear if both success and error states navigate to the error page
🔧 Suggested fix if fallback should not navigate to error
if (isError) { return <Navigate replace to={AbsolutePaths.ERROR} /> } - return <Navigate replace to={AbsolutePaths.ERROR} /> + return null🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/common-components/PageStatus.tsx` around lines 31 - 35, The fallback currently navigates to AbsolutePaths.ERROR even when neither isLoading nor isError, making the earlier isError check redundant; update the PageStatus component so that when !isLoading && !isError it does not navigate to AbsolutePaths.ERROR but instead returns the successful UI (e.g., render children or null) or navigates to the correct success path — adjust the return after the isError check (referencing isError, isLoading, Navigate, and AbsolutePaths.ERROR) to handle the non-error, non-loading case appropriately.frontend/src/common-components/map/MapContent.tsx (1)
50-54:⚠️ Potential issue | 🟠 MajorReplace stale Chakra color token with valid hex color.
color="blue.500"is a Chakra token that won't work in this context. TheMapMarkercomponent expects a hex color format (used directly instyle={{ backgroundColor: color }}and passed togetTextColorFromLuminance()which parses hex values). Use a valid CSS hex color like"#3b82f6"(Tailwind's blue-500 equivalent) instead.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/common-components/map/MapContent.tsx` around lines 50 - 54, The MapMarker color prop is using an invalid Chakra token ("blue.500"); update the JSX in MapContent where Marker and MapMarker are rendered (the Marker wrapper and MapMarker component) to pass a hex color string instead (e.g. "#3b82f6") so MapMarker's style={{ backgroundColor: color }} and getTextColorFromLuminance() receive a valid hex value; locate the MapMarker usage in MapContent (the Marker/MapMarker pair) and replace color="blue.500" with color="#3b82f6".frontend/src/pages/events/components/event-calendar/HourColumn.tsx (1)
6-14:⚠️ Potential issue | 🟡 Minor
useMemohas no effect becauseoriginDatechanges every render.
startOfDay(new Date())on line 6 creates a newDateobject on every render. SinceoriginDateis in the dependency array,useMemowill recomputedateson every render, defeating its purpose.Move
originDateinside theuseMemoor memoize it separately:Suggested fix
export function HourColumn({ h }: { h: number }) { - const originDate = startOfDay(new Date()) const dates = useMemo(() => { + const originDate = startOfDay(new Date()) const datesTemp: Date[] = [] for (let i = 0; i < 24; i++) { const date = addHours(originDate, i) datesTemp.push(date) } - return datesTemp - }, [originDate]) + return { dates: datesTemp, originDate } + }, []) - const minDate = originDate - const maxDate = endOfDay(originDate) + const minDate = dates.originDate + const maxDate = endOfDay(dates.originDate)Or, simpler refactor returning both values:
export function HourColumn({ h }: { h: number }) { - const originDate = startOfDay(new Date()) - const dates = useMemo(() => { + const { dates, minDate, maxDate } = useMemo(() => { + const originDate = startOfDay(new Date()) const datesTemp: Date[] = [] for (let i = 0; i < 24; i++) { const date = addHours(originDate, i) datesTemp.push(date) } - return datesTemp - }, [originDate]) - - const minDate = originDate - const maxDate = endOfDay(originDate) + return { dates: datesTemp, minDate: originDate, maxDate: endOfDay(originDate) } + }, [])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/events/components/event-calendar/HourColumn.tsx` around lines 6 - 14, The useMemo for dates is ineffective because originDate is recomputed each render (originDate = startOfDay(new Date())), so move the originDate creation into the same useMemo (or memoize it separately) so the dependency array is stable: update the dates useMemo to compute const originDate = startOfDay(new Date()) inside the useMemo (or create originDate via useMemo(() => startOfDay(new Date()), [])) and keep dates computed from that stable originDate; modify references to originDate/dates accordingly in HourColumn (useMemo, originDate, dates, startOfDay, addHours).frontend/src/pages/home/components/ImageCarousel.tsx (1)
62-71:⚠️ Potential issue | 🟡 MinorAdd explicit button semantics to carousel indicator dots.
These controls are unlabeled for assistive tech, and default button type may submit surrounding forms.
Suggested fix
<button + type="button" + aria-label={`Ugrás a(z) ${index + 1}. képre`} + aria-current={index === currentIndex} className="h-[10px] w-[10px] p-0 border-2 border-solid rounded-full cursor-pointer transition-all hover:border-[5px]" style={{🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/home/components/ImageCarousel.tsx` around lines 62 - 71, The carousel indicator button in ImageCarousel is missing accessible semantics and a safe button type; update the button element (the indicator rendering that uses onClick, currentIndex and brandColor) to include type="button", an appropriate aria-label (e.g. `aria-label={`Go to slide ${index + 1}`}`), and aria-current={index === currentIndex ? "true" : undefined} (or aria-pressed) so assistive tech can identify the control and current slide; keep the existing styles and onClick behavior.frontend/src/pages/home/components/HomePageNewsList.tsx (1)
6-16:⚠️ Potential issue | 🟡 MinorAvoid mutating the input array in
sortByHighlighted.
Array.sort()mutates the original array in place. SincehomeNews.datacomes from a hook (likely React Query), mutating it can cause unexpected behavior or stale UI issues.🛡️ Proposed fix to avoid mutation
const sortByHighlighted = (news: NewsArticleView[]) => { - return news.sort((a, b) => { + return [...news].sort((a, b) => { if (a.highlighted && !b.highlighted) { return -1 }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/home/components/HomePageNewsList.tsx` around lines 6 - 16, sortByHighlighted currently mutates its input by calling news.sort(), which can corrupt data from hooks like homeNews.data; change it to operate on a shallow copy (e.g., copy the array with [...news] or news.slice()) and return the sorted copy instead, keeping the comparator logic in sortByHighlighted unchanged so callers (including where homeNews.data is passed) receive a new array without mutating the original.frontend/src/pages/events/components/event-calendar/DayCalendar.tsx (1)
34-40:⚠️ Potential issue | 🟡 MinorAdd bounds to scale increment/decrement to prevent invalid values.
Unlike
WeekCalendar, which clamps the scale between 0.6 and 2,DayCalendarhas no bounds. This allows the scale to become negative or excessively large, breaking the layout.Proposed fix to match WeekCalendar bounds
const incrementScale = () => { - setScale((prev) => prev + 0.2) + setScale((prev) => Math.min(prev + 0.2, 2)) } const decrementScale = () => { - setScale((prev) => prev - 0.2) + setScale((prev) => Math.max(prev - 0.2, 0.6)) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/events/components/event-calendar/DayCalendar.tsx` around lines 34 - 40, The incrementScale and decrementScale functions allow scale to go outside sane limits; update them to clamp the new scale between 0.6 and 2 (same bounds used in WeekCalendar) before calling setScale. In other words, compute the candidate value inside incrementScale/decrementScale (prev + 0.2 / prev - 0.2), then clamp it to the range [0.6, 2] and pass the clamped value to setScale so scale never becomes negative or excessively large.frontend/src/common-components/layout/CmschPage.tsx (1)
14-27:⚠️ Potential issue | 🟠 MajorPreserve incoming
classNamewhen adding page padding.
className="pb-10"currently overrides anyclassNamepassed throughCmschPageProps.Suggested fix
+import { cn } from '@/lib/utils' ... -export const CmschPage = ({ children, loginRequired, minRole, title, ...props }: CmschPageProps) => { +export const CmschPage = ({ children, loginRequired, minRole, title, className, ...props }: CmschPageProps) => { ... - <CmschContainer {...props} className="pb-10"> + <CmschContainer {...props} className={cn('pb-10', className)}>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/common-components/layout/CmschPage.tsx` around lines 14 - 27, CmschPage currently forces className="pb-10" on the CmschContainer which overwrites any incoming className in props; update CmschPage so it merges the incoming props.className with "pb-10" (e.g., combine props.className and "pb-10" into a single className string) before passing to CmschContainer, ensuring you reference CmschPage, CmschContainer and the incoming props to build the merged className.backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/LoginService.kt (1)
396-429:⚠️ Potential issue | 🟠 MajorMissing concurrency lock in
fetchKeycloakUserEntity— potential race condition.Unlike
fetchUserEntity(authsch) andfetchGoogleUserEntity, this method does not acquire a lock viauserLocks.lockForKey(). Concurrent logins with the same Keycloak user could result in duplicate user creation or lost updates when updatinginternalId.Suggested fix: Add locking consistent with other methods
fun fetchKeycloakUserEntity(profile: KeycloakUserInfoResponse): UserEntity { + val lock = userLocks.lockForKey(profile.sid) + try { var user: UserEntity val existingByInternalId = users.findById(profile.sid) // ... existing logic ... updateFieldsForKeycloak(profile, user) users.save(user) + adminMenuService.invalidateUser(user.internalId) return user + } finally { + lock.unlock() + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/LoginService.kt` around lines 396 - 429, fetchKeycloakUserEntity currently lacks the concurrency lock used by fetchUserEntity/fetchGoogleUserEntity, risking duplicate users or lost internalId updates; wrap the lookup/create/update/save logic inside userLocks.lockForKey(...) using a stable key (preferably profile.sid, falling back to profile.email when sid is blank) so the entire block that reads users.findById/profile.email, mutates user.internalId, calls updateFieldsForKeycloak(profile, user) and users.save(user) executes under the same lock; mirror the locking pattern and exception-safe release used in fetchUserEntity/fetchGoogleUserEntity.frontend/src/pages/news/components/NewsList.tsx (1)
18-18:⚠️ Potential issue | 🟠 MajorArray mutation:
.sort()mutates the original array.
newsList.sort()mutates the prop array in place, which violates React's immutability principle and can cause subtle bugs. Create a copy before sorting.Fix
- const sortedNewsList = useMemo(() => newsList.sort((a, b) => b.timestamp - a.timestamp), [newsList]) + const sortedNewsList = useMemo(() => [...newsList].sort((a, b) => b.timestamp - a.timestamp), [newsList])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/news/components/NewsList.tsx` at line 18, The current useMemo in NewsList uses newsList.sort() which mutates the original prop array; change the computation for sortedNewsList to sort a shallow copy instead (e.g., copy newsList via slice/spread before calling sort) so the original newsList prop is not mutated; update the useMemo callback that computes sortedNewsList to use a copied array and keep the dependency [newsList] unchanged.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (299)
README.mdbackend/docs/event.mdbackend/src/main/kotlin/hu/bme/sch/cmsch/component/admission/AdmissionComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/admission/AdmissionComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/app/ApplicationComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/app/ApplicationComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/app/FooterComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/app/ManifestComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/app/StylingComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/app/StylingComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/bmejegy/BmejegyComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/challenge/ChallengeComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/communities/CommunitiesComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/conference/ConferenceComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/countdown/CountdownComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/countdown/CountdownComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/debt/DebtComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/email/EmailComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/errorlog/ErrorLogComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/event/EventComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/event/EventComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/form/FormComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/gallery/GalleryComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/home/HomeComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/home/HomeComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/impressum/ImpressumComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/impressum/ImpressumComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/key/AccessKeyComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/leaderboard/LeaderBoardComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/location/LocationComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/login/CmschUserDetailsService.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/login/LoginComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/login/LoginComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/login/LoginService.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/login/PasswordLoginController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/messaging/MessagingComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/news/NewsComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/news/NewsComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/profile/ProfileComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/profile/ProfileComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/proto/ProtoComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/pushnotification/PushNotificationComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/qrfight/QrFightComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/qrfight/QrFightComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/race/RaceComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/race/RaceComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/riddle/RiddleComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/riddle/RiddleComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/script/ScriptComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/serviceaccount/ServiceAccountComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/serviceaccount/ServiceAccountComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/sheets/SheetsComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/staticpage/StaticPageComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/staticpage/StaticPageComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/task/TaskComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/task/TaskComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/team/TeamComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/team/TeamComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/token/TokenComponent.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/component/token/TokenComponentController.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/config/SecurityConfig.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/model/UserEntity.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/service/RateLimiterService.ktbackend/src/main/kotlin/hu/bme/sch/cmsch/service/UserService.ktfrontend/components.jsonfrontend/index.htmlfrontend/package.jsonfrontend/postcss.config.jsfrontend/src/App.tsxfrontend/src/api/contexts/auth/AuthContext.tsxfrontend/src/api/contexts/config/ConfigContext.tsxfrontend/src/api/contexts/config/types.tsfrontend/src/api/contexts/service/ServiceContext.tsxfrontend/src/api/contexts/themeConfig/ThemeConfig.tsxfrontend/src/api/hooks/access-key/useAccessKeyMutation.tsfrontend/src/api/hooks/access-key/useAccessKeyQuery.tsfrontend/src/api/hooks/alias/useAliasChangeMutation.tsfrontend/src/api/hooks/auth/useAuthInfo.tsfrontend/src/api/hooks/community/useCommunity.tsfrontend/src/api/hooks/community/useCommunityList.tsfrontend/src/api/hooks/community/useOrganization.tsfrontend/src/api/hooks/community/useOrganizationList.tsfrontend/src/api/hooks/community/useTinderAnswerSend.tsfrontend/src/api/hooks/community/useTinderAnswers.tsfrontend/src/api/hooks/community/useTinderCommunity.tsfrontend/src/api/hooks/community/useTinderInteractionReset.tsfrontend/src/api/hooks/community/useTinderInteractionSend.tsfrontend/src/api/hooks/community/useTinderQuestions.tsfrontend/src/api/hooks/config/useConfigQuery.tsfrontend/src/api/hooks/debt/useDebtQuery.tsfrontend/src/api/hooks/developers/useDevelopers.tsfrontend/src/api/hooks/event/useEventListQuery.tsfrontend/src/api/hooks/event/useEventQuery.tsfrontend/src/api/hooks/extra/useExtraPage.tsxfrontend/src/api/hooks/form/useFormPage.tsxfrontend/src/api/hooks/form/useFormSubmit.tsxfrontend/src/api/hooks/gallery/useHomeGallery.tsfrontend/src/api/hooks/group-change/useGroupChangeMutation.tsfrontend/src/api/hooks/home/useHomeNews.tsxfrontend/src/api/hooks/leaderboard/useLeaderBoardQuery.tsfrontend/src/api/hooks/location/useLocationQuery.tsfrontend/src/api/hooks/news/useNewsListQuery.tsfrontend/src/api/hooks/news/useNewsQuery.tsfrontend/src/api/hooks/profile/useProfileQuery.tsfrontend/src/api/hooks/qr/useQrLevelsQuery.tsfrontend/src/api/hooks/race/useFreestyleRaceQuery.tsfrontend/src/api/hooks/race/useRaceByTeamQuery.tsfrontend/src/api/hooks/race/useRaceQuery.tsfrontend/src/api/hooks/riddle/useRiddleDeatilsQuery.tsfrontend/src/api/hooks/riddle/useRiddleHintQuery.tsfrontend/src/api/hooks/riddle/useRiddleHistoryQuery.tsfrontend/src/api/hooks/riddle/useRiddleListQuery.tsfrontend/src/api/hooks/riddle/useRiddleSkipMutation.tsfrontend/src/api/hooks/riddle/useRiddleSubmitMutation.tsfrontend/src/api/hooks/task/useTaskCategoriesQuery.tsfrontend/src/api/hooks/task/useTaskFullDetailsQuery.tsfrontend/src/api/hooks/task/useTaskSubmissionMutation.tsfrontend/src/api/hooks/task/useTasksInCategoryQuery.tsfrontend/src/api/hooks/team/actions/useTeamCancelJoin.tsfrontend/src/api/hooks/team/actions/useTeamCreate.tsfrontend/src/api/hooks/team/actions/useTeamEdit.tsfrontend/src/api/hooks/team/actions/useTeamJoin.tsfrontend/src/api/hooks/team/actions/useTeamLeave.tsfrontend/src/api/hooks/team/queries/useMyTeam.tsfrontend/src/api/hooks/team/queries/useTeamDetails.tsfrontend/src/api/hooks/team/queries/useTeamList.tsfrontend/src/api/hooks/token/useScanTokenMutation.tsfrontend/src/api/hooks/token/useTokensQuery.tsfrontend/src/api/hooks/useTokenRefresh.tsfrontend/src/common-components/BoardStat.tsxfrontend/src/common-components/CmschLink.tsxfrontend/src/common-components/CollapsableTableRow.tsxfrontend/src/common-components/ComponentUnavailable.tsxfrontend/src/common-components/ConfirmDialogButton.tsxfrontend/src/common-components/CurrentEventCard.tsxfrontend/src/common-components/CustomBreadcrumb.tsxfrontend/src/common-components/CustomTabButton.tsxfrontend/src/common-components/EnableNotifications.tsxfrontend/src/common-components/EventIndicator.tsxfrontend/src/common-components/LeaderboardTable.tsxfrontend/src/common-components/LinkButton.tsxfrontend/src/common-components/Loading.tsxfrontend/src/common-components/PageStatus.tsxfrontend/src/common-components/PresenceAlert.tsxfrontend/src/common-components/PulsingDot.tsxfrontend/src/common-components/PushNotificationHandler.tsxfrontend/src/common-components/SearchBar.tsxfrontend/src/common-components/StopItModal.tsxfrontend/src/common-components/TokenRarityDisplay.tsxfrontend/src/common-components/VotingField.tsxfrontend/src/common-components/WalletButton.tsxfrontend/src/common-components/Warning.tsxfrontend/src/common-components/chakra-md-renderer.tsxfrontend/src/common-components/cmsch-ui-renderer.tsxfrontend/src/common-components/footer/Footer.tsxfrontend/src/common-components/footer/OrganizerLogo.tsxfrontend/src/common-components/footer/PartnerLogo.tsxfrontend/src/common-components/layout/AppBackground.tsxfrontend/src/common-components/layout/CmschContainer.tsxfrontend/src/common-components/layout/CmschLayout.tsxfrontend/src/common-components/layout/CmschPage.tsxfrontend/src/common-components/map/GroupMapContainer.tsxfrontend/src/common-components/map/MapContent.tsxfrontend/src/common-components/map/MapMarker.tsxfrontend/src/common-components/navigation/ColorModeSwitcher.tsxfrontend/src/common-components/navigation/Navbar.tsxfrontend/src/common-components/navigation/desktop/ChildNavItem.tsxfrontend/src/common-components/navigation/desktop/DesktopNav.tsxfrontend/src/common-components/navigation/desktop/NavItemNoChildren.tsxfrontend/src/common-components/navigation/desktop/NavItemWithChildren.tsxfrontend/src/common-components/navigation/mobile/MobileNav.tsxfrontend/src/common-components/navigation/mobile/NavItemNoChildren.tsxfrontend/src/common-components/navigation/mobile/NavItemWithChildren.tsxfrontend/src/components/ui/accordion.tsxfrontend/src/components/ui/alert-dialog.tsxfrontend/src/components/ui/alert.tsxfrontend/src/components/ui/avatar.tsxfrontend/src/components/ui/badge.tsxfrontend/src/components/ui/breadcrumb.tsxfrontend/src/components/ui/button.tsxfrontend/src/components/ui/card.tsxfrontend/src/components/ui/checkbox.tsxfrontend/src/components/ui/dialog.tsxfrontend/src/components/ui/dropdown-menu.tsxfrontend/src/components/ui/input.tsxfrontend/src/components/ui/label.tsxfrontend/src/components/ui/popover.tsxfrontend/src/components/ui/progress.tsxfrontend/src/components/ui/radio-group.tsxfrontend/src/components/ui/select.tsxfrontend/src/components/ui/separator.tsxfrontend/src/components/ui/skeleton.tsxfrontend/src/components/ui/switch.tsxfrontend/src/components/ui/table.tsxfrontend/src/components/ui/tabs.tsxfrontend/src/components/ui/textarea.tsxfrontend/src/components/ui/toast.tsxfrontend/src/components/ui/toaster.tsxfrontend/src/components/ui/tooltip.tsxfrontend/src/hooks/use-toast.tsfrontend/src/index.cssfrontend/src/index.tsxfrontend/src/lib/utils.tsfrontend/src/pages/access-key/accessKey.page.tsxfrontend/src/pages/communities/community.page.tsxfrontend/src/pages/communities/communityList.page.tsxfrontend/src/pages/communities/components/CardListItem.tsxfrontend/src/pages/communities/components/DataSheet.tsxfrontend/src/pages/communities/components/Frame.tsxfrontend/src/pages/communities/components/TinderCard.tsxfrontend/src/pages/communities/likedCommunityList.page.tsxfrontend/src/pages/communities/organization.page.tsxfrontend/src/pages/communities/organizationList.page.tsxfrontend/src/pages/communities/tinder.page.tsxfrontend/src/pages/communities/tinderQuestions.page.tsxfrontend/src/pages/communities/tinderRouter.tsxfrontend/src/pages/countdown/components/clock.tsxfrontend/src/pages/countdown/countdown.page.tsxfrontend/src/pages/countdown/countdown.util.tsxfrontend/src/pages/debt/components/debt-list-item.tsxfrontend/src/pages/debt/debt.page.tsxfrontend/src/pages/error/error.page.tsxfrontend/src/pages/error/unauthorized.page.tsxfrontend/src/pages/events/components/CardListItem.tsxfrontend/src/pages/events/components/CurrentEvent.tsxfrontend/src/pages/events/components/CustomTab.tsxfrontend/src/pages/events/components/EventFilterOption.tsxfrontend/src/pages/events/components/EventList.tsxfrontend/src/pages/events/components/EventListItem.tsxfrontend/src/pages/events/components/EventTags.tsxfrontend/src/pages/events/components/event-calendar/CurrentDateBar.tsxfrontend/src/pages/events/components/event-calendar/DayCalendar.tsxfrontend/src/pages/events/components/event-calendar/EventBox.tsxfrontend/src/pages/events/components/event-calendar/HourColumn.tsxfrontend/src/pages/events/components/event-calendar/WeekCalendar.tsxfrontend/src/pages/events/components/event-calendar/ZoomBar.tsxfrontend/src/pages/events/components/event-calendar/utils.tsfrontend/src/pages/events/event.page.tsxfrontend/src/pages/events/eventCalendar.page.tsxfrontend/src/pages/events/eventList.page.tsxfrontend/src/pages/events/util/filter.tsxfrontend/src/pages/extra/extra.page.tsxfrontend/src/pages/form/components/GridField.tsxfrontend/src/pages/form/components/GridFieldItem.tsxfrontend/src/pages/form/components/autoFormField.tsxfrontend/src/pages/form/components/formStatusBadge.tsxfrontend/src/pages/form/form.page.tsxfrontend/src/pages/home/components/EmbeddedVideo.tsxfrontend/src/pages/home/components/HomePageEventList.tsxfrontend/src/pages/home/components/HomePageGalleryCarousel.tsxfrontend/src/pages/home/components/HomePageNewsList.tsxfrontend/src/pages/home/components/ImageCarousel.tsxfrontend/src/pages/home/components/Schedule.tsxfrontend/src/pages/home/home.page.tsxfrontend/src/pages/impressum/components/DeveloperWrapItem.tsxfrontend/src/pages/impressum/components/OrganizerSection.tsxfrontend/src/pages/impressum/components/OrganizerWrapItem.tsxfrontend/src/pages/impressum/impressum.page.tsxfrontend/src/pages/impressum/util/arrayFunctions.tsfrontend/src/pages/index/index.page.tsxfrontend/src/pages/leaderboard/leaderboard.page.tsxfrontend/src/pages/leaderboard/leaderboardByCategory.page.tsxfrontend/src/pages/leaderboard/leaderboardByUserOrGroup.page.tsxfrontend/src/pages/loading/loading.page.tsxfrontend/src/pages/login/login.page.tsxfrontend/src/pages/login/resetPassword.page.tsxfrontend/src/pages/map/map.page.tsxfrontend/src/pages/news/components/News.tsxfrontend/src/pages/news/components/NewsList.tsxfrontend/src/pages/news/components/NewsListItem.tsxfrontend/src/pages/news/news.page.tsxfrontend/src/pages/news/newsList.page.tsxfrontend/src/pages/news/util/sortNewsList.tsfrontend/src/pages/profile/components/GroupLeaderContactList.tsxfrontend/src/pages/profile/components/Popup.tsxfrontend/src/pages/profile/components/ProfileQR.tsxfrontend/src/pages/profile/profile.aliasChange.page.tsxfrontend/src/pages/profile/profile.groupChange.page.tsxfrontend/src/pages/profile/profile.page.tsxfrontend/src/pages/profile/util/percentFunctions.tsfrontend/src/pages/qr-fight/components/DataDisplayWrapper.tsxfrontend/src/pages/qr-fight/components/LevelDataDisplay.tsxfrontend/src/pages/qr-fight/components/LevelStatusBadge.tsxfrontend/src/pages/qr-fight/components/TotemDataDisplay.tsxfrontend/src/pages/qr-fight/components/TotemField.tsxfrontend/src/pages/qr-fight/components/TowerDataDisplay.tsxfrontend/src/pages/qr-fight/components/TowerField.tsxfrontend/src/pages/qr-fight/components/TreasureDataDisplayWrapper.tsxfrontend/src/pages/qr-fight/qrLevels.page.tsxfrontend/src/pages/race/components/RaceBoard.tsxfrontend/src/pages/race/freestyleRace.page.tsxfrontend/src/pages/race/race.page.tsxfrontend/src/pages/race/raceByTeam.page.tsxfrontend/src/pages/riddle/components/RiddleCategoryListItem.tsxfrontend/src/pages/riddle/components/RiddleListItem.tsxfrontend/src/pages/riddle/components/SpoilerText.tsxfrontend/src/pages/riddle/riddle.page.tsx
💤 Files with no reviewable changes (2)
- backend/docs/event.md
- frontend/src/pages/events/components/CustomTab.tsx
| open fun findByEmail(email: String): Optional<UserEntity> { | ||
| return users.findByEmail(email) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== UserEntity email field and constraints =="
rg -n "class UserEntity|email|@Column|unique" --type kt backend/src/main/kotlin
echo
echo "== SQL/migration constraints involving user email =="
rg -n "email|unique|index|constraint" --type sql backend || true
echo
echo "== Repository methods for email lookups =="
rg -n "interface .*UserRepository|findByEmail\(" --type kt backend/src/main/kotlin
echo
echo "== Normalization patterns around email persistence/lookup =="
rg -n "lowercase\(|trim\(|findByEmail\(|email\s*=|setEmail" --type kt backend/src/main/kotlinRepository: kir-dev/cmsch
Length of output: 145
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Finding Kotlin files with UserEntity definition =="
fd -e kt . backend/src/main/kotlin | head -20
echo
echo "== UserEntity email field and constraints =="
rg -g "*.kt" "class UserEntity|email|@Column|unique" backend/src/main/kotlin
echo
echo "== SQL/migration files =="
fd -e sql backend
echo
echo "== UserRepository findByEmail =="
rg -g "*.kt" "findByEmail" backend/src/main/kotlin
echo
echo "== Email normalization patterns =="
rg -g "*.kt" "lowercase|trim|\.email" backend/src/main/kotlin -A 2 -B 2Repository: kir-dev/cmsch
Length of output: 50370
🏁 Script executed:
#!/bin/bash
echo "=== UserEntity full definition ==="
rg -g "*.kt" -B5 -A30 "class UserEntity" backend/src/main/kotlin/hu/bme/sch/cmsch/model/UserEntity.kt | head -80
echo
echo "=== Email field definition in UserEntity ==="
rg -g "*.kt" -B2 -A2 "var email:" backend/src/main/kotlin/hu/bme/sch/cmsch/model/UserEntity.kt
echo
echo "=== Index definitions for UserEntity ==="
rg -g "*.kt" -B5 "Index\|@Table" backend/src/main/kotlin/hu/bme/sch/cmsch/model/UserEntity.kt | grep -A10 -B10 "index"
echo
echo "=== Login/registration email handling ==="
rg -g "*.kt" "findByEmail|email.lowercase|email.trim|email =" backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/ -B2 -A2
echo
echo "=== Email normalization in UserService ==="
rg -g "*.kt" "lowercase|trim" backend/src/main/kotlin/hu/bme/sch/cmsch/service/UserService.kt -B2 -A2Repository: kir-dev/cmsch
Length of output: 7766
🏁 Script executed:
#!/bin/bash
echo "=== Check if email field has any `@Column` unique constraint ==="
rg -g "*.kt" -B5 "var email:" backend/src/main/kotlin/hu/bme/sch/cmsch/model/UserEntity.kt
echo
echo "=== Verify no normalization in registration or auth flows ==="
rg -g "*.kt" "\.trim\(\)|\.lowercase\(\)|\.uppercase\(\)" backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/ -c
echo
echo "=== Check PasswordLoginController registration flow ==="
rg -g "*.kt" -B10 -A10 "email = request.email" backend/src/main/kotlin/hu/bme/sch/cmsch/component/login/PasswordLoginController.ktRepository: kir-dev/cmsch
Length of output: 1353
Email uniqueness is not enforced at the database level, and email canonicalization is missing from registration and auth lookup flows.
The email field in UserEntity has only a non-unique index and no @Column(unique=true) constraint. More critically, no trim/lowercase normalization is applied in registration (PasswordLoginController assigns email = request.email directly) or during lookups (findByEmail() is case-sensitive). This creates several risks:
- Different email cases (e.g.,
Test@example.comvstest@example.com) are treated as separate accounts during signup - Lookups fail for case-mismatched emails
GroupToUserMappingEntityalready usesfindByEmailIgnoreCase(), indicating case sensitivity is a known concern elsewhere
Add a unique constraint on the email column, establish a canonicalization policy (trim + lowercase), and apply it consistently in PasswordLoginController registration, LoginService auth flows, and CmschUserDetailsService lookups to ensure predictable auth behavior.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/src/main/kotlin/hu/bme/sch/cmsch/service/UserService.kt` around lines
49 - 50, Add a DB-level unique constraint to the email column on UserEntity
(e.g., `@Column`(unique=true) / migration) and enforce a canonicalization policy
(trim + lowercase) everywhere: normalize incoming emails in
PasswordLoginController during registration, normalize auth lookup parameters in
LoginService and CmschUserDetailsService before calling the repository, and
update repository usage to case-insensitive lookups if needed (e.g., use
users.findByEmailIgnoreCase(...) or ensure normalized values are
stored/queried). Also consider adding a PrePersist/PreUpdate hook on UserEntity
to ensure stored emails are always canonicalized and add a migration to dedupe
existing differing-case emails before applying the unique constraint.
| export const parseTopMessage = (topMessage: string) => { | ||
| const splits = topMessage.split(/(\[\[.*?\]\])/g) | ||
| const coloredComponents = splits.map((partial) => { | ||
| const coloredComponents = splits.map((partial, idx) => { | ||
| if (partial.match(/(\[\[.*?\]\])/)) { | ||
| const coloredText = partial.substring(2, partial.length - 2) | ||
| const color = useBrandColor() | ||
| return ( | ||
| <chakra.span id={coloredText} color={useBrandColor(400, 400)}> | ||
| <span key={idx} id={coloredText} style={{ color }}> | ||
| {coloredText} | ||
| </chakra.span> | ||
| </span> | ||
| ) | ||
| } else { | ||
| return <span>{partial}</span> | ||
| return <span key={idx}>{partial}</span> | ||
| } | ||
| }) | ||
| return <>{coloredComponents}</> |
There was a problem hiding this comment.
Invalid hook call inside a non-component function and conditional block.
useBrandColor() is a React hook being called inside a .map() callback and within a conditional block at line 8. This violates React's Rules of Hooks—hooks must be called at the top level of React function components, not inside loops, conditions, or nested functions.
Additionally, parseTopMessage is a regular function (not a React component), so hooks cannot be used within it at all.
Since useBrandColor() simply returns 'var(--primary)' (a static CSS variable reference), you can inline the value directly.
🐛 Proposed fix: inline the CSS variable
-import { useBrandColor } from '@/util/core-functions.util.ts'
-
export const parseTopMessage = (topMessage: string) => {
const splits = topMessage.split(/(\[\[.*?\]\])/g)
const coloredComponents = splits.map((partial, idx) => {
if (partial.match(/(\[\[.*?\]\])/)) {
const coloredText = partial.substring(2, partial.length - 2)
- const color = useBrandColor()
return (
- <span key={idx} id={coloredText} style={{ color }}>
+ <span key={idx} id={coloredText} style={{ color: 'var(--primary)' }}>
{coloredText}
</span>
)
} else {
return <span key={idx}>{partial}</span>
}
})
return <>{coloredComponents}</>
}🧰 Tools
🪛 Biome (2.4.4)
[error] 8-8: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
(lint/correctness/useHookAtTopLevel)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/pages/countdown/countdown.util.tsx` around lines 3 - 18,
parseTopMessage illegally calls the React hook useBrandColor() inside the .map()
callback (and inside a conditional) which violates the Rules of Hooks; fix by
removing the hook call from parseTopMessage and using a plain CSS variable
string instead (e.g., const color = 'var(--primary)') defined once at the top of
parseTopMessage (or accept color as an argument to parseTopMessage) and then use
that color variable in the span render for the coloredText; update references to
useBrandColor() in parseTopMessage to this plain value so no hooks are invoked
inside the function.
| } catch (error) { | ||
| console.error(error) | ||
| reportError(error) | ||
| toast({ title: 'Hiba', description: 'Hálózati hiba történt', variant: 'destructive' }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check all reportError usages in the codebase
echo "=== All reportError usages ==="
rg -n "reportError" --type=ts --type=tsx -g '!node_modules' | head -40
echo -e "\n=== Checking resetPassword.page.tsx file ==="
fd "resetPassword.page.tsx" --type f | xargs -I {} sh -c 'echo "File: {}"; head -60 "{}"'Repository: kir-dev/cmsch
Length of output: 2444
🏁 Script executed:
#!/bin/bash
# Search for reportError more broadly
echo "=== Searching for reportError definitions/exports ==="
rg "reportError" -B 2 -A 2 | head -50
echo -e "\n=== Searching specifically in utility and service files ==="
rg "reportError" frontend/src/util/ frontend/src/services/ 2>/dev/null || echo "No matches in util/services"
echo -e "\n=== Checking if it's a window global ==="
rg "window\.reportError"
echo -e "\n=== Full content of resetPassword.page.tsx to verify imports ==="
cat frontend/src/pages/login/resetPassword.page.tsx | head -70Repository: kir-dev/cmsch
Length of output: 5335
🏁 Script executed:
#!/bin/bash
# Get more context about reportError in index.html
grep -n "reportError\|processAndReportError" frontend/index.html -B 5 -A 10Repository: kir-dev/cmsch
Length of output: 1483
Use processAndReportError(error) instead of reportError(error).
reportError is a global function defined in frontend/index.html that expects parameters (message, stack), but the code passes an error object. The correct global function for error objects is processAndReportError(error), which is already defined in the same index.html and properly extracts message and stack from the error.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/pages/login/resetPassword.page.tsx` around lines 48 - 51, In the
catch block that currently calls reportError(error) (in the reset password flow
within resetPassword.page.tsx), replace the reportError call with
processAndReportError(error) so the global error helper receives an Error object
and extracts message/stack correctly; keep console.error(error) and the toast
unchanged.
Summary by CodeRabbit
New Features
Improvements
Documentation
Chores