fix: migrate event mutations to generated hooks#728
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughLarge refactor replacing many custom React Query mutation hooks with generated API endpoint hooks and Zod-backed DTOs; mutation payloads now use Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Client UI
participant Hook as Generated Hook
participant API as API Server
participant DB as Database
participant QC as QueryClient
UI->>Hook: mutate({ pathParams, data })
Hook->>API: HTTP request (controller endpoint)
API->>DB: read/write (Prisma)
DB-->>API: result
API-->>Hook: response (Zod-validated)
Hook-->>UI: mutation resolved
Hook->>QC: invalidateQueries(get*QueryKey / predicate)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/web/src/features/guild/events/components/ranking/event-ranking-table.tsx (1)
300-316:⚠️ Potential issue | 🟡 MinorGuard the mutation when
guildIdoreventIdis missing.These props are optional, but the new payload still falls back to empty strings. If
canEditis ever enabled without both IDs, the dialog submits to an invalid route instead of failing fast.Suggested fix
const handleEditPoints = async ( rankingId: string, pointsDelta: number, comment?: string, ) => { + if (!guildId || !eventId) { + toast.error(t("events.points.editError")); + return; + } + try { await updateRankingPoints.mutateAsync({ pathParams: { - guildId: guildId ?? "", - eventId: eventId ?? "", + guildId, + eventId, rankingId, }, data: {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/guild/events/components/ranking/event-ranking-table.tsx` around lines 300 - 316, handleEditPoints currently calls updateRankingPoints.mutateAsync using empty-string fallbacks for guildId/eventId; add a guard at the start of handleEditPoints to verify guildId and eventId are present (truthy) and bail out early (return or throw) if either is missing so the mutation is never invoked with invalid route params; update any callers/UI (e.g., canEdit usage) to respect this guard or disable the edit control when IDs are absent.apps/web/src/features/guild/events/event-edit-settings-page.tsx (1)
99-125:⚠️ Potential issue | 🟠 Major
null as nevercast masks an API type mismatch: request type forbids null but response types allow it.The generated
UpdateEventDtointerface declaresendsAt?: string(optional but not nullable), yet the response DTOs declareendsAt: string | null(explicitly nullable). Code at lines 103, and inevent-detail.tsxline 402, attempts to sendnullto clear the end date, forcing it through withnull as never. Fix the generated type definition to allowendsAt?: string | nullin the request payload instead of suppressing it at each call site.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/guild/events/event-edit-settings-page.tsx` around lines 99 - 125, The request DTO incorrectly forbids null for endsAt so callers use unsafe casts like null as never; update the generated UpdateEventDto so endsAt is nullable (endsAt?: string | null) in the client types, regenerate the API client/types, then remove the null as never casts in call sites (e.g., updateEvent.mutateAsync in event-edit-settings-page.tsx and the usage in event-detail.tsx) so they can pass null to clear the end date safely; ensure the generator/OpenAPI schema marks endsAt as nullable or update the type mapping that generates UpdateEventDto accordingly.apps/web/src/features/guild/events/components/kills/kill-participants-card.tsx (1)
426-443:⚠️ Potential issue | 🟠 MajorGuard
guildIdandeventIdbefore firing the mutation.Line 433 now falls back to
""for required path params. If either prop is missing, this will issue a PATCH to an invalid URL instead of bailing out like the existingkillIdguard does.🐛 Proposed fix
const handleEditPoints = async ( killPointId: string, pointsDelta: number, comment?: string, ) => { - if (!killId) return; + if (!guildId || !eventId || !killId) { + return; + } try { await updateKillPoint.mutateAsync({ pathParams: { - guildId: guildId ?? "", - eventId: eventId ?? "", + guildId, + eventId, killId, killPointId, },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/guild/events/components/kills/kill-participants-card.tsx` around lines 426 - 443, The handler handleEditPoints currently only guards killId and falls back to empty strings for guildId/eventId when calling updateKillPoint.mutateAsync, which can produce invalid PATCHes; update the function to also guard for guildId and eventId (return early if either is missing) before invoking updateKillPoint.mutateAsync so the mutation is never called with empty path params (refer to guildId, eventId, killId, and updateKillPoint.mutateAsync to locate the change).
🧹 Nitpick comments (2)
apps/web/src/features/guild/events/components/dialogs/map-manage-dialog.tsx (1)
263-286: Batch template loads are invalidating the same queries once per map.
handleLoadTemplatefans outaddMapand sometimesassignMapToLocationfor every template entry, and both mutation instances invalidate the map-structure cache on every success. Larger templates will trigger a burst of identical refetches. Prefer a batch path that invalidates once afterPromise.allSettled.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/guild/events/components/dialogs/map-manage-dialog.tsx` around lines 263 - 286, handleLoadTemplate currently fans out addMap and assignMapToLocation for each map which causes the map-structure cache to be invalidated repeatedly; change the flow so each mutation does not trigger its individual invalidation (remove or bypass per-mutation onSuccess invalidation in the addMap and assignMapToLocation mutation definitions or call their mutateAsync without triggering success handlers) and instead perform the single cache refresh after the batch: run Promise.allSettled over mapsToAdd as you do, collect results, then call queryClient.invalidateQueries for the map-structure (the single cache key your mutations were invalidating) once after the Promise.allSettled completes; update handleLoadTemplate, addMap, assignMapToLocation and any per-mutation onSuccess logic to locate and implement this single-invalidation approach.apps/web/src/features/guild/events/hero-detail.tsx (1)
106-183: Extract the repeated mutation invalidation callbacks.The same
variables.pathParams→invalidate*Queries(...)plumbing is duplicated across six hooks. A tiny helper would make this migration easier to maintain when the generated hook contracts change again.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/guild/events/hero-detail.tsx` around lines 106 - 183, Extract the repeated onSuccess invalidation logic into a small helper that accepts (queryClient) and returns a function taking the mutation variables (or returns an onSuccess handler) so all six hooks can reuse it; for example create a helper like buildInvalidateHandlers(queryClient) that exposes mapInvalidate(variables) and respawnInvalidate(variables, includeKills?) or a single onSuccessFrom(variant) factory, then replace the inline onSuccess bodies in assignMember, unassignMember, selfAssignMember, selfUnassignMember to call the mapInvalidate path (which calls invalidateMapQueries with variables.pathParams.guildId/eventId/mapId), and replace the onSuccess in closeRespawnWindow and openRespawnWindow to call respawnInvalidate (which calls invalidateRespawnQueries and, for closeRespawnWindow, also calls invalidateKillQueries). Ensure the helper references queryClient and the same invalidateMapQueries/invalidateRespawnQueries/invalidateKillQueries functions so behavior remains identical.
🤖 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/api/src/events/events-assignment.controller.ts`:
- Around line 243-247: The addMap service currently returns assignedMembers
without their nested roles, which violates the EventMapResponseDto expected by
the `@ZodResponse` decorator; update the Prisma query used in the addMap
handler/service (the function named addMap) to include roles in the
assignedMembers relation (i.e., add an include: { assignedMembers: { include: {
roles: true, /* keep other fields */ } }, ... } ) so that
assignedMembers[].roles is returned as an array and the response matches
EventMapResponseDto.
In
`@apps/web/src/features/guild/events/components/dialogs/hero-manage-dialog.tsx`:
- Around line 111-120: The code treating npcIdNum as truthy drops valid zero
IDs; change the condition when building the payload for addHero.mutateAsync to
include npcId when npcIdNum !== undefined (not truthiness), i.e. compute
npcIdNum from data.npcId and spread {(npcId: npcIdNum)} only when npcIdNum !==
undefined so that 0 is preserved; update the block around npcIdNum and
addHero.mutateAsync to use that !== undefined check.
In `@apps/web/src/features/guild/events/components/dialogs/map-manage-dialog.tsx`:
- Around line 88-126: The dialog is reading from the hero prop and
localLocations (captured from EventDetail via selectedHero) so calling
invalidateMapQueries in the mutation onSuccess handlers (addMap, deleteMap,
createLocation, updateLocation, deleteLocation, reorderLocations,
assignMapToLocation) only refetches in the background and won’t update the open
dialog; either switch the dialog to read from a live query (so query results
update automatically) or update the dialog’s local state directly in each
mutation onSuccess callback—e.g., apply the returned map/location changes to
localLocations/hero within those onSuccess handlers so the UI updates
immediately when
addMap/deleteMap/createLocation/updateLocation/deleteLocation/reorderLocations/assignMapToLocation
succeed.
In `@apps/web/src/features/guild/events/event-edit-rulebook-page.tsx`:
- Around line 75-80: The patch is sending (null as never) for rulebookMarkdown
which violates UpdateEventDto (rulebookMarkdown?: string); remove the null cast
and instead omit the property when normalizedRulebookMarkdown is empty or send
an empty string; implement the omission by conditionally spreading the field
into the data object using normalizedRulebookMarkdown to gate inclusion (or set
rulebookMarkdown: "" if you prefer), updating the data payload construction
where rulebookMarkdown is currently set.
In `@apps/web/src/features/guild/events/event-edit-scoring-page.tsx`:
- Around line 134-149: The success handler for recalculatePoints currently
invalidates event detail and kill queries but misses invalidating the ranking
cache used by useListEventRanking; update the onSuccess block in the
recalculatePoints mutation (alongside the existing calls to
invalidateEventDetailQueries and invalidateKillQueries) to also call the ranking
invalidation helper (e.g., invalidateEventRankingQueries or the project’s
equivalent) passing queryClient, routeParams.guildId, and routeParams.eventId so
the ranking preview in event-detail.tsx is refreshed.
In `@apps/web/src/features/guild/events/event-member-kills-page.tsx`:
- Line 384: The translation call in event-member-kills-page.tsx uses a hardcoded
fallback string ("pkt") — remove the hardcoded second argument and call
t("events.common.pointsShort") so the UI always uses i18n keys; ensure the
translation resources (e.g., locale JSONs) include the events.common.pointsShort
entry for all supported languages and update them if missing.
In `@apps/web/src/features/guild/events/hero-detail.tsx`:
- Around line 95-96: This component reads route params with useParams({ strict:
false }) but then uses guildId ?? "" when invoking generated mutations which can
call endpoints like /guilds//... if params are not yet available; update the
component to guard guildId before calling any generated mutation hook triggers
(and before rendering UI that invokes them): either return early/render a
loading state when guildId is falsy (e.g., if (!guildId) return
null/placeholder) or ensure each mutation call is only executed when guildId is
present (check !!guildId before calling mutate or use the mutation/query option
enabled: !!guildId where applicable); apply this guard for all handlers that
reference guildId (the useParams destructuring and any places that call the
generated mutation trigger functions in this file).
In
`@apps/web/src/features/guild/events/hooks/mutations/invalidate-kill-queries.ts`:
- Around line 18-47: isEventKillQuery currently catches the "/kills" roots but
not detail keys like "/kills/{killId}", leaving detail views stale; update
isEventKillQuery (and its existing checks using getEventKillsPath,
getEventMembersPathPrefix, getEventHeroesPathPrefix) to also return true when
the path starts with the event kills root plus a slash (i.e.
path.startsWith(getEventKillsPath(guildId, eventId) + "/")), and likewise treat
member-level kill details (e.g. paths under getEventMembersPathPrefix that
include "/kills/") as matches (add a condition similar to the final heroes check
for members). Ensure you only modify isEventKillQuery and use the existing
helper functions named above.
In `@apps/web/src/i18n/translations/events.json`:
- Around line 230-234: The translations JSON uses the wrong top-level namespace:
it defines "event.common.pointsShort" but the consumer (events/common lookup in
events-member-kills-page.tsx) expects "events.common.pointsShort"; update the
JSON so the key lives under "events.common.pointsShort" (or merge the
pointsShort entry into the existing "events" -> "common" block) so the lookup in
events.common.pointsShort resolves correctly.
---
Outside diff comments:
In
`@apps/web/src/features/guild/events/components/kills/kill-participants-card.tsx`:
- Around line 426-443: The handler handleEditPoints currently only guards killId
and falls back to empty strings for guildId/eventId when calling
updateKillPoint.mutateAsync, which can produce invalid PATCHes; update the
function to also guard for guildId and eventId (return early if either is
missing) before invoking updateKillPoint.mutateAsync so the mutation is never
called with empty path params (refer to guildId, eventId, killId, and
updateKillPoint.mutateAsync to locate the change).
In
`@apps/web/src/features/guild/events/components/ranking/event-ranking-table.tsx`:
- Around line 300-316: handleEditPoints currently calls
updateRankingPoints.mutateAsync using empty-string fallbacks for
guildId/eventId; add a guard at the start of handleEditPoints to verify guildId
and eventId are present (truthy) and bail out early (return or throw) if either
is missing so the mutation is never invoked with invalid route params; update
any callers/UI (e.g., canEdit usage) to respect this guard or disable the edit
control when IDs are absent.
In `@apps/web/src/features/guild/events/event-edit-settings-page.tsx`:
- Around line 99-125: The request DTO incorrectly forbids null for endsAt so
callers use unsafe casts like null as never; update the generated UpdateEventDto
so endsAt is nullable (endsAt?: string | null) in the client types, regenerate
the API client/types, then remove the null as never casts in call sites (e.g.,
updateEvent.mutateAsync in event-edit-settings-page.tsx and the usage in
event-detail.tsx) so they can pass null to clear the end date safely; ensure the
generator/OpenAPI schema marks endsAt as nullable or update the type mapping
that generates UpdateEventDto accordingly.
---
Nitpick comments:
In `@apps/web/src/features/guild/events/components/dialogs/map-manage-dialog.tsx`:
- Around line 263-286: handleLoadTemplate currently fans out addMap and
assignMapToLocation for each map which causes the map-structure cache to be
invalidated repeatedly; change the flow so each mutation does not trigger its
individual invalidation (remove or bypass per-mutation onSuccess invalidation in
the addMap and assignMapToLocation mutation definitions or call their
mutateAsync without triggering success handlers) and instead perform the single
cache refresh after the batch: run Promise.allSettled over mapsToAdd as you do,
collect results, then call queryClient.invalidateQueries for the map-structure
(the single cache key your mutations were invalidating) once after the
Promise.allSettled completes; update handleLoadTemplate, addMap,
assignMapToLocation and any per-mutation onSuccess logic to locate and implement
this single-invalidation approach.
In `@apps/web/src/features/guild/events/hero-detail.tsx`:
- Around line 106-183: Extract the repeated onSuccess invalidation logic into a
small helper that accepts (queryClient) and returns a function taking the
mutation variables (or returns an onSuccess handler) so all six hooks can reuse
it; for example create a helper like buildInvalidateHandlers(queryClient) that
exposes mapInvalidate(variables) and respawnInvalidate(variables, includeKills?)
or a single onSuccessFrom(variant) factory, then replace the inline onSuccess
bodies in assignMember, unassignMember, selfAssignMember, selfUnassignMember to
call the mapInvalidate path (which calls invalidateMapQueries with
variables.pathParams.guildId/eventId/mapId), and replace the onSuccess in
closeRespawnWindow and openRespawnWindow to call respawnInvalidate (which calls
invalidateRespawnQueries and, for closeRespawnWindow, also calls
invalidateKillQueries). Ensure the helper references queryClient and the same
invalidateMapQueries/invalidateRespawnQueries/invalidateKillQueries functions so
behavior remains identical.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: fcedaf29-b5db-429d-9d6f-4dcd30417ea7
⛔ Files ignored due to path filters (5)
apps/web/src/lib/api/generated/main/events/events.tsis excluded by!**/generated/**apps/web/src/lib/api/generated/main/model/events-assignment-controller-add-map201.tsis excluded by!**/generated/**apps/web/src/lib/api/generated/main/model/events-ranking-controller-update-kill-point-body.tsis excluded by!**/generated/**apps/web/src/lib/api/generated/main/model/index.tsis excluded by!**/generated/**apps/web/src/lib/api/generated/main/model/update-ranking-points-body.tsis excluded by!**/generated/**
📒 Files selected for processing (40)
apps/api/openapi.yamlapps/api/src/events/dto/event-response.dto.tsapps/api/src/events/events-assignment.controller.tsapps/api/src/events/events-ranking.controller.tsapps/web/src/features/guild/events/components/dialogs/event-create-dialog.tsxapps/web/src/features/guild/events/components/dialogs/event-participation-confirmation-dialog.tsxapps/web/src/features/guild/events/components/dialogs/hero-manage-dialog.tsxapps/web/src/features/guild/events/components/dialogs/map-manage-dialog.tsxapps/web/src/features/guild/events/components/kills/kill-participants-card.tsxapps/web/src/features/guild/events/components/ranking/event-ranking-table.tsxapps/web/src/features/guild/events/event-detail.tsxapps/web/src/features/guild/events/event-edit-rulebook-page.tsxapps/web/src/features/guild/events/event-edit-scoring-page.tsxapps/web/src/features/guild/events/event-edit-settings-page.tsxapps/web/src/features/guild/events/event-member-kills-page.tsxapps/web/src/features/guild/events/events.tsxapps/web/src/features/guild/events/hero-detail.tsxapps/web/src/features/guild/events/hooks/mutations/index.tsapps/web/src/features/guild/events/hooks/mutations/invalidate-event-queries.tsapps/web/src/features/guild/events/hooks/mutations/invalidate-kill-queries.tsapps/web/src/features/guild/events/hooks/mutations/invalidate-map-queries.tsapps/web/src/features/guild/events/hooks/mutations/invalidate-ranking-queries.tsapps/web/src/features/guild/events/hooks/mutations/invalidate-respawn-queries.tsapps/web/src/features/guild/events/hooks/mutations/use-assign-member.tsapps/web/src/features/guild/events/hooks/mutations/use-create-event.tsapps/web/src/features/guild/events/hooks/mutations/use-delete-event.tsapps/web/src/features/guild/events/hooks/mutations/use-event-mutations.tsapps/web/src/features/guild/events/hooks/mutations/use-event-participation-confirmation.tsapps/web/src/features/guild/events/hooks/mutations/use-location-mutations.tsapps/web/src/features/guild/events/hooks/mutations/use-respawn-window.tsapps/web/src/features/guild/events/hooks/mutations/use-toggle-event-pin.tsapps/web/src/features/guild/events/hooks/mutations/use-update-event-settings.tsapps/web/src/features/guild/events/hooks/mutations/use-update-points.tsapps/web/src/features/guild/events/hooks/queries/use-event-kill-history.tsapps/web/src/features/guild/events/hooks/queries/use-event-member-kill-history.tsapps/web/src/features/guild/events/hooks/queries/use-kill-detail.tsapps/web/src/features/guild/events/hooks/queries/use-map-coverage-timer.tsapps/web/src/features/guild/events/hooks/queries/use-recent-hero-kills.tsapps/web/src/features/guild/events/hooks/socket/use-event-socket.tsapps/web/src/i18n/translations/events.json
💤 Files with no reviewable changes (10)
- apps/web/src/features/guild/events/hooks/mutations/use-update-event-settings.ts
- apps/web/src/features/guild/events/hooks/mutations/use-create-event.ts
- apps/web/src/features/guild/events/hooks/mutations/use-delete-event.ts
- apps/web/src/features/guild/events/hooks/mutations/use-event-mutations.ts
- apps/web/src/features/guild/events/hooks/mutations/use-event-participation-confirmation.ts
- apps/web/src/features/guild/events/hooks/mutations/use-location-mutations.ts
- apps/web/src/features/guild/events/hooks/mutations/index.ts
- apps/web/src/features/guild/events/hooks/mutations/use-respawn-window.ts
- apps/web/src/features/guild/events/hooks/mutations/use-assign-member.ts
- apps/web/src/features/guild/events/hooks/mutations/use-update-points.ts
| const npcIdNum = data.npcId ? Number(data.npcId) : undefined; | ||
| await addHero.mutateAsync({ | ||
| ...(npcIdNum && { npcId: npcIdNum }), | ||
| npcName: data.npcName, | ||
| pathParams: { | ||
| guildId, | ||
| eventId, | ||
| }, | ||
| data: { | ||
| ...(npcIdNum ? { npcId: npcIdNum } : {}), | ||
| npcName: data.npcName, | ||
| }, |
There was a problem hiding this comment.
Don't treat npcId = 0 as "missing".
The create path uses a truthy check, so 0 gets dropped even though the update path accepts it via !== undefined.
🐛 Proposed fix
await addHero.mutateAsync({
pathParams: {
guildId,
eventId,
},
data: {
- ...(npcIdNum ? { npcId: npcIdNum } : {}),
+ ...(npcIdNum !== undefined ? { npcId: npcIdNum } : {}),
npcName: data.npcName,
},
});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/guild/events/components/dialogs/hero-manage-dialog.tsx`
around lines 111 - 120, The code treating npcIdNum as truthy drops valid zero
IDs; change the condition when building the payload for addHero.mutateAsync to
include npcId when npcIdNum !== undefined (not truthiness), i.e. compute
npcIdNum from data.npcId and spread {(npcId: npcIdNum)} only when npcIdNum !==
undefined so that 0 is preserved; update the block around npcIdNum and
addHero.mutateAsync to use that !== undefined check.
| const queryClient = useQueryClient(); | ||
| const invalidateMapQueries = () => { | ||
| invalidateEventMapStructureQueries(queryClient, guildId, eventId); | ||
| }; | ||
| const addMap = useEventsAssignmentControllerAddMap({ | ||
| mutation: { | ||
| onSuccess: invalidateMapQueries, | ||
| }, | ||
| }); | ||
| const deleteMap = useEventsAssignmentControllerDeleteMap({ | ||
| mutation: { | ||
| onSuccess: invalidateMapQueries, | ||
| }, | ||
| }); | ||
| const createLocation = useEventsAssignmentControllerCreateLocation({ | ||
| mutation: { | ||
| onSuccess: invalidateMapQueries, | ||
| }, | ||
| }); | ||
| const updateLocation = useEventsAssignmentControllerUpdateLocation({ | ||
| mutation: { | ||
| onSuccess: invalidateMapQueries, | ||
| }, | ||
| }); | ||
| const deleteLocation = useEventsAssignmentControllerDeleteLocation({ | ||
| mutation: { | ||
| onSuccess: invalidateMapQueries, | ||
| }, | ||
| }); | ||
| const reorderLocations = useEventsAssignmentControllerReorderLocations({ | ||
| mutation: { | ||
| onSuccess: invalidateMapQueries, | ||
| }, | ||
| }); | ||
| const assignMapToLocation = useEventsAssignmentControllerAssignMapToLocation({ | ||
| mutation: { | ||
| onSuccess: invalidateMapQueries, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Invalidating the cache here will not refresh the open dialog.
This component renders from the hero prop and localLocations, not from a live query result. Since EventDetail passes selectedHero as captured state, these onSuccess handlers refetch in the background but the dialog can keep showing stale maps/locations until it is reopened. Either source the dialog from live query state or update the local dialog state on each success path.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/guild/events/components/dialogs/map-manage-dialog.tsx`
around lines 88 - 126, The dialog is reading from the hero prop and
localLocations (captured from EventDetail via selectedHero) so calling
invalidateMapQueries in the mutation onSuccess handlers (addMap, deleteMap,
createLocation, updateLocation, deleteLocation, reorderLocations,
assignMapToLocation) only refetches in the background and won’t update the open
dialog; either switch the dialog to read from a live query (so query results
update automatically) or update the dialog’s local state directly in each
mutation onSuccess callback—e.g., apply the returned map/location changes to
localLocations/hero within those onSuccess handlers so the UI updates
immediately when
addMap/deleteMap/createLocation/updateLocation/deleteLocation/reorderLocations/assignMapToLocation
succeed.
| pathParams: routeParams, | ||
| data: { | ||
| rulebookMarkdown: | ||
| normalizedRulebookMarkdown.length > 0 | ||
| ? normalizedRulebookMarkdown | ||
| : (null as never), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== generated UpdateEventDto ==\n'
find . -iname 'update-event-dto.ts' -print -exec sh -c 'echo "--- $1"; sed -n "1,200p" "$1"' sh {} \;
printf '\n== rulebookMarkdown references ==\n'
rg -n -C3 '\brulebookMarkdown\b'Repository: lootlog/monorepo
Length of output: 50373
Avoid null as never; omit the field or send an empty string instead.
The generated UpdateEventDto defines rulebookMarkdown?: string (not nullable). Sending null violates this contract; TypeScript should reject it. The as never cast suppresses that error and hides a real type mismatch. Instead, either omit the field entirely when empty (as done in event-create-dialog.tsx) or send an empty string, both matching the API schema.
Code reference
rulebookMarkdown:
normalizedRulebookMarkdown.length > 0
? normalizedRulebookMarkdown
: (null as never),Change to:
...(normalizedRulebookMarkdown.length > 0 && {
rulebookMarkdown: normalizedRulebookMarkdown,
}),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/guild/events/event-edit-rulebook-page.tsx` around lines
75 - 80, The patch is sending (null as never) for rulebookMarkdown which
violates UpdateEventDto (rulebookMarkdown?: string); remove the null cast and
instead omit the property when normalizedRulebookMarkdown is empty or send an
empty string; implement the omission by conditionally spreading the field into
the data object using normalizedRulebookMarkdown to gate inclusion (or set
rulebookMarkdown: "" if you prefer), updating the data payload construction
where rulebookMarkdown is currently set.
| const recalculatePoints = useRecalculateEventPoints({ | ||
| mutation: { | ||
| onSuccess: () => { | ||
| invalidateEventDetailQueries( | ||
| queryClient, | ||
| routeParams.guildId, | ||
| routeParams.eventId, | ||
| ); | ||
| invalidateKillQueries( | ||
| queryClient, | ||
| routeParams.guildId, | ||
| routeParams.eventId, | ||
| ); | ||
| }, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Recalculating points also needs ranking cache invalidation.
This success handler refreshes event-detail and kill queries, but the recalculation changes the data behind useListEventRanking as well. The ranking preview in event-detail.tsx can stay stale until something else refetches it.
Suggested fix
import { invalidateEventDetailQueries } from "./hooks/mutations/invalidate-event-queries";
import { invalidateKillQueries } from "./hooks/mutations/invalidate-kill-queries";
+import { invalidateRankingQueries } from "./hooks/mutations/invalidate-ranking-queries"; const recalculatePoints = useRecalculateEventPoints({
mutation: {
onSuccess: () => {
invalidateEventDetailQueries(
queryClient,
routeParams.guildId,
routeParams.eventId,
);
invalidateKillQueries(
queryClient,
routeParams.guildId,
routeParams.eventId,
);
+ invalidateRankingQueries(
+ queryClient,
+ routeParams.guildId,
+ routeParams.eventId,
+ );
},
},
});📝 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 recalculatePoints = useRecalculateEventPoints({ | |
| mutation: { | |
| onSuccess: () => { | |
| invalidateEventDetailQueries( | |
| queryClient, | |
| routeParams.guildId, | |
| routeParams.eventId, | |
| ); | |
| invalidateKillQueries( | |
| queryClient, | |
| routeParams.guildId, | |
| routeParams.eventId, | |
| ); | |
| }, | |
| }, | |
| }); | |
| import { invalidateEventDetailQueries } from "./hooks/mutations/invalidate-event-queries"; | |
| import { invalidateKillQueries } from "./hooks/mutations/invalidate-kill-queries"; | |
| import { invalidateRankingQueries } from "./hooks/mutations/invalidate-ranking-queries"; | |
| // ... other code ... | |
| const recalculatePoints = useRecalculateEventPoints({ | |
| mutation: { | |
| onSuccess: () => { | |
| invalidateEventDetailQueries( | |
| queryClient, | |
| routeParams.guildId, | |
| routeParams.eventId, | |
| ); | |
| invalidateKillQueries( | |
| queryClient, | |
| routeParams.guildId, | |
| routeParams.eventId, | |
| ); | |
| invalidateRankingQueries( | |
| queryClient, | |
| routeParams.guildId, | |
| routeParams.eventId, | |
| ); | |
| }, | |
| }, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/guild/events/event-edit-scoring-page.tsx` around lines
134 - 149, The success handler for recalculatePoints currently invalidates event
detail and kill queries but misses invalidating the ranking cache used by
useListEventRanking; update the onSuccess block in the recalculatePoints
mutation (alongside the existing calls to invalidateEventDetailQueries and
invalidateKillQueries) to also call the ranking invalidation helper (e.g.,
invalidateEventRankingQueries or the project’s equivalent) passing queryClient,
routeParams.guildId, and routeParams.eventId so the ranking preview in
event-detail.tsx is refreshed.
| {formatPoints(totalPoints)} | ||
| <span className="ml-1 text-xs font-medium"> | ||
| {t("events.common.pointsShort")} | ||
| {t("events.common.pointsShort", "pkt")} |
There was a problem hiding this comment.
Avoid hardcoded fallback text in frontend translation call
Use the translation key directly and keep "pkt" only in translation resources.
🔧 Suggested fix
- {t("events.common.pointsShort", "pkt")}
+ {t("events.common.pointsShort")}As per coding guidelines, "All static text in frontend apps must use i18n — never hardcode user-facing strings".
📝 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.
| {t("events.common.pointsShort", "pkt")} | |
| {t("events.common.pointsShort")} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/guild/events/event-member-kills-page.tsx` at line 384,
The translation call in event-member-kills-page.tsx uses a hardcoded fallback
string ("pkt") — remove the hardcoded second argument and call
t("events.common.pointsShort") so the UI always uses i18n keys; ensure the
translation resources (e.g., locale JSONs) include the events.common.pointsShort
entry for all supported languages and update them if missing.
| const { guildId, eventId, heroId } = useParams({ strict: false }); | ||
| const queryClient = useQueryClient(); |
There was a problem hiding this comment.
Guard guildId before calling the generated mutations.
This component reads params with strict: false, but several new handlers still fall back to guildId ?? "". If the page renders before params are available, these mutations will hit /guilds//... instead of bailing out.
Suggested fix
const handleSelfAssignClick = async (mapId: string) => {
- if (!eventId) return;
+ if (!guildId || !eventId) return;
@@
const handleSelfUnassignClick = async (mapId: string) => {
- if (!eventId) return;
+ if (!guildId || !eventId) return;
@@
const handleClearAllAssignments = async () => {
- if (!eventId || allMaps.length === 0) return;
+ if (!guildId || !eventId || allMaps.length === 0) return;
@@
}) => {
- if (!eventId || !heroId) return;
+ if (!guildId || !eventId || !heroId) return;
@@
}) => {
- if (!eventId || !heroId) return;
+ if (!guildId || !eventId || !heroId) return;Also applies to: 328-359, 417-479
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/guild/events/hero-detail.tsx` around lines 95 - 96,
This component reads route params with useParams({ strict: false }) but then
uses guildId ?? "" when invoking generated mutations which can call endpoints
like /guilds//... if params are not yet available; update the component to guard
guildId before calling any generated mutation hook triggers (and before
rendering UI that invokes them): either return early/render a loading state when
guildId is falsy (e.g., if (!guildId) return null/placeholder) or ensure each
mutation call is only executed when guildId is present (check !!guildId before
calling mutate or use the mutation/query option enabled: !!guildId where
applicable); apply this guard for all handlers that reference guildId (the
useParams destructuring and any places that call the generated mutation trigger
functions in this file).
| const isEventKillQuery = (query: Query, guildId: string, eventId: string) => { | ||
| const [path] = query.queryKey; | ||
|
|
||
| if (typeof path !== "string") { | ||
| return false; | ||
| } | ||
|
|
||
| if (path === getEventKillsPath(guildId, eventId)) { | ||
| return true; | ||
| } | ||
|
|
||
| if ( | ||
| path.startsWith(getEventMembersPathPrefix(guildId, eventId)) && | ||
| path.endsWith("/kills") | ||
| ) { | ||
| return true; | ||
| } | ||
|
|
||
| if ( | ||
| path.startsWith(getEventHeroesPathPrefix(guildId, eventId)) && | ||
| path.endsWith("/kills") | ||
| ) { | ||
| return true; | ||
| } | ||
|
|
||
| return ( | ||
| path.startsWith(getEventHeroesPathPrefix(guildId, eventId)) && | ||
| path.includes("/kills/") | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Also match event-level kill detail keys.
isEventKillQuery() invalidates the /kills root, but not /kills/{killId}. That can leave kill-detail views stale after point/confirmation mutations.
Suggested fix
const isEventKillQuery = (query: Query, guildId: string, eventId: string) => {
const [path] = query.queryKey;
+ const eventKillsPath = getEventKillsPath(guildId, eventId);
if (typeof path !== "string") {
return false;
}
- if (path === getEventKillsPath(guildId, eventId)) {
+ if (path === eventKillsPath || path.startsWith(`${eventKillsPath}/`)) {
return true;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@apps/web/src/features/guild/events/hooks/mutations/invalidate-kill-queries.ts`
around lines 18 - 47, isEventKillQuery currently catches the "/kills" roots but
not detail keys like "/kills/{killId}", leaving detail views stale; update
isEventKillQuery (and its existing checks using getEventKillsPath,
getEventMembersPathPrefix, getEventHeroesPathPrefix) to also return true when
the path starts with the event kills root plus a slash (i.e.
path.startsWith(getEventKillsPath(guildId, eventId) + "/")), and likewise treat
member-level kill details (e.g. paths under getEventMembersPathPrefix that
include "/kills/") as matches (add a condition similar to the final heroes check
for members). Ensure you only modify isEventKillQuery and use the existing
helper functions named above.
| "event": { | ||
| "common": { | ||
| "pointsShort": "pkt" | ||
| } | ||
| }, |
There was a problem hiding this comment.
Translation key namespace does not match current consumer lookup
apps/web/src/features/guild/events/event-member-kills-page.tsx (Line 384) reads events.common.pointsShort, but this segment adds event.common.pointsShort. That key won’t satisfy the current lookup.
🔧 Suggested fix
- "event": {
- "common": {
- "pointsShort": "pkt"
- }
- },
+ "common": {
+ "pointsShort": "pkt"
+ },If you already have another common block in this JSON, merge pointsShort into that same block to keep one canonical path.
📝 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.
| "event": { | |
| "common": { | |
| "pointsShort": "pkt" | |
| } | |
| }, | |
| "common": { | |
| "pointsShort": "pkt" | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/i18n/translations/events.json` around lines 230 - 234, The
translations JSON uses the wrong top-level namespace: it defines
"event.common.pointsShort" but the consumer (events/common lookup in
events-member-kills-page.tsx) expects "events.common.pointsShort"; update the
JSON so the key lives under "events.common.pointsShort" (or merge the
pointsShort entry into the existing "events" -> "common" block) so the lookup in
events.common.pointsShort resolves correctly.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
lootlog-docs | 6ddeed3 | Commit Preview URL Branch Preview URL |
Apr 12 2026, 03:30 AM |
Summary by CodeRabbit
Refactor
Documentation