Skip to content

fix: migrate event mutations to generated hooks#728

Merged
kamilwronka merged 2 commits intodevelopfrom
feature/events-orval-api-gen
Apr 12, 2026
Merged

fix: migrate event mutations to generated hooks#728
kamilwronka merged 2 commits intodevelopfrom
feature/events-orval-api-gen

Conversation

@kamilwronka
Copy link
Copy Markdown
Contributor

@kamilwronka kamilwronka commented Apr 12, 2026

Summary by CodeRabbit

  • Refactor

    • Improved cache invalidation and query refreshes so event lists, details, maps, kills and rankings update more reliably across the app.
    • Standardized API/mutation payload shapes and mutation wiring, leading to more consistent create/update/delete flows and correct loading/disabled states in event dialogs and controls.
  • Documentation

    • Added a localized short label for points ("pkt") to translations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 079037ea-e609-4187-b95c-e109d94a525a

📥 Commits

Reviewing files that changed from the base of the PR and between a748c18 and 6ddeed3.

📒 Files selected for processing (2)
  • apps/api/src/events/services/event-catalog.service.ts
  • apps/docs/next-env.d.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/docs/next-env.d.ts

📝 Walkthrough

Walkthrough

Large refactor replacing many custom React Query mutation hooks with generated API endpoint hooks and Zod-backed DTOs; mutation payloads now use { pathParams, data }; added centralized query invalidation utilities and migrated query keys to generated factories. Frontend cache invalidation was wired throughout.

Changes

Cohort / File(s) Summary
API DTOs & Controllers
apps/api/src/events/dto/event-response.dto.ts, apps/api/src/events/events-assignment.controller.ts, apps/api/src/events/events-ranking.controller.ts
Added EventMapResponseDto (createZodDto) and switched addMap success response to @ZodResponse with explicit type. Converted a type-only import to a value import in ranking controller.
Prisma include projection
apps/api/src/events/services/event-catalog.service.ts
Changed eventMap.assignedMembers include from true to a selective select: memberSelectWithTopRole.
Barrel & Hook deletions
apps/web/src/features/guild/events/hooks/mutations/index.ts, removed files under .../hooks/mutations/ (use-event-mutations.ts, use-create-event.ts, use-delete-event.ts, use-assign-member.ts, use-location-mutations.ts, use-event-participation-confirmation.ts, use-respawn-window.ts, use-update-points.ts, use-update-event-settings.ts)
Removed legacy custom mutation hooks and their re-exports; retained/updated only use-toggle-event-pin.
Generated endpoint hooks (mutations)
apps/web/src/features/guild/events/* (event-create-dialog.tsx, event-detail.tsx, event-edit-.tsx, hero-manage-dialog.tsx, map-manage-dialog.tsx, kills/, ranking/*, hero-detail.tsx, event-participation-confirmation-dialog.tsx, events.tsx)
Replaced custom hooks with generated controller hooks (e.g., useUpdateEvent, useEventsAssignmentController*, useEventsRankingController*); updated all mutation calls to { pathParams, data } shape and added useQueryClient-based invalidation on success.
Cache invalidation utilities
apps/web/src/features/guild/events/hooks/mutations/invalidate-event-queries.ts, invalidate-kill-queries.ts, invalidate-map-queries.ts, invalidate-ranking-queries.ts, invalidate-respawn-queries.ts
Added/rewrote centralized invalidation helpers that use generated query-key factories and predicate-based query matching instead of old queryKeys usage.
Hook query key migrations
apps/web/src/features/guild/events/hooks/queries/*.ts, hooks/socket/use-event-socket.ts
Replaced local queryKeys keys with generated get*QueryKey factories and consolidated base params for limit/heroId; socket handler now delegates ranking invalidation to helper.
Optimistic update / settings
apps/web/src/features/guild/events/hooks/mutations/use-toggle-event-pin.ts
Reworked to use generated useEventsSettingsControllerUpdateSettings with optimistic cache update, proper cancellation/rollback, and { pathParams, data } payload shape.
UI text / translations
apps/web/src/i18n/translations/events.json, apps/web/src/features/guild/events/event-member-kills-page.tsx
Added event.common.pointsShort: "pkt" and adjusted translation call to pass explicit default parameter.
Type reference
apps/docs/next-env.d.ts
Updated Next.js route types path from ./.next/dev/types/routes.d.ts to ./.next/types/routes.d.ts.

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

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through hooks and changed their tracks,
PathParams packed tidy in data-packed sacks,
Queries I nudged to forget and then fetch,
Zod keeps responses neat—no more a mess,
A rabbit’s small cheer for a cleaner cache!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: migrate event mutations to generated hooks' accurately reflects the main objective of the changeset, which involves replacing legacy custom mutation hooks with generated API controller hooks across the events module.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/events-orval-api-gen

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

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 | 🟡 Minor

Guard the mutation when guildId or eventId is missing.

These props are optional, but the new payload still falls back to empty strings. If canEdit is 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 never cast masks an API type mismatch: request type forbids null but response types allow it.

The generated UpdateEventDto interface declares endsAt?: string (optional but not nullable), yet the response DTOs declare endsAt: string | null (explicitly nullable). Code at lines 103, and in event-detail.tsx line 402, attempts to send null to clear the end date, forcing it through with null as never. Fix the generated type definition to allow endsAt?: string | null in 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 | 🟠 Major

Guard guildId and eventId before 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 existing killId guard 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.

handleLoadTemplate fans out addMap and sometimes assignMapToLocation for 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 after Promise.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.pathParamsinvalidate*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

📥 Commits

Reviewing files that changed from the base of the PR and between 648a2ab and a748c18.

⛔ Files ignored due to path filters (5)
  • apps/web/src/lib/api/generated/main/events/events.ts is excluded by !**/generated/**
  • apps/web/src/lib/api/generated/main/model/events-assignment-controller-add-map201.ts is excluded by !**/generated/**
  • apps/web/src/lib/api/generated/main/model/events-ranking-controller-update-kill-point-body.ts is excluded by !**/generated/**
  • apps/web/src/lib/api/generated/main/model/index.ts is excluded by !**/generated/**
  • apps/web/src/lib/api/generated/main/model/update-ranking-points-body.ts is excluded by !**/generated/**
📒 Files selected for processing (40)
  • apps/api/openapi.yaml
  • apps/api/src/events/dto/event-response.dto.ts
  • apps/api/src/events/events-assignment.controller.ts
  • apps/api/src/events/events-ranking.controller.ts
  • apps/web/src/features/guild/events/components/dialogs/event-create-dialog.tsx
  • apps/web/src/features/guild/events/components/dialogs/event-participation-confirmation-dialog.tsx
  • apps/web/src/features/guild/events/components/dialogs/hero-manage-dialog.tsx
  • apps/web/src/features/guild/events/components/dialogs/map-manage-dialog.tsx
  • apps/web/src/features/guild/events/components/kills/kill-participants-card.tsx
  • apps/web/src/features/guild/events/components/ranking/event-ranking-table.tsx
  • apps/web/src/features/guild/events/event-detail.tsx
  • apps/web/src/features/guild/events/event-edit-rulebook-page.tsx
  • apps/web/src/features/guild/events/event-edit-scoring-page.tsx
  • apps/web/src/features/guild/events/event-edit-settings-page.tsx
  • apps/web/src/features/guild/events/event-member-kills-page.tsx
  • apps/web/src/features/guild/events/events.tsx
  • apps/web/src/features/guild/events/hero-detail.tsx
  • apps/web/src/features/guild/events/hooks/mutations/index.ts
  • apps/web/src/features/guild/events/hooks/mutations/invalidate-event-queries.ts
  • apps/web/src/features/guild/events/hooks/mutations/invalidate-kill-queries.ts
  • apps/web/src/features/guild/events/hooks/mutations/invalidate-map-queries.ts
  • apps/web/src/features/guild/events/hooks/mutations/invalidate-ranking-queries.ts
  • apps/web/src/features/guild/events/hooks/mutations/invalidate-respawn-queries.ts
  • apps/web/src/features/guild/events/hooks/mutations/use-assign-member.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/use-respawn-window.ts
  • apps/web/src/features/guild/events/hooks/mutations/use-toggle-event-pin.ts
  • apps/web/src/features/guild/events/hooks/mutations/use-update-event-settings.ts
  • apps/web/src/features/guild/events/hooks/mutations/use-update-points.ts
  • apps/web/src/features/guild/events/hooks/queries/use-event-kill-history.ts
  • apps/web/src/features/guild/events/hooks/queries/use-event-member-kill-history.ts
  • apps/web/src/features/guild/events/hooks/queries/use-kill-detail.ts
  • apps/web/src/features/guild/events/hooks/queries/use-map-coverage-timer.ts
  • apps/web/src/features/guild/events/hooks/queries/use-recent-hero-kills.ts
  • apps/web/src/features/guild/events/hooks/socket/use-event-socket.ts
  • apps/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

Comment thread apps/api/src/events/events-assignment.controller.ts
Comment on lines 111 to +120
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,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +88 to +126
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,
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +75 to +80
pathParams: routeParams,
data: {
rulebookMarkdown:
normalizedRulebookMarkdown.length > 0
? normalizedRulebookMarkdown
: (null as never),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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

Comment on lines +134 to +149
const recalculatePoints = useRecalculateEventPoints({
mutation: {
onSuccess: () => {
invalidateEventDetailQueries(
queryClient,
routeParams.guildId,
routeParams.eventId,
);
invalidateKillQueries(
queryClient,
routeParams.guildId,
routeParams.eventId,
);
},
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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")}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
{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.

Comment on lines 95 to +96
const { guildId, eventId, heroId } = useParams({ strict: false });
const queryClient = useQueryClient();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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

Comment on lines +18 to +47
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/")
);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +230 to +234
"event": {
"common": {
"pointsShort": "pkt"
}
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
"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.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 12, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@kamilwronka kamilwronka merged commit 64b8c05 into develop Apr 12, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant