Skip to content

feat: add associations management#40

Open
BIA3IA wants to merge 50 commits into
mainfrom
web/associations
Open

feat: add associations management#40
BIA3IA wants to merge 50 commits into
mainfrom
web/associations

Conversation

@BIA3IA

@BIA3IA BIA3IA commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Blocked by #39 and by PoliNetworkOrg/backend#36

lorenzocorallo and others added 30 commits June 4, 2026 19:57
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds an association management feature with a new dashboard page, client components for viewing/editing associations and their links, supporting types/constants, and role-gated server actions. It also adds sidebar navigation data, button/dialog UI additions, a getInitials fix, and config/dependency updates.

Changes

Association Management Feature

Layer / File(s) Summary
Types and constants
src/app/dashboard/(active)/web/associations/types.ts, .../constants.ts
Defines Association, AssociationLinks, AssociationLink, LinkField types and ASSOCIATION_LINK, EMPTY_ASSOCIATION_LINKS, LINK_FIELDS constants.
Server actions
src/server/actions/web.ts
Role-gated createAssociation, editAssociation, editAssociationLinks, deleteAssociation actions built via a shared getAssociationFormData helper, injecting createdBy/modifiedBy and mapping tRPC errors.
Page and list view
.../associations/page.tsx, .../associations-view.tsx
WebAssociationsIndex loads associations via tRPC; AssociationsView manages local draft/edit state, wires create/edit/delete/save-links flows with toasts and router refresh.
Card editing and links dialog
.../card-association.tsx, .../association-links.tsx
CardAssociation renders edit/read-only card UI with logo upload, descriptions, and delete confirmation; AssociationLinksDialog edits link fields with normalization.

Supporting UI Components and Infrastructure

Layer / File(s) Summary
Button variants, delete dialog, and web header
src/components/ui/button.tsx, src/components/delete-dialog.tsx, src/components/web-header.tsx
Adds success/error button variants, explicit svg sizing for icon-sm, a DeleteDialog confirmation component, and a WebHeader component.
Sidebar navigation data
src/components/dashboard-sidebar/data.tsx
Defines DSData navigation structure and derives NAV_MAP via flattenNavigation().
Utility fix and app configuration
src/lib/utils/index.ts, next.config.ts, package.json
Fixes getInitials hyphen handling, adds image remote patterns and server actions body size limit, bumps @polinetwork/backend version.

Sequence Diagram(s)

sequenceDiagram
  participant Page as WebAssociationsIndex
  participant View as AssociationsView
  participant Card as CardAssociation
  participant Dialog as AssociationLinksDialog
  participant Action as Server Actions

  Page->>View: initialAssociations
  View->>Card: render with callbacks
  Card->>Card: enter edit mode, edit fields/logo
  Card->>Action: onSave (createAssociation/editAssociation)
  Action->>Action: requireRole, build FormData
  Action->>View: association or error
  View->>View: toast, update state, router.refresh()
  Card->>Dialog: open links dialog
  Dialog->>Card: onSave normalized links
  Card->>Action: onSaveLinks (editAssociationLinks)
  Action->>View: links result or error
  View->>View: toast, update links, router.refresh()
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding associations management.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@BIA3IA BIA3IA marked this pull request as ready for review June 10, 2026 22:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (6)
src/components/ui/sidebar.tsx (1)

76-89: 💤 Low value

Refactor setOpen callback to avoid unnecessary re-creation.

Including open in the dependency array causes the setOpen callback to be recreated every time the sidebar state changes, which reduces the performance benefit of useCallback. Consider restructuring to use functional updates consistently:

const setOpen = React.useCallback(
  (value: boolean | ((value: boolean) => boolean)) => {
    const newOpenState = typeof value === "function" 
      ? (setOpenProp ?? _setOpen)
      : undefined
    
    if (typeof value === "function") {
      // Use functional form to avoid needing `open` in closure
      if (setOpenProp) {
        setOpenProp((prev) => {
          const next = value(prev)
          document.cookie = `${SIDEBAR_COOKIE_NAME}=${next}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
          return next
        })
      } else {
        _setOpen((prev) => {
          const next = value(prev)
          document.cookie = `${SIDEBAR_COOKIE_NAME}=${next}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
          return next
        })
      }
    } else {
      if (setOpenProp) {
        setOpenProp(value)
      } else {
        _setOpen(value)
      }
      document.cookie = `${SIDEBAR_COOKIE_NAME}=${value}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
    }
  },
  [setOpenProp]
)

This removes open from the dependency array, preventing recreation on every state change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/ui/sidebar.tsx` around lines 76 - 89, The setOpen callback
closes over open causing it to be recreated on every state change; refactor
setOpen (created with React.useCallback) to avoid using open in the closure by
using functional updates: when value is a function, call either setOpenProp or
_setOpen with a functional updater that computes next from prev, writes the
cookie using SIDEBAR_COOKIE_NAME and SIDEBAR_COOKIE_MAX_AGE inside that updater,
and returns next; when value is a boolean, call setOpenProp or _setOpen directly
and write the cookie with that boolean; finally update the useCallback
dependency array to only [setOpenProp].
src/app/dashboard/(active)/layout.tsx (2)

41-41: ⚡ Quick win

Remove commented-out dead code.

The commented Separator component should be removed to keep the codebase clean.

-// <Separator orientation="vertical" className="mr-2 data-vertical:h-4 data-vertical:self-auto" />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/`(active)/layout.tsx at line 41, Remove the commented-out
Separator JSX line (the "// <Separator orientation="vertical" className="mr-2
data-vertical:h-4 data-vertical:self-auto" />" comment) from the layout
component so the dead code is gone; also check for and remove any now-unused
import or reference to the Separator symbol in the same file (layout.tsx) to
avoid unused-import lint warnings, then run the linter/build to confirm no
remaining references.

31-34: 💤 Low value

Consider a more semantic approach for header layout.

The empty <div></div> on line 34 is used to achieve three-column flexbox layout with justify-between to center the breadcrumb. While this works, it's a layout hack that could be clearer.

💡 Alternative approaches

Option 1: Add a clarifying comment:

         <SidebarTrigger className="-ml-1" size="icon" />
         <Breadcrumb className="absolute left-1/2 -translate-x-1/2" />
-        <div></div>
+        <div>{/* Spacer for flex layout */}</div>

Option 2: Use CSS Grid for more explicit three-column layout:

-      <header className="flex h-16 shrink-0 relative items-center justify-between gap-2 border-b mb-8">
+      <header className="grid grid-cols-3 h-16 shrink-0 relative items-center gap-2 border-b mb-8">
         <SidebarTrigger className="-ml-1" size="icon" />
-        <Breadcrumb className="absolute left-1/2 -translate-x-1/2" />
-        <div></div>
+        <Breadcrumb className="justify-self-center" />
+        <div>{/* Reserved for future header actions */}</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/`(active)/layout.tsx around lines 31 - 34, The header
currently uses an empty <div></div> as a layout hack to center Breadcrumb
between SidebarTrigger and the right side; update layout.tsx to use a clearer
semantic approach: either switch the header's container (the element with
className "flex h-16 ...") to a CSS grid with three explicit columns and place
SidebarTrigger, Breadcrumb, and a semantic spacer element (e.g., a
visually-hidden or aria-hidden div) into the three grid cells, or replace the
empty <div> with a purposefully named spacer element (e.g., <div
aria-hidden="true" className="header-spacer" />) and add a brief comment
explaining its role; ensure Breadcrumb and SidebarTrigger keep their existing
props and classes so behavior doesn’t change.
src/app/dashboard/(active)/breadcrumb.tsx (1)

72-74: ⚡ Quick win

Improve UUID/ID detection heuristic for breadcrumbs.

The current implementation treats any numeric string as an ID (line 73: !Number.isNaN(Number(segment))), which may produce misleading breadcrumb labels. For example, /dashboard/web/associations/2024 would display "Details" instead of "2024".

Consider using a more specific pattern to detect actual UUIDs or database IDs:

♻️ More robust UUID/ID detection
 function isUUIDorId(segment: string) {
-  return !Number.isNaN(Number(segment)) || segment.length > 20 // Regex custom a seconda dei tuoi ID
+  // Match UUID v4 format or numeric IDs that look like database IDs
+  const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
+  const numericIdPattern = /^\d{6,}$/ // 6+ digit numeric IDs
+  return uuidPattern.test(segment) || numericIdPattern.test(segment) || segment.length > 30
 }

This approach:

  • Explicitly matches UUID v4 format
  • Only treats longer numeric strings (6+ digits) as IDs, avoiding false positives on year numbers
  • Increases the length threshold to 30 to reduce false positives
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/`(active)/breadcrumb.tsx around lines 72 - 74, The
isUUIDorId heuristic in breadcrumb.tsx is too permissive (treats any numeric
string as an ID); update the isUUIDorId(segment: string) function to match a
UUID v4 regex for UUID detection, treat purely-numeric segments as IDs only when
they are sufficiently long (e.g., >= 6 digits) to avoid years like "2024", and
raise the non-numeric length threshold (e.g., to 30) to reduce false positives;
keep the function name isUUIDorId and operate on the incoming segment string so
breadcrumbs show real IDs vs human-readable labels.
src/components/dashboard-sidebar/main-nav.tsx (1)

64-87: ⚡ Quick win

Remove unnecessary undefined check for open state.

The condition open !== undefined on line 64 is always true because:

  1. open is typed as boolean in the useState<boolean> declaration (line 57)
  2. The initial value always resolves to a boolean via the ?? false fallback
  3. setOpen is only ever called with boolean values

The <Skeleton /> branch is unreachable dead code.

♻️ Simplify the conditional rendering
-  return open !== undefined ? (
+  return (
     <Collapsible render={<SidebarMenuItem />} open={open} onOpenChange={handleOpenChange} className="group/collapsible">
       <CollapsibleTrigger
         render={
           <SidebarMenuButton className="font-medium">
             {category.icon}
             {category.title}
             <ChevronRight className="ml-auto transition-transform ease-linear rotate-0 group-data-open/collapsible:rotate-90" />
           </SidebarMenuButton>
         }
       />
       <CollapsibleContent>
         {category.items?.length ? (
           <SidebarMenuSub className="px-1.5">
             {category.items.map((item) => (
               <DSMenuItem key={item.url === "#" ? item.title : item.url} item={item} />
             ))}
           </SidebarMenuSub>
         ) : null}
       </CollapsibleContent>
     </Collapsible>
-  ) : (
-    <Skeleton />
   )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard-sidebar/main-nav.tsx` around lines 64 - 87, The
ternary checking "open !== undefined" is unnecessary because "open" is a boolean
from useState<boolean> (with ?? false fallback) so the Skeleton branch is dead;
replace the entire conditional return with a direct return of the <Collapsible
...> block (keeping Collapsible, CollapsibleTrigger, CollapsibleContent,
SidebarMenuItem/DSMenuItem, SidebarMenuSub, handleOpenChange and the open prop
intact) and remove the unreachable <Skeleton /> branch and the ternary
expression so the component always renders the Collapsible.
src/app/dashboard/(active)/web/associations/constants.ts (1)

30-41: 💤 Low value

Consider distinct icons for TikTok and Spotify.

Both TikTok (line 35) and Spotify (line 40) use the Music icon, which reduces visual distinction in the UI. Consider using platform-specific icons or different generic icons to improve differentiation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/`(active)/web/associations/constants.ts around lines 30 -
41, LINK_FIELDS currently assigns the same Music icon to ASSOCIATION_LINK.TIKTOK
and ASSOCIATION_LINK.SPOTIFY which hurts visual distinction; update the entry
for ASSOCIATION_LINK.TIKTOK to use a TikTok-specific icon (e.g., Tiktok) and the
entry for ASSOCIATION_LINK.SPOTIFY to use a Spotify-specific icon (e.g.,
Spotify) or two distinct generic icons, ensuring the imports at the top include
the chosen icon components and that the LINK_FIELDS array entries for
ASSOCIATION_LINK.TIKTOK and ASSOCIATION_LINK.SPOTIFY are updated to reference
those new icon symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/dashboard/`(active)/layout.tsx:
- Around line 7-14: parseCookie currently returns the raw JSON which is
implicitly any; update parseCookie to return a safe Record<string, boolean> for
DashboardSidebar by treating JSON.parse result as unknown, performing runtime
validation and coercion: ensure the parsed value is an object, iterate its keys
and keep only entries whose values are booleans (or coerce string/"true"/"false"
and numeric 0/1 to booleans), and return that sanitized Record<string, boolean>;
keep the same function name parseCookie so callers continue to work and return
{} on invalid input.

In `@src/app/dashboard/`(active)/web/associations/associations-view.tsx:
- Around line 45-68: The handler in associations-view.tsx expects
deleteAssociation to return a standardized result object with an error string
(e.g., { error: "UNAUTHORIZED" | "NOT_FOUND" } or no error), so change the
server-side action deleteAssociation (in src/server/actions/web.ts) to map the
raw TRPC response into a consistent shape before returning: catch TRPC errors
and return { error: "UNAUTHORIZED" } or { error: "NOT_FOUND" } as appropriate,
and return a success object (e.g., {}) on success; ensure the exported function
name deleteAssociation is the one that performs this mapping so the front-end
checks (result.error) work reliably.
- Around line 19-33: Replace the fragile Date.now() temporary id in handleAdd
with a deterministic client-side draft id strategy: introduce a component-level
negative counter (e.g., nextTempIdRef or nextTempId variable) or generate a UUID
and use that value for association.id, then continue to call setAssociations,
setEditingAssociationId and setDraftAssociationIds with that new id; update any
code relying on numeric ids (if using negative counter) or ensure Set membership
works for string ids (if using UUID) so draft ids are clearly distinguished from
server-assigned ids.

In `@src/app/dashboard/`(active)/web/associations/card-association.tsx:
- Around line 37-41: The handleIconUpload function currently assigns raw
uploaded SVG text via setLogoSvg and is later rendered with
dangerouslySetInnerHTML (risking XSS); change it to validate and sanitize
uploads: verify the file's type/extension and size, read the file as text only
if it's an SVG, then pass the text through a trusted sanitizer (e.g.,
isomorphic-dompurify / DOMPurify) configured to strip scripts, event handlers,
external references and disallowed tags/attributes before calling setLogoSvg;
ensure any failed validation rejects the upload and does not update the state.

In `@src/hooks/use-cookie-storage.tsx`:
- Around line 28-46: The setValue callback in use-cookie-storage captures
storedValue in its closure, so function updaters call value(storedValue) and can
get stale state; change setValue to use the functional updater pattern: call
setStoredValue(prev => { const next = typeof value === 'function' ? (value as
any)(prev) : value; /* persist next via setCookie/deleteCookie using key and
mergedOptions */ return next; }); remove storedValue from the useCallback
dependency array and keep key and mergedOptions only; ensure
deleteCookie/setCookie use the computed next value when persisting.

In `@src/server/actions/web.ts`:
- Around line 40-45: The deleteAssociation action returns the raw TRPC result
but should match the other actions' { association, error } shape; update
deleteAssociation to call trpc.web.associations.deleteAssociation.mutate({ id })
and then return an object like { association: result.association, error:
result.error } (or map the mutation's response fields to association and error),
keeping the existing authorization check via requireRole; reference the function
deleteAssociation and the call trpc.web.associations.deleteAssociation.mutate to
locate and adjust the return shape to match createAssociation, editAssociation,
and editAssociationLinks.

---

Nitpick comments:
In `@src/app/dashboard/`(active)/breadcrumb.tsx:
- Around line 72-74: The isUUIDorId heuristic in breadcrumb.tsx is too
permissive (treats any numeric string as an ID); update the isUUIDorId(segment:
string) function to match a UUID v4 regex for UUID detection, treat
purely-numeric segments as IDs only when they are sufficiently long (e.g., >= 6
digits) to avoid years like "2024", and raise the non-numeric length threshold
(e.g., to 30) to reduce false positives; keep the function name isUUIDorId and
operate on the incoming segment string so breadcrumbs show real IDs vs
human-readable labels.

In `@src/app/dashboard/`(active)/layout.tsx:
- Line 41: Remove the commented-out Separator JSX line (the "// <Separator
orientation="vertical" className="mr-2 data-vertical:h-4
data-vertical:self-auto" />" comment) from the layout component so the dead code
is gone; also check for and remove any now-unused import or reference to the
Separator symbol in the same file (layout.tsx) to avoid unused-import lint
warnings, then run the linter/build to confirm no remaining references.
- Around line 31-34: The header currently uses an empty <div></div> as a layout
hack to center Breadcrumb between SidebarTrigger and the right side; update
layout.tsx to use a clearer semantic approach: either switch the header's
container (the element with className "flex h-16 ...") to a CSS grid with three
explicit columns and place SidebarTrigger, Breadcrumb, and a semantic spacer
element (e.g., a visually-hidden or aria-hidden div) into the three grid cells,
or replace the empty <div> with a purposefully named spacer element (e.g., <div
aria-hidden="true" className="header-spacer" />) and add a brief comment
explaining its role; ensure Breadcrumb and SidebarTrigger keep their existing
props and classes so behavior doesn’t change.

In `@src/app/dashboard/`(active)/web/associations/constants.ts:
- Around line 30-41: LINK_FIELDS currently assigns the same Music icon to
ASSOCIATION_LINK.TIKTOK and ASSOCIATION_LINK.SPOTIFY which hurts visual
distinction; update the entry for ASSOCIATION_LINK.TIKTOK to use a
TikTok-specific icon (e.g., Tiktok) and the entry for ASSOCIATION_LINK.SPOTIFY
to use a Spotify-specific icon (e.g., Spotify) or two distinct generic icons,
ensuring the imports at the top include the chosen icon components and that the
LINK_FIELDS array entries for ASSOCIATION_LINK.TIKTOK and
ASSOCIATION_LINK.SPOTIFY are updated to reference those new icon symbols.

In `@src/components/dashboard-sidebar/main-nav.tsx`:
- Around line 64-87: The ternary checking "open !== undefined" is unnecessary
because "open" is a boolean from useState<boolean> (with ?? false fallback) so
the Skeleton branch is dead; replace the entire conditional return with a direct
return of the <Collapsible ...> block (keeping Collapsible, CollapsibleTrigger,
CollapsibleContent, SidebarMenuItem/DSMenuItem, SidebarMenuSub, handleOpenChange
and the open prop intact) and remove the unreachable <Skeleton /> branch and the
ternary expression so the component always renders the Collapsible.

In `@src/components/ui/sidebar.tsx`:
- Around line 76-89: The setOpen callback closes over open causing it to be
recreated on every state change; refactor setOpen (created with
React.useCallback) to avoid using open in the closure by using functional
updates: when value is a function, call either setOpenProp or _setOpen with a
functional updater that computes next from prev, writes the cookie using
SIDEBAR_COOKIE_NAME and SIDEBAR_COOKIE_MAX_AGE inside that updater, and returns
next; when value is a boolean, call setOpenProp or _setOpen directly and write
the cookie with that boolean; finally update the useCallback dependency array to
only [setOpenProp].
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67a0ff49-3909-424b-9df4-1ae5178ad04d

📥 Commits

Reviewing files that changed from the base of the PR and between 51678bc and 5b24e28.

📒 Files selected for processing (48)
  • src/app/dashboard/(active)/account/loading.tsx
  • src/app/dashboard/(active)/account/page.tsx
  • src/app/dashboard/(active)/azure/members/loading.tsx
  • src/app/dashboard/(active)/azure/members/page.tsx
  • src/app/dashboard/(active)/azure/page.tsx
  • src/app/dashboard/(active)/breadcrumb.tsx
  • src/app/dashboard/(active)/complete-profile.tsx
  • src/app/dashboard/(active)/layout.tsx
  • src/app/dashboard/(active)/page.tsx
  • src/app/dashboard/(active)/telegram/grants/loading.tsx
  • src/app/dashboard/(active)/telegram/grants/page.tsx
  • src/app/dashboard/(active)/telegram/groups/group-row.tsx
  • src/app/dashboard/(active)/telegram/groups/loading.tsx
  • src/app/dashboard/(active)/telegram/groups/page.tsx
  • src/app/dashboard/(active)/telegram/page.tsx
  • src/app/dashboard/(active)/telegram/user-details/page.tsx
  • src/app/dashboard/(active)/telegram/user-details/remove-role.tsx
  • src/app/dashboard/(active)/telegram/user-list/loading.tsx
  • src/app/dashboard/(active)/telegram/user-list/page.tsx
  • src/app/dashboard/(active)/web/associations/association-links.tsx
  • src/app/dashboard/(active)/web/associations/associations-view.tsx
  • src/app/dashboard/(active)/web/associations/card-association.tsx
  • src/app/dashboard/(active)/web/associations/constants.ts
  • src/app/dashboard/(active)/web/associations/page.tsx
  • src/app/dashboard/(active)/web/associations/types.ts
  • src/app/dashboard/layout.tsx
  • src/app/layout.tsx
  • src/app/page.tsx
  • src/components/admin-header/index.tsx
  • src/components/admin-header/right-nav.tsx
  • src/components/dashboard-sidebar/data.tsx
  • src/components/dashboard-sidebar/index.tsx
  • src/components/dashboard-sidebar/main-nav.tsx
  • src/components/dashboard-sidebar/user-nav.tsx
  • src/components/delete-dialog.tsx
  • src/components/ui/alert.tsx
  • src/components/ui/button.tsx
  • src/components/ui/sidebar.tsx
  • src/components/web-header.tsx
  • src/constants.ts
  • src/hooks/use-cookie-storage.tsx
  • src/hooks/use-session-storage.tsx
  • src/index.css
  • src/lib/i18n.ts
  • src/lib/utils/index.ts
  • src/server/actions/web.ts
  • src/utils/cookies.ts
  • tsconfig.json
💤 Files with no reviewable changes (5)
  • src/lib/i18n.ts
  • src/app/dashboard/(active)/telegram/page.tsx
  • src/components/admin-header/index.tsx
  • src/components/admin-header/right-nav.tsx
  • src/app/dashboard/(active)/azure/page.tsx

Comment thread src/app/dashboard/(active)/layout.tsx
Comment thread src/app/dashboard/(active)/web/associations/associations-view.tsx
Comment thread src/app/dashboard/(active)/web/associations/associations-view.tsx
Comment thread src/hooks/use-cookie-storage.tsx
Comment thread src/server/actions/web.ts
@BIA3IA BIA3IA added the status: blocked Work cannot proceed until another issue is resolved label Jun 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/server/actions/web.ts (1)

62-67: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

deleteAssociation return type is inconsistent with other actions.

This was flagged in a previous review and remains unresolved. deleteAssociation returns the raw tRPC result, while createAssociation, editAssociation, and editAssociationLinks all return { association, error } shapes. Downstream callers expecting a uniform interface will break.

♻️ Standardize the return type
 export async function deleteAssociation(id: number) {
   const { allowed } = await requireRole(["owner", "direttivo", "president"])
   if (!allowed) return { association: null, error: "UNAUTHORIZED" }
 
-  return await trpc.web.associations.deleteAssociation.mutate({ id })
+  const result = await trpc.web.associations.deleteAssociation.mutate({ id })
+  if ("error" in result) return { association: null, error: result.error }
+
+  return { association: result, error: null }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/actions/web.ts` around lines 62 - 67, The deleteAssociation action
currently returns the raw tRPC mutation result, unlike createAssociation,
editAssociation, and editAssociationLinks which return a consistent {
association, error } shape. Update deleteAssociation to follow the same
interface by using the trpc.web.associations.deleteAssociation.mutate call but
wrapping its result into the same standardized object format expected by
downstream callers, and keep the unauthorized branch aligned with that return
shape as well.
🧹 Nitpick comments (1)
src/app/dashboard/(active)/web/associations/card-association.tsx (1)

75-83: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

logo and links in save values are ignored by the consumer.

saveChanges passes logo: null and links to onSave, but the downstream consumer (associations-view.tsx handleSave) only destructures name, descriptionIt, descriptionEn, and logoFile. Links are saved separately via handleSaveLinks. These dead fields are misleading — consider omitting them or documenting that they're intentionally ignored.

♻️ Proposed refactor
       const saved = await item.onSave({
         id: item.id,
         name,
         logoFile,
         descriptionIt,
         descriptionEn,
-        links,
-        logo: null,
       })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/`(active)/web/associations/card-association.tsx around
lines 75 - 83, The save payload in `saveChanges` includes `logo` and `links`,
but `associations-view.tsx`’s `handleSave` ignores them and `links` are handled
separately by `handleSaveLinks`. Update `card-association.tsx` so `item.onSave`
only receives the fields actually consumed downstream (`id`, `name`, `logoFile`,
`descriptionIt`, `descriptionEn`), or otherwise document the intentional no-op
fields if they must remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@next.config.ts`:
- Around line 22-26: Add client-side file size validation to match the 2MB
server action limit: update handleIconUpload in card-association.tsx to check
the selected file’s size before upload and reject anything over 2MB with a clear
user-facing message. Keep the next.config.ts serverActions bodySizeLimit as-is,
and ensure the validation is applied at the upload entry point so oversized
files are blocked before reaching the server action.

In `@src/app/dashboard/`(active)/web/associations/card-association.tsx:
- Around line 39-44: The handleIconUpload flow in card-association.tsx currently
accepts any selected file and only discovers oversized uploads later in the
server action. Add a client-side size check in handleIconUpload before calling
setLogoFile and setLogoPreview, using the same 2mb limit configured in
next.config.ts, and prevent preview/state updates when the file exceeds that
limit. Keep the existing file selection logic in handleIconUpload and only
proceed with URL.createObjectURL after the size validation passes.

In `@src/server/actions/web.ts`:
- Around line 29-32: The createAssociation flow is treating the raw result from
trpc.web.associations.addAssociation.mutate as a successful association without
checking for an error response. Update createAssociation in web.ts to mirror
editAssociation by inspecting the mutation result for an error field and
returning it via the error property when present, otherwise return the
association data; keep handleSave and the createAssociation return shape
consistent so invalid results are not written into state.

---

Duplicate comments:
In `@src/server/actions/web.ts`:
- Around line 62-67: The deleteAssociation action currently returns the raw tRPC
mutation result, unlike createAssociation, editAssociation, and
editAssociationLinks which return a consistent { association, error } shape.
Update deleteAssociation to follow the same interface by using the
trpc.web.associations.deleteAssociation.mutate call but wrapping its result into
the same standardized object format expected by downstream callers, and keep the
unauthorized branch aligned with that return shape as well.

---

Nitpick comments:
In `@src/app/dashboard/`(active)/web/associations/card-association.tsx:
- Around line 75-83: The save payload in `saveChanges` includes `logo` and
`links`, but `associations-view.tsx`’s `handleSave` ignores them and `links` are
handled separately by `handleSaveLinks`. Update `card-association.tsx` so
`item.onSave` only receives the fields actually consumed downstream (`id`,
`name`, `logoFile`, `descriptionIt`, `descriptionEn`), or otherwise document the
intentional no-op fields if they must remain.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56ed62d1-28d7-4c3d-a7df-7c5a5fddabb3

📥 Commits

Reviewing files that changed from the base of the PR and between 5b24e28 and 0374c9d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • next.config.ts
  • package.json
  • src/app/dashboard/(active)/web/associations/associations-view.tsx
  • src/app/dashboard/(active)/web/associations/card-association.tsx
  • src/app/dashboard/(active)/web/associations/types.ts
  • src/components/dashboard-sidebar/data.tsx
  • src/server/actions/web.ts
✅ Files skipped from review due to trivial changes (2)
  • package.json
  • src/app/dashboard/(active)/web/associations/types.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/dashboard-sidebar/data.tsx
  • src/app/dashboard/(active)/web/associations/associations-view.tsx

Comment thread next.config.ts
Comment on lines 22 to +26
experimental: {
reactCompiler: true,
serverActions: {
bodySizeLimit: "2mb",
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

bodySizeLimit should be paired with client-side file size validation.

The 2MB limit is set server-side, but handleIconUpload in card-association.tsx (lines 39-44) performs no size check. Files exceeding 2MB will fail at the server action level with an unclear error. See the cross-file comment on card-association.tsx for the client-side fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@next.config.ts` around lines 22 - 26, Add client-side file size validation to
match the 2MB server action limit: update handleIconUpload in
card-association.tsx to check the selected file’s size before upload and reject
anything over 2MB with a clear user-facing message. Keep the next.config.ts
serverActions bodySizeLimit as-is, and ensure the validation is applied at the
upload entry point so oversized files are blocked before reaching the server
action.

Comment on lines +39 to +44
async function handleIconUpload(event: ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0]
if (!file) return
setLogoFile(file)
setLogoPreview(URL.createObjectURL(file))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add client-side file size validation before upload.

The bodySizeLimit in next.config.ts is set to "2mb". Without a client-side check, uploading a larger file will fail at the server action with an opaque error. The previous XSS concern (SVG + dangerouslySetInnerHTML) is resolved since SVGs are now rendered via next/image (<img>), which does not execute embedded JavaScript.

🛡️ Proposed fix
 async function handleIconUpload(event: ChangeEvent<HTMLInputElement>) {
   const file = event.target.files?.[0]
   if (!file) return
+  if (file.size > 2 * 1024 * 1024) {
+    toast.error("File size must be under 2MB.")
+    return
+  }
   setLogoFile(file)
   setLogoPreview(URL.createObjectURL(file))
 }
📝 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
async function handleIconUpload(event: ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0]
if (!file) return
setLogoFile(file)
setLogoPreview(URL.createObjectURL(file))
}
async function handleIconUpload(event: ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0]
if (!file) return
if (file.size > 2 * 1024 * 1024) {
toast.error("File size must be under 2MB.")
return
}
setLogoFile(file)
setLogoPreview(URL.createObjectURL(file))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/dashboard/`(active)/web/associations/card-association.tsx around
lines 39 - 44, The handleIconUpload flow in card-association.tsx currently
accepts any selected file and only discovers oversized uploads later in the
server action. Add a client-side size check in handleIconUpload before calling
setLogoFile and setLogoPreview, using the same 2mb limit configured in
next.config.ts, and prevent preview/state updates when the file exceeds that
limit. Keep the existing file selection logic in handleIconUpload and only
proceed with URL.createObjectURL after the size validation passes.

Comment thread src/server/actions/web.ts
Comment on lines +29 to +32
return {
association: await trpc.web.associations.addAssociation.mutate(formdata),
error: null,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

createAssociation doesn't check for errors from the tRPC mutation.

Unlike editAssociation (line 44 checks if ("error" in result)), createAssociation wraps the raw mutation result directly as association with error: null. If addAssociation.mutate returns an error object, it will be silently treated as a successful association, and downstream code in handleSave will set it as the association state — corrupting the UI.

🐛 Proposed fix: add error checking consistent with `editAssociation`
   const formdata = getAssociationFormData(input)
   formdata.set("createdBy", String(telegramId))
 
-  return {
-    association: await trpc.web.associations.addAssociation.mutate(formdata),
-    error: null,
-  }
+  const result = await trpc.web.associations.addAssociation.mutate(formdata)
+  if ("error" in result) return { association: null, error: result.error }
+
+  return { association: result, error: null }
 }
📝 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
return {
association: await trpc.web.associations.addAssociation.mutate(formdata),
error: null,
}
const result = await trpc.web.associations.addAssociation.mutate(formdata)
if ("error" in result) return { association: null, error: result.error }
return { association: result, error: null }
🧰 Tools
🪛 GitHub Check: Typecheck and Lint

[failure] 30-30:
Argument of type 'FormData' is not assignable to parameter of type '{ name: string; descriptionIt: string; descriptionEn: string; logoSvg: string | null; createdBy: number; }'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/actions/web.ts` around lines 29 - 32, The createAssociation flow
is treating the raw result from trpc.web.associations.addAssociation.mutate as a
successful association without checking for an error response. Update
createAssociation in web.ts to mirror editAssociation by inspecting the mutation
result for an error field and returning it via the error property when present,
otherwise return the association data; keep handleSave and the createAssociation
return shape consistent so invalid results are not written into state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: blocked Work cannot proceed until another issue is resolved

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants