Skip to content

PWA - #258

Open
shadownrx wants to merge 1 commit into
programaconnosotros:mainfrom
shadownrx:pwa
Open

PWA#258
shadownrx wants to merge 1 commit into
programaconnosotros:mainfrom
shadownrx:pwa

Conversation

@shadownrx

@shadownrx shadownrx commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

Descripción

He convertido el sitio en una Progressive Web App (PWA). Esto permite que la comunidad pueda instalar la web como una aplicación nativa en sus celulares y mejora el rendimiento general.

Cambios realizados

  • Manifest: Configuración de manifest.json en /public con soporte para iconos maskable.
  • Service Worker: Implementación de @ducanh2912/next-pwa para habilitar caché y modo offline básico.
  • Branding: Se definió el color institucional #7c3aed para la barra de estado y el fondo de carga.
  • Metadatos: Actualización de los metadatos en el Layout para compatibilidad con iOS (Apple Web App).

¿Cómo probarlo?

Una vez mergeado, al entrar desde Chrome o Edge aparecerá el icono de "Instalar" en la barra de direcciones.

Summary by CodeRabbit

Release Notes

  • New Features

    • Progressive Web App (PWA) support with offline access and installability
    • Session-aware features across platform pages
  • Bug Fixes

    • Fixed event registration to use internal workflow only
    • Removed status badge calculations from event display
  • Refactor

    • Removed external registration URLs from event management
    • Restructured sidebar navigation with new Community and Info General sections
    • Improved card hover animations with brand-specific colors
  • Chores

    • Removed loading skeletons from 30+ pages
    • Cleaned up linting rules and directives

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This comprehensive pull request adds PWA support, removes external event registration URLs from the data model and UI, deletes 40+ loading skeleton components, applies consistent PCN-themed hover styles across the platform, refactors sidebar navigation, removes unused parameters throughout the codebase, and deletes the RecentlyAddedEventsSection component. Additionally, the Zero-to-Agent-Sponsors page is removed and various ESLint suppressions are eliminated.

Changes

Cohort / File(s) Summary
PWA Configuration
.eslintrc.json, .gitignore, next.config.mjs, package.json, public/manifest.json
Adds PWA support by installing @ducanh2912/next-pwa, wrapping Next.js config with withPWA, creating manifest.json with app metadata and icons, and updating ignore patterns for service worker artifacts.
Event Model & Schema Updates
prisma/schema.prisma, prisma/migrations/..., src/schemas/event-schema.ts
Removes externalRegistrationUrl field from Event model, associated Prisma migration, and event validation schema; simplifies event registration flow.
Event Component Removals
src/components/events/event-card.tsx, src/components/events/event-detail-client.tsx, src/components/events/event-form.tsx, src/components/events/register-event-button.tsx, src/components/home/recently-added-events-section.tsx
Removes external registration URL references from event UI; deletes EventStatusBadge; eliminates RecentlyAddedEventsSection component and its associated fetch function.
Event Page Updates
src/app/(platform)/eventos/[id]/page.tsx, src/app/(platform)/eventos/[id]/inscripcion/page.tsx, src/app/(platform)/eventos/[id]/editar/page.tsx
Removes externalRegistrationUrl from event registration flow and form initialization; simplifies registration to only handle internal redirects; updates metadata generation to use absolute URLs and PCN branding.
Loading Skeleton Deletion
src/components/skeletons/page-skeletons.tsx, src/app/(platform)/**/loading.tsx (40+ files)
Removes entire skeleton component library and all 40+ route-level loading.tsx files across the platform, eliminating pre-render placeholders.
Hover Style Updates - Cards & Sections
src/app/(platform)/**page.tsx (charlas, code-warfare, cursos, desarrollo, especialidades, eventos, historia, lectura, monitoreo, perfil, testimonios, visitas, software-recomendado/util), src/components/**/*.tsx (advises, announcements, events, comunity, home, influencers, monitoreo, notificaciones, testimonials, ui components)
Adds consistent PCN-themed hover effects across 50+ components: hover:border-pcnPurple for light mode and dark:hover:border-pcnGreen dark:hover:shadow-pcnGreen/20 for dark mode.
Sidebar Navigation Refactoring
src/components/ui/app-sidebar.tsx, src/components/ui/nav-main.tsx, src/components/ui/nav-projects.tsx, src/components/ui/sidebar.tsx
Restructures sidebar navigation by removing "Recursos"/"Redes" submenus and adding "Comunidad"/"Info General" sections; creates new NavProjects component for social networks; changes sidebar variant from "sidebar" to "inset"; makes nav item URLs required.
Unused Parameter Renames
src/actions/**/*.ts, src/components/**/*.tsx, src/middleware.ts, src/components/ui/**/*.tsx
Systematically renames underscore-prefixed unused parameters (e.g., _data, _isOpen) to proper names (e.g., data, isOpen) across 50+ functions and prop types; no runtime behavior changes.
Session Integration & Cookie Reading
src/app/layout.tsx, src/app/(platform)/analiticas/page.tsx, src/app/(platform)/podcast/page.tsx, src/app/(platform)/preguntas-frecuentes/page.tsx
Adds sessionId cookie reading and Prisma session/user lookup in multiple page components and root layout; updates metadata handling to use absolute URLs and manifest reference.
Authentication Page Refactoring
src/app/autenticacion/iniciar-sesion/page.tsx, src/app/autenticacion/registro/page.tsx, src/app/autenticacion/verificar-email/page.tsx
Removes Suspense wrappers from authentication pages; exports content components directly as default exports.
Notification & Profile Updates
src/app/(platform)/notificaciones/notifications-client.tsx, src/app/(platform)/notificaciones/page.tsx, src/components/profile/profile-form.tsx
Adds date formatting helpers and new color utility function (getBestTextColor); updates card hover styling for notifications.
Page Removals & Configuration
src/app/(platform)/page.tsx, src/app/(platform)/zero-to-agent-sponsors/page.tsx, src/app/cowork/page.tsx, src/app/meetup/page.tsx
Removes dynamic rendering export from cowork/meetup routes; removes 352-line Zero-to-Agent-Sponsors page entirely; updates home page WhatsApp button styling and removes recentlyAddedEventsSection prop.
ESLint Comment Removals & Button Variants
src/components/**/*.tsx, src/components/landing/**/*.tsx, src/components/ui/button.tsx
Removes 50+ inline @next/next/no-img-element ESLint suppressions throughout codebase; removes gold/silver/bronze button variants.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • Setup para PWA #38 — The PR fully implements PWA setup with @ducanh2912/next-pwa dependency, Next.js config wrapper, manifest.json, and metadata updates.

Possibly related PRs

Suggested reviewers

  • agustin-sanc

Poem

🐇 A hop through PCN's grand redesign,
PWA service workers now align,
External URLs swept away with care,
Purple borders dancing everywhere,
Loading skeletons banished from the land,
The platform shines with a steadier hand!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The title 'PWA' is vague and generic, using a non-descriptive acronym without context that doesn't clearly convey the main change to someone unfamiliar with the PR. Consider using a more descriptive title like 'Add Progressive Web App (PWA) support with manifest and service worker integration' or 'Enable PWA installation with offline support and branding'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (18)
src/components/advises/advise-card.tsx (1)

66-79: ⚠️ Potential issue | 🟡 Minor

previousLikes snapshot is captured but never used.

Line 66 stores a snapshot of optimisticLikes, but the rollback path on line 75 simply re-invokes addOptimisticLike(session.user.id) (which toggles the optimistic reducer back). The previousLikes array is never read. Either remove the dead variable, or use it for a more explicit rollback (e.g., by introducing a "set" action in the reducer). Renaming from _previousLikes to previousLikes also drops the underscore convention that signaled an intentionally-unused binding, which may trip no-unused-vars style lint rules.

🧹 Proposed cleanup: drop the unused snapshot
     setIsLiking(true);
-    const previousLikes = [...optimisticLikes];

     try {
       // Optimistically update the UI
       addOptimisticLike(session.user.id);
       await toggleLike(advise.id);
     } catch (error) {
       console.error('Error toggling like:', error);
       // Revert optimistic update on error
       addOptimisticLike(session.user.id);
     } finally {
       setIsLiking(false);
     }

Side note (not introduced here, but worth verifying in this PR that upgrades to React 19): addOptimisticLike is invoked outside startTransition/an action. React 19 surfaces a warning — "An optimistic state update occurred outside a transition or action" — in that case. Consider wrapping the toggle in startTransition if you see that warning.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/advises/advise-card.tsx` around lines 66 - 79, previousLikes
is captured but never used; remove the dead variable or use it to perform an
explicit rollback: either delete the line "const previousLikes =
[...optimisticLikes];" to satisfy linter, or implement a rollback that calls a
setter action (e.g., introduce a reducer action "setOptimisticLikes" and call
setOptimisticLikes(previousLikes) in the catch block instead of re-invoking
addOptimisticLike(session.user.id)); also preserve the unused-name intent if you
must keep the snapshot by renaming it back to _previousLikes to avoid
no-unused-vars errors. Ensure references to addOptimisticLike, toggleLike,
optimisticLikes, and setIsLiking are updated accordingly.
src/components/landing/team.tsx (2)

219-222: ⚠️ Potential issue | 🟡 Minor

Hardcoded #04f4be is inconsistent with the PCN theme applied elsewhere in this file.

The linked-card branch at line 206 uses bg-pcnPurple/40 ... dark:bg-pcnGreen/40, but this fallback branch hardcodes a teal #04f4be via inline style. Given the PR explicitly aligns on the institutional #7c3aed / PCN theme (Card border, icon, hover borders all use pcnPurple/pcnGreen), the non-linked variant should match.

🎨 Proposed fix
-              <div
-                className="absolute inset-0 mix-blend-color transition-opacity duration-300 group-hover:opacity-0"
-                style={{ backgroundColor: '#04f4be', opacity: 0.4 }}
-              />
+              <div className="absolute inset-0 bg-pcnPurple/40 mix-blend-color transition-opacity duration-300 group-hover:opacity-0 dark:bg-pcnGreen/40" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/landing/team.tsx` around lines 219 - 222, The inline style on
the overlay div (the element with className "absolute inset-0 mix-blend-color
transition-opacity duration-300 group-hover:opacity-0") hardcodes
backgroundColor: '#04f4be' which breaks theme consistency; remove the inline
style and replace it with the same Tailwind theme classes used by the
linked-card branch (e.g. "bg-pcnPurple/40 dark:bg-pcnGreen/40") so the
non-linked variant matches the institutional pcnPurple/pcnGreen styling and
keeps the intended 40% opacity.

201-205: ⚠️ Potential issue | 🟠 Major

Remove inline <img> tags or restore ESLint suppressions; the rule is active.

The eslint-disable-next-line @next/next/no-img-element`` suppressions were removed from lines 201–205 and 214–218, but the @next/next/no-img-element rule is active (inherited via the `next` and `next/core-web-vitals` ESLint presets). These `` elements will now fail lint checks.

Migrate to next/image for automatic optimization, lazy-loading, and improved LCP performance (aligned with PWA goals), or restore the inline suppressions if inline images are intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/landing/team.tsx` around lines 201 - 205, Replace the inline
<img> tags for the team avatar with Next.js' Image component (import Image from
'next/image') to satisfy `@next/next/no-img-element`: use Image with
src={person.imageUrl}, alt={person.name} and appropriate layout (e.g., fill or
width/height) and className for object-cover/grayscale and keep the
transition/group-hover behavior; update both occurrences that reference
person.imageUrl/person.name in this component (team rendering code). If using
next/image is not possible/intentional, restore the original eslint suppression
lines (// eslint-disable-next-line `@next/next/no-img-element`) immediately above
each <img> usage instead.
src/components/photo-gallery/photo-card.tsx (1)

57-61: ⚠️ Potential issue | 🟡 Minor

Migrate to next/image or re-add the eslint-disable suppression.

The @next/next/no-img-element suppression was removed but the raw <img> element remains. With next/core-web-vitals enabled (default for this project), ESLint will flag this as an error. Migrating to next/image is preferred—it provides automatic optimization, lazy loading, and improved LCP, which aligns with the PWA performance goals.

🖼️ Option: migrate to next/image
-import { Maximize2, Share2, Download } from 'lucide-react';
+import Image from 'next/image';
+import { Maximize2, Share2, Download } from 'lucide-react';
...
-        <img
-          src={photo.image || '/placeholder.svg'}
-          alt={photo.title}
-          className="h-full w-full object-cover transition-opacity duration-200 group-hover:opacity-80"
-        />
+        <Image
+          src={photo.image || '/placeholder.svg'}
+          alt={photo.title}
+          fill
+          sizes="(max-width: 768px) 50vw, 33vw"
+          className="object-cover transition-opacity duration-200 group-hover:opacity-80"
+        />

Note: remote image hosts must be allow-listed in next.config.{js,ts} under images.remotePatterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/photo-gallery/photo-card.tsx` around lines 57 - 61, The raw
<img> in the PhotoCard component (photo-gallery/photo-card.tsx) violates
`@next/next/no-img-element`; replace it with next/image: import Image from
'next/image' and swap the <img> for <Image> preserving src (photo.image ||
'/placeholder.svg'), alt (photo.title), and styling by using either width/height
or fill with the same className (e.g., object-cover, transition-opacity,
group-hover:opacity-80), and ensure any remote hosts are allow-listed in
next.config.{js,ts} via images.remotePatterns; alternatively, if you
intentionally must keep a plain img, re-add the eslint-disable comment for
`@next/next/no-img-element` above the element.
src/components/photo-gallery/photo-dialog.tsx (1)

94-191: ⚠️ Potential issue | 🟠 Major

Accessibility regression: Dialog without a DialogTitle.

Radix UI's DialogContent requires a DialogTitle descendant for screen-reader users; omitting it emits a runtime warning (DialogContent requires a DialogTitle...) and produces an unlabeled modal. The title rendering on lines 187–190 has been commented out, so every photo dialog opened now fails this requirement.

At minimum, render a visually hidden title so assistive tech still has an accessible name:

♿ Suggested fix using sr-only
         <DialogContent
           className="w-[90vw] max-w-4xl overflow-hidden border-none p-0 [&>button]:hidden"
           onKeyDown={handleKeyDown}
           tabIndex={0}
         >
+          <DialogTitle className="sr-only">{currentPhoto.title}</DialogTitle>
           <div className="relative flex h-[80vh] items-center justify-center">

If the visible caption block on lines 187–191 is intentionally disabled for design reasons, prefer the sr-only title above to maintain accessibility compliance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/photo-gallery/photo-dialog.tsx` around lines 94 - 191, The
DialogContent is missing a required DialogTitle (causing a runtime warning and
unlabeled modal); restore a DialogTitle descendant (use the DialogTitle
component) and render the photo's accessible name (e.g., currentPhoto.title or a
fallback) as its content, but keep it visually hidden by adding a utility class
like "sr-only" if you don't want a visible caption; update the commented block
that referenced DialogTitle/currentPhoto.title (and optional date via
formatDate(currentPhoto.date)) to use DialogTitle with className="sr-only" so
screen readers get a label.
src/components/ui/vortex.tsx (2)

187-195: ⚠️ Potential issue | 🟡 Minor

ctx parameter is declared but unused — will trigger ESLint no-unused-vars warning.

The ESLint configuration enables no-unused-vars (set to warn), and the function body never references ctx. Without the underscore-prefix convention (e.g., _ctx), this will trigger a lint warning in next lint. Either restore the _ctx prefix or remove the parameter entirely and update the three call sites at lines 63, 223, and 229.

🛠️ Option A — drop the unused parameter
-  const resize = (canvas: HTMLCanvasElement, ctx?: CanvasRenderingContext2D) => {
+  const resize = (canvas: HTMLCanvasElement) => {
     const { innerWidth, innerHeight } = window;

Then simplify the callers:

-        resize(canvas, ctx);
+        resize(canvas);
...
-      resize(canvas, ctx || undefined);
+      resize(canvas);
...
-          resize(canvas, ctx || undefined);
+          resize(canvas);
🛠️ Option B — restore the underscore prefix
-  const resize = (canvas: HTMLCanvasElement, ctx?: CanvasRenderingContext2D) => {
+  const resize = (canvas: HTMLCanvasElement, _ctx?: CanvasRenderingContext2D) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/vortex.tsx` around lines 187 - 195, The resize function
declares an unused ctx parameter which triggers ESLint no-unused-vars; remove
the ctx parameter from the resize signature (replace "const resize = (canvas:
HTMLCanvasElement, ctx?: CanvasRenderingContext2D) =>" with a single canvas
parameter) and update every call site in this file that currently passes a
second ctx argument to call resize(canvas) with only the canvas; ensure the
center array updates remain unchanged.

218-236: ⚠️ Potential issue | 🟡 Minor

This will trigger an ESLint warning, not fail the lint.

The suppression comment was deleted, but the useEffect at lines 218-236 still has an empty dependency array [] while calling setup() and resize() (which close over component-scoped constants). Next.js's default react-hooks/exhaustive-deps configuration is severity "warn", not "error", so this produces a warning rather than a lint failure.

Two reasonable paths:

  1. Keep the mount-only semantics and re-add a targeted suppression with justification:

    // The vortex animation is intentionally initialized once; props are captured on mount.
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);
  2. Make it correct per the rule by memoizing setup/resize with useCallback and listing them (or the relevant primitive props) in the deps array — this will tear down and re-run the animation whenever those props change.

For a PR focused solely on PWA enablement, Option 1 with the justifying comment is the safest change.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/vortex.tsx` around lines 218 - 236, The useEffect at the
top of the vortex component calls setup() and resize() while using an empty
dependency array, which triggers an exhaustive-deps ESLint warning; to fix
quickly for this PR, restore a targeted suppression with a short justification
above the closing brace (referencing useEffect, setup, resize, and canvasRef)
such as explaining the vortex animation is intentionally initialized once and
therefore should not rerun, i.e. add a single-line eslint-disable-next-line
react-hooks/exhaustive-deps comment with the justification; alternatively, if
you prefer to satisfy the rule, memoize setup and resize with useCallback and
include them in the useEffect deps so the effect reruns correctly when those
callbacks change.
src/components/ui/file-upload-public.tsx (1)

37-41: ⚠️ Potential issue | 🟠 Major

Restore ESLint suppressions or fix the violations—removing them without resolving the underlying issues will break the build.

The suppressions for react-hooks/exhaustive-deps (line 37–41) and @next/next/no-img-element (line 119) were removed, but the violations they silenced remain in the code:

  1. Lines 37–41: The useEffect reads preview without including it in the dependency array. This triggers react-hooks/exhaustive-deps. (Intentional to avoid re-running on local preview changes, but now unsilenced.)
  2. Line 119: A raw <img> is used instead of next/image, triggering @next/next/no-img-element.

Since .eslintrc.json extends "next" and "next/core-web-vitals", and Next.js runs ESLint during next build, this will fail CI unless lint checks are disabled.

Suggested options

Option A — Restore the targeted suppressions:

   useEffect(() => {
     if (value && value !== preview) {
       setPreview(value);
     }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [value]);
+            {/* eslint-disable-next-line `@next/next/no-img-element` */}
             <img src={preview} alt="Preview" className="h-full w-full object-cover" />

Option B — Address the violations: Replace the <img> with next/image (using fill and sizes), and refactor the effect to either use a ref guard or derive the value instead of managing separate state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/file-upload-public.tsx` around lines 37 - 41, The effect
using useEffect that reads preview but only lists value in its dependency array
(symbols: useEffect, preview, value, setPreview) must be fixed or the ESLint
suppression restored; either (A) re-add the eslint-disable-next-line
react-hooks/exhaustive-deps comment above that effect, or (B) refactor the
effect to avoid reading preview from closure (e.g., derive preview from value or
use a ref guard and include preview in the dependency array) so
react-hooks/exhaustive-deps is satisfied. Also replace the raw <img> usage with
Next.js Image (import Image from 'next/image' and use Image with appropriate
props such as fill and sizes) to resolve the `@next/next/no-img-element`
violation; ensure both fixes are applied or the corresponding eslint-disable
comments are restored exactly where these two violations occur.
src/app/autenticacion/iniciar-sesion/page.tsx (2)

49-49: ⚠️ Potential issue | 🟡 Minor

Remove debug console.log before merging.

-    setIsLoading(true);
-    console.log('[SignInPage] onSubmit iniciado');
+    setIsLoading(true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/autenticacion/iniciar-sesion/page.tsx` at line 49, Remove the debug
console.log in the sign-in submission flow: delete the console.log('[SignInPage]
onSubmit iniciado') call from the onSubmit handler in the SignInPage component
(page.tsx) so no debug output is emitted in production; keep any real logging
via the app's logger if needed.

29-36: ⚠️ Potential issue | 🟠 Major

Wrap useSearchParams() in <Suspense> boundary (consistent issue across all three auth pages).

Under Next.js 15, unwrapped useSearchParams() calls in 'use client' components cause static rendering failures when routes are pre-rendered. This issue exists in all three auth pages: iniciar-sesion, registro, and verificar-email.

Refactor by extracting page content into a separate component and wrapping it in <Suspense>, or add export const dynamic = 'force-dynamic'; to the page file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/autenticacion/iniciar-sesion/page.tsx` around lines 29 - 36, The
SignInPage uses useSearchParams() in a client component which breaks static
rendering; either extract the JSX into a separate client component that uses
useSearchParams() and render it inside a <Suspense> boundary in the page, or
mark the page as dynamic by adding export const dynamic = 'force-dynamic';
specifically update the SignInPage file (reference: SignInPage and
useSearchParams()) to implement one of these fixes across the auth pages
(iniciar-sesion, registro, verificar-email) so useSearchParams() is not invoked
during static pre-rendering.
src/app/autenticacion/verificar-email/page.tsx (1)

31-35: ⚠️ Potential issue | 🟠 Major

Removing the <Suspense> wrapper breaks useSearchParams() in Next.js 15.

useSearchParams() in a client component must be wrapped in a <Suspense> boundary; otherwise, when the route is statically prerendered (the default), next build fails with:

useSearchParams() should be wrapped in a suspense boundary

The refactor needs to either keep a Suspense boundary around the component or force the segment dynamic with export const dynamic = 'force-dynamic'.

🔧 Suggested minimal fix
-import { useState, useEffect } from 'react';
+import { Suspense, useState, useEffect } from 'react';
@@
-export default function VerifyEmailPage() {
+function VerifyEmailContent() {
   const router = useRouter();
   const searchParams = useSearchParams();
@@
   );
 }
+
+export default function VerifyEmailPage() {
+  return (
+    <Suspense fallback={null}>
+      <VerifyEmailContent />
+    </Suspense>
+  );
+}

Alternatively, add export const dynamic = 'force-dynamic'; at the top of the module.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/autenticacion/verificar-email/page.tsx` around lines 31 - 35, The
component VerifyEmailPage uses useSearchParams() which requires a Suspense
boundary in Next.js 15 or the route to be dynamic; fix by either ensuring this
component is rendered inside a React.Suspense wrapper (wrap the parent that
renders <VerifyEmailPage /> with <Suspense>) or make the module dynamic by
adding an export const dynamic = 'force-dynamic' at the top of the file so
useSearchParams() can run without a Suspense boundary; reference VerifyEmailPage
and useSearchParams to locate the change.
src/middleware.ts (1)

4-12: ⚠️ Potential issue | 🟡 Minor

request is now declared but unused — this rename works against the new lint rule.

The body never references request (the auth-redirect block is commented out), so after removing _-prefix ignoring in .eslintrc.json, this parameter will warn. Options:

  • Keep _request and restore argsIgnorePattern: '^_' on the rule (preferred — see .eslintrc.json comment). This is the standard convention for intentionally-unused params.
  • Drop the parameter entirely until the auth logic is re-enabled.
♻️ Option: drop the parameter until the auth block is uncommented
-export function middleware(request: NextRequest) {
+export function middleware() {
   // const sessionId = request.cookies.get('sessionId')?.value;
   //
   // if (!sessionId) {
   //   return NextResponse.redirect(new URL('/autenticacion/iniciar-sesion', request.url));
   // }
 
   return NextResponse.next();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/middleware.ts` around lines 4 - 12, The middleware function currently
declares an unused parameter request which will trigger the new lint rule;
either rename the parameter to _request (preferred to follow the intent of
argsIgnorePattern '^_' and keep the signature for future auth logic) or remove
the parameter entirely until the auth block is re-enabled—update the function
signature in the exported middleware (function middleware(...)) accordingly so
the linter no longer flags the unused parameter and the commented auth redirect
code can be restored later without signature changes.
src/app/(platform)/perfil/[id]/page.tsx (1)

448-456: ⚠️ Potential issue | 🟡 Minor

<img> will trip @next/next/no-img-element now that the suppression was removed.

Same pattern as recuperar-clave/page.tsx: the per-line disable was removed but the raw <img> remains. Consider migrating to next/image so the PWA also benefits from Next's image optimization, or reinstate a scoped disable if the URL source is arbitrary/remote and can't be listed in next.config.mjs image domains.

♻️ Proposed migration
+import Image from 'next/image';
...
                             {talk.portrait && (
                               <div className="aspect-square w-full shrink-0 overflow-hidden rounded-lg md:w-48">
-                                <img
+                                <Image
                                   src={talk.portrait}
                                   alt={talk.speakerName}
+                                  width={192}
+                                  height={192}
                                   className="h-full w-full object-cover"
                                 />
                               </div>
                             )}

Ensure any remote hosts used by talk.portrait are allow-listed under images.remotePatterns in next.config.mjs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(platform)/perfil/[id]/page.tsx around lines 448 - 456, The current
JSX uses a raw <img> for talk.portrait in the perfil/[id]/page.tsx component
which will trigger `@next/next/no-img-element` now that the rule suppression was
removed; replace the <img> with Next.js' Image component (imported from
'next/image') and use its props (src={talk.portrait}, alt={talk.speakerName},
className or layout/width/height as appropriate) to enable Next's optimization,
and ensure any remote hosts used by talk.portrait are allow-listed in
next.config.mjs via images.domains or images.remotePatterns; if you cannot
whitelist the source, reinstate a scoped eslint-disable-next-line for this
specific img usage instead.
src/actions/auth/sign-up.ts (1)

18-29: ⚠️ Potential issue | 🟡 Minor

Rest-sibling exclusion will now warn under the new no-unused-vars config.

confirmPassword exists only to strip it from ...cleanedData. Default no-unused-vars does not consider rest-siblings "used". The previous _confirmPassword alias sidestepped this; since ^_ ignoring was also removed (see .eslintrc.json), this binding (and any similar exclusion patterns in the PR) will now flag.

Prefer fixing this at the rule level (enable ignoreRestSiblings: true — see comment on .eslintrc.json) rather than re-introducing unused aliases here. The local code is fine.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/auth/sign-up.ts` around lines 18 - 29, The destructuring variable
confirmPassword is only used to strip it from the rest object (cleanedData),
which will trigger the updated no-unused-vars rule because rest-siblings aren't
ignored; instead of adding unused aliases in signUpActionSchema.parse or
changing the sign-up code, enable the ESLint option "ignoreRestSiblings: true"
for the no-unused-vars rule so that patterns like the destructure
(confirmPassword, ...cleanedData) are treated as used and no lint warning is
emitted.
src/app/autenticacion/recuperar-clave/page.tsx (1)

171-175: ⚠️ Potential issue | 🟡 Minor

Migrate <img> to next/image to resolve @next/next/no-img-element ESLint warning.

The ESLint config extends next/core-web-vitals, which enforces the no-img-element rule. The raw <img> tag at lines 171-175 will trigger this warning. Migrating to next/image is recommended for improved LCP/CLS performance.

♻️ Proposed migration to next/image
+import Image from 'next/image';
...
-          <img
-            src={resolvedTheme === 'dark' ? '/logo.webp' : '/pcn-purple.png'}
-            alt="Logo"
-            className="w-20"
-          />
+          <Image
+            src={resolvedTheme === 'dark' ? '/logo.webp' : '/pcn-purple.png'}
+            alt="Logo"
+            width={80}
+            height={80}
+            className="w-20"
+            priority
+          />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/autenticacion/recuperar-clave/page.tsx` around lines 171 - 175,
Replace the raw <img> element with Next.js Image: import Image from 'next/image'
at the top, then swap the <img src={resolvedTheme === 'dark' ? '/logo.webp' :
'/pcn-purple.png'} alt="Logo" className="w-20" /> with <Image src={resolvedTheme
=== 'dark' ? '/logo.webp' : '/pcn-purple.png'} alt="Logo" width={80} height={80}
className="w-20" /> (or provide appropriate width/height or use layout options)
so the component using resolvedTheme uses next/image and satisfies the
no-img-element rule.
src/app/(platform)/software-util/page.tsx (1)

35-70: ⚠️ Potential issue | 🟡 Minor

isPopular is destructured but never rendered here.

Dropping the underscore prefix re-surfaces the unused-var warning: the "Popular" badge block is not present in this component (it only renders the "Gratis" badge on lines 65-69), so isPopular is read from props but never used. Either remove it from the destructuring or render the badge to match src/app/(platform)/software-recomendado/page.tsx (lines 70-74), which is near-duplicate of this file.

🛠️ Option A — render the badge (consistent with software-recomendado)
             {isFree && (
               <Badge className="border-green-500/30 bg-green-500/20 text-green-700 dark:text-green-300">
                 Gratis
               </Badge>
             )}
+            {isPopular && (
+              <Badge className="border-orange-500/30 bg-orange-500/20 text-orange-700 dark:text-orange-300">
+                Popular
+              </Badge>
+            )}
🛠️ Option B — drop from destructuring
-  isFree = false,
-  isPopular = false,
+  isFree = false,
 }: SoftwareRecommendationCardProps) {

Side note: software-util/page.tsx and software-recomendado/page.tsx are nearly identical — consider consolidating to a shared component to avoid drift like this.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(platform)/software-util/page.tsx around lines 35 - 70, The prop
isPopular is read in SoftwareRecommendationCard but never used; update the JSX
inside the right-side badge container (the <div className="flex flex-wrap
gap-1">) to conditionally render a "Popular" Badge when isPopular is true
(mirroring the existing isFree handling), e.g. add a block similar to the isFree
conditional that outputs a Badge with appropriate label and styling so the
component matches the behavior in software-recomendado/page.tsx and removes the
unused-var warning.
src/app/(platform)/historia/page.tsx (1)

122-322: ⚠️ Potential issue | 🟠 Major

Migrate <img> tags to next/image or restore eslint-disable comments to prevent CI failure.

The @next/next/no-img-element rule is active (enabled via the "next" preset in .eslintrc.json), and raw <img> tags remain throughout lines 122–322 and beyond without any ESLint disable directives. This will cause ESLint to fail on CI. Migrate these to next/image for lazy-loading, automatic sizing, and LCP improvements, or restore the disable comments if that is not feasible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(platform)/historia/page.tsx around lines 122 - 322, The page
component contains many raw <img> elements inside Card, CardContent and other
JSX (e.g., ids "voluntariado-ieee", "code-warfare", "tucuman-hacking") which
triggers the `@next/next/no-img-element` ESLint rule and will fail CI; fix by
replacing each <img> with Next.js' Image component (import Image from
"next/image") and supply required props (src, alt, width/height or use
layout/fill and wrapper with position) preserving className and layout, or if
conversion is not feasible right now, add a scoped ESLint disable comment (/*
eslint-disable `@next/next/no-img-element` */) immediately where these images are
used to silence the rule; update all instances in this page.tsx (including
images inside CardContent blocks) so the linter no longer errors.
src/app/(platform)/charlas/page.tsx (1)

103-107: ⚠️ Potential issue | 🟡 Minor

Use next/image component instead of plain <img> tags to align with Next.js best practices.

The @next/next/no-img-element rule is enabled via the next/core-web-vitals ESLint config extension. Both <img> tags in this file (portrait at lines 103–107 and slides around line 172) should be migrated to the next/image <Image> component for automatic optimization and compliance with Next.js guidelines.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(platform)/charlas/page.tsx around lines 103 - 107, Replace the
plain <img> tags with Next.js' Image component: import Image from 'next/image'
at the top of the file, replace the portrait <img> (using talk.portrait and alt
built from talk.speakerName) with <Image> and provide either explicit
width/height or use fill by making the parent container position:relative;
preserve className styles like object-cover and grayscale; do the same
replacement for the slides image (the other <img> using the slide source) and
ensure any external URLs are handled (add unoptimized if necessary or configure
domains), which will satisfy the `@next/next/no-img-element` rule.
♻️ Duplicate comments (1)
src/components/ui/file-upload.tsx (1)

37-41: ⚠️ Potential issue | 🟠 Major

Same lint regression as in file-upload-public.tsx — please apply a consistent fix.

The react-hooks/exhaustive-deps suppression on the valuepreview sync effect (Line 37–41) and the @next/next/no-img-element suppression on the preview <img> (Line 126–130) were removed, but the underlying code still violates both rules:

  • preview is read inside the effect but intentionally excluded from deps to avoid a self-triggering loop.
  • A raw <img> is still used instead of next/image.

This will produce the same lint errors as the sibling component and is likely to fail next build if ESLint-on-build is enabled. Please either restore the targeted eslint-disable-next-line comments or migrate to next/image and refactor the effect (e.g., via a ref guard or derived state) so the rules pass cleanly. Keeping both FileUpload and FileUploadPublic consistent is worthwhile since they share this exact pattern.

Also applies to: 126-130

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/file-upload.tsx` around lines 37 - 41, The effect in the
FileUpload component that syncs prop value to state preview (useEffect in
file-upload.tsx) reads preview but omits it from deps, causing an eslint
react-hooks/exhaustive-deps violation, and the preview markup still uses a raw
<img> causing `@next/next/no-img-element` errors; fix by either restoring the
targeted eslint-disable-next-line comments on that specific useEffect and the
preview <img> (matching the approach used in FileUploadPublic) or refactor:
convert preview into derived state (avoid reading preview inside the effect) or
use a ref guard to prevent self-triggering loops, and replace the raw <img> with
next/image (import Image from 'next/image') to satisfy
`@next/next/no-img-element`—apply the same chosen solution to both FileUpload and
FileUploadPublic for consistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 174487c5-cc2d-495a-a6dd-5731c6d924fd

📥 Commits

Reviewing files that changed from the base of the PR and between beb2c70 and aebb841.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • public/zero-to-agent-card.png is excluded by !**/*.png
📒 Files selected for processing (143)
  • .eslintrc.json
  • .gitignore
  • .kamal/hooks/docker-setup.sample
  • .kamal/hooks/post-app-boot.sample
  • .kamal/hooks/post-deploy.sample
  • .kamal/hooks/post-proxy-reboot.sample
  • .kamal/hooks/pre-app-boot.sample
  • .kamal/hooks/pre-build.sample
  • .kamal/hooks/pre-connect.sample
  • .kamal/hooks/pre-deploy.sample
  • .kamal/hooks/pre-proxy-reboot.sample
  • next.config.mjs
  • package.json
  • prisma/migrations/20260422034035_add_external_registration_url/migration.sql
  • prisma/schema.prisma
  • public/manifest.json
  • src/actions/analytics/track-page-visit.ts
  • src/actions/auth/sign-up.ts
  • src/actions/errors/log-error.ts
  • src/actions/events/fetch-recently-added-events.ts
  • src/app/(platform)/analiticas/loading.tsx
  • src/app/(platform)/analiticas/page.tsx
  • src/app/(platform)/anuncios/loading.tsx
  • src/app/(platform)/charlas/loading.tsx
  • src/app/(platform)/charlas/page.tsx
  • src/app/(platform)/code-warfare/loading.tsx
  • src/app/(platform)/consejos/[id]/loading.tsx
  • src/app/(platform)/cursos/[courseId]/loading.tsx
  • src/app/(platform)/cursos/loading.tsx
  • src/app/(platform)/cursos/page.tsx
  • src/app/(platform)/desarrollo/loading.tsx
  • src/app/(platform)/desarrollo/page.tsx
  • src/app/(platform)/especialidades/loading.tsx
  • src/app/(platform)/especialidades/page.tsx
  • src/app/(platform)/eventos/[id]/editar/loading.tsx
  • src/app/(platform)/eventos/[id]/editar/page.tsx
  • src/app/(platform)/eventos/[id]/inscripcion/loading.tsx
  • src/app/(platform)/eventos/[id]/inscripcion/page.tsx
  • src/app/(platform)/eventos/[id]/inscripciones/loading.tsx
  • src/app/(platform)/eventos/[id]/inscripciones/page.tsx
  • src/app/(platform)/eventos/[id]/loading.tsx
  • src/app/(platform)/eventos/[id]/page.tsx
  • src/app/(platform)/eventos/loading.tsx
  • src/app/(platform)/eventos/nuevo/loading.tsx
  • src/app/(platform)/galeria/loading.tsx
  • src/app/(platform)/historia/loading.tsx
  • src/app/(platform)/historia/page.tsx
  • src/app/(platform)/home-client-side.tsx
  • src/app/(platform)/influencers/loading.tsx
  • src/app/(platform)/layout.tsx
  • src/app/(platform)/lectura/loading.tsx
  • src/app/(platform)/lectura/page.tsx
  • src/app/(platform)/monitoreo/errors-client.tsx
  • src/app/(platform)/monitoreo/loading.tsx
  • src/app/(platform)/monitoreo/logs-client.tsx
  • src/app/(platform)/monitoreo/page.tsx
  • src/app/(platform)/music/loading.tsx
  • src/app/(platform)/notificaciones/loading.tsx
  • src/app/(platform)/notificaciones/notifications-client.tsx
  • src/app/(platform)/notificaciones/page.tsx
  • src/app/(platform)/page.tsx
  • src/app/(platform)/perfil/[id]/loading.tsx
  • src/app/(platform)/perfil/[id]/page.tsx
  • src/app/(platform)/perfil/loading.tsx
  • src/app/(platform)/podcast/loading.tsx
  • src/app/(platform)/podcast/page.tsx
  • src/app/(platform)/preguntas-frecuentes/loading.tsx
  • src/app/(platform)/preguntas-frecuentes/page.tsx
  • src/app/(platform)/software-recomendado/loading.tsx
  • src/app/(platform)/software-recomendado/page.tsx
  • src/app/(platform)/software-util/loading.tsx
  • src/app/(platform)/software-util/page.tsx
  • src/app/(platform)/testimonials.tsx
  • src/app/(platform)/testimonios/[id]/loading.tsx
  • src/app/(platform)/testimonios/[id]/page.tsx
  • src/app/(platform)/testimonios/loading.tsx
  • src/app/(platform)/testimonios/testimonials-client.tsx
  • src/app/(platform)/usuarios/loading.tsx
  • src/app/(platform)/visitas/loading.tsx
  • src/app/(platform)/visitas/page.tsx
  • src/app/(platform)/zero-to-agent-sponsors/loading.tsx
  • src/app/(platform)/zero-to-agent-sponsors/page.tsx
  • src/app/autenticacion/iniciar-sesion/page.tsx
  • src/app/autenticacion/recuperar-clave/page.tsx
  • src/app/autenticacion/registro/page.tsx
  • src/app/autenticacion/verificar-email/page.tsx
  • src/app/cowork/page.tsx
  • src/app/layout.tsx
  • src/app/meetup/page.tsx
  • src/components/advises/advise-card.tsx
  • src/components/advises/delete-advise-dialog.tsx
  • src/components/advises/edit-advise-dialog.tsx
  • src/components/announcements/announcement-card.tsx
  • src/components/announcements/announcement-form.tsx
  • src/components/announcements/delete-announcement-dialog.tsx
  • src/components/announcements/event-announcements.tsx
  • src/components/comunity/user-card.tsx
  • src/components/especialidades/table-of-contents.tsx
  • src/components/events/delete-event-dialog.tsx
  • src/components/events/event-card.tsx
  • src/components/events/event-detail-client.tsx
  • src/components/events/event-flyer.tsx
  • src/components/events/event-form.tsx
  • src/components/events/event-photos.tsx
  • src/components/events/register-event-button.tsx
  • src/components/home/asz-software-logo.tsx
  • src/components/home/bowery-logo.tsx
  • src/components/home/discord-card.tsx
  • src/components/home/recently-added-events-section.tsx
  • src/components/home/sponsors-section.tsx
  • src/components/influencers/influencer-card.tsx
  • src/components/landing/activities.tsx
  • src/components/landing/discord.tsx
  • src/components/landing/lightning-talks.tsx
  • src/components/landing/motivation.tsx
  • src/components/landing/platform-features-large.tsx
  • src/components/landing/team.tsx
  • src/components/landing/testimonial.tsx
  • src/components/photo-gallery/photo-card.tsx
  • src/components/photo-gallery/photo-dialog.tsx
  • src/components/photo-gallery/search-bar.tsx
  • src/components/photo-gallery/sort-selector.tsx
  • src/components/profile/language-coin.tsx
  • src/components/profile/language-coins-container.tsx
  • src/components/profile/profile-form.tsx
  • src/components/skeletons/page-skeletons.tsx
  • src/components/testimonials/testimonial-card.tsx
  • src/components/ui/app-sidebar.tsx
  • src/components/ui/button.tsx
  • src/components/ui/carousel.tsx
  • src/components/ui/content-card.tsx
  • src/components/ui/file-upload-public.tsx
  • src/components/ui/file-upload.tsx
  • src/components/ui/image-carousel.tsx
  • src/components/ui/nav-main.tsx
  • src/components/ui/nav-projects.tsx
  • src/components/ui/pagination.tsx
  • src/components/ui/platform-header.tsx
  • src/components/ui/sidebar.tsx
  • src/components/ui/text-generate-effect.tsx
  • src/components/ui/vortex.tsx
  • src/middleware.ts
  • src/schemas/event-schema.ts
💤 Files with no reviewable changes (57)
  • src/components/profile/language-coin.tsx
  • src/components/ui/image-carousel.tsx
  • src/app/(platform)/eventos/[id]/loading.tsx
  • prisma/migrations/20260422034035_add_external_registration_url/migration.sql
  • src/app/(platform)/charlas/loading.tsx
  • src/app/(platform)/anuncios/loading.tsx
  • src/app/(platform)/testimonials.tsx
  • src/app/(platform)/code-warfare/loading.tsx
  • src/app/(platform)/notificaciones/loading.tsx
  • src/components/landing/lightning-talks.tsx
  • src/components/ui/content-card.tsx
  • src/app/(platform)/cursos/loading.tsx
  • src/components/landing/testimonial.tsx
  • src/app/(platform)/consejos/[id]/loading.tsx
  • src/app/cowork/page.tsx
  • src/app/(platform)/perfil/loading.tsx
  • src/app/(platform)/especialidades/loading.tsx
  • src/components/ui/button.tsx
  • src/app/(platform)/influencers/loading.tsx
  • src/app/(platform)/analiticas/loading.tsx
  • src/components/home/discord-card.tsx
  • src/app/(platform)/podcast/loading.tsx
  • src/app/(platform)/eventos/nuevo/loading.tsx
  • src/app/(platform)/lectura/loading.tsx
  • src/app/(platform)/testimonios/loading.tsx
  • src/app/(platform)/desarrollo/loading.tsx
  • src/app/(platform)/software-util/loading.tsx
  • src/actions/events/fetch-recently-added-events.ts
  • src/app/(platform)/zero-to-agent-sponsors/loading.tsx
  • src/app/(platform)/galeria/loading.tsx
  • src/app/(platform)/cursos/[courseId]/loading.tsx
  • src/app/meetup/page.tsx
  • src/components/landing/platform-features-large.tsx
  • src/app/(platform)/preguntas-frecuentes/loading.tsx
  • src/app/(platform)/eventos/loading.tsx
  • src/app/(platform)/eventos/[id]/editar/loading.tsx
  • src/components/landing/motivation.tsx
  • src/app/(platform)/historia/loading.tsx
  • src/components/landing/discord.tsx
  • src/components/home/asz-software-logo.tsx
  • src/app/(platform)/software-recomendado/loading.tsx
  • src/app/(platform)/usuarios/loading.tsx
  • src/components/landing/activities.tsx
  • src/app/(platform)/eventos/[id]/inscripciones/loading.tsx
  • src/schemas/event-schema.ts
  • src/components/events/event-flyer.tsx
  • src/components/home/recently-added-events-section.tsx
  • src/components/home/bowery-logo.tsx
  • src/app/(platform)/music/loading.tsx
  • src/app/(platform)/zero-to-agent-sponsors/page.tsx
  • src/app/(platform)/perfil/[id]/loading.tsx
  • src/app/(platform)/testimonios/[id]/loading.tsx
  • src/components/events/event-photos.tsx
  • src/app/(platform)/monitoreo/loading.tsx
  • src/app/(platform)/visitas/loading.tsx
  • src/app/(platform)/eventos/[id]/inscripcion/loading.tsx
  • src/components/skeletons/page-skeletons.tsx

Comment thread .eslintrc.json
Comment on lines 3 to 5
"rules": {
"no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }]
"no-unused-vars": "warn"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does next/core-web-vitals include @typescript-eslint/no-unused-vars for TypeScript projects?

💡 Result:

No. Next.js’s next/core-web-vitals config is not the TypeScript ruleset; TypeScript-specific unused-vars behavior comes from eslint-config-next/typescript (based on plugin:@typescript-eslint/recommended), not from next/core-web-vitals. Evidence: - The Next.js ESLint docs describe eslint-config-next/core-web-vitals as upgrading rules related to Core Web Vitals, and separately describe eslint-config-next/typescript as adding TypeScript-specific lint rules from typescript-eslint. - A Next.js GitHub discussion confirms that @typescript-eslint/no-unused-vars must be configured/installed separately; it is not implied by core-web-vitals alone. Practical takeaway for TypeScript projects: if you want (or need) @typescript-eslint/no-unused-vars, ensure you’re also including next/typescript (or otherwise adding the @typescript-eslint plugin/config).

Citations:


🏁 Script executed:

# Find and read the .eslintrc.json file
fd -type f -name ".eslintrc.json" -exec cat {} \;

Repository: programaconnosotros/pcn-website

Length of output: 246


🏁 Script executed:

# Check if there are TypeScript files in the project
fd -type f \( -name "*.ts" -o -name "*.tsx" \) | head -20

Repository: programaconnosotros/pcn-website

Length of output: 246


🏁 Script executed:

# Check if next/typescript is mentioned anywhere in config or package.json
rg "typescript" --type json

Repository: programaconnosotros/pcn-website

Length of output: 111


🏁 Script executed:

# Find and read .eslintrc.json
cat .eslintrc.json

Repository: programaconnosotros/pcn-website

Length of output: 184


🏁 Script executed:

# Check for TypeScript files (using correct fd syntax)
fd "\.(ts|tsx)$" | head -20

Repository: programaconnosotros/pcn-website

Length of output: 880


🏁 Script executed:

# Check package.json for eslint and typescript-eslint dependencies
cat package.json | grep -A 20 '"dependencies":\|"devDependencies":'

Repository: programaconnosotros/pcn-website

Length of output: 1628


🏁 Script executed:

# Check for `@typescript-eslint` in all dependencies
grep -i "@typescript-eslint" package.json

Repository: programaconnosotros/pcn-website

Length of output: 57


🏁 Script executed:

# Also check the full package.json to see all devDependencies
cat package.json | grep -E "^\s+\"@" | head -40

Repository: programaconnosotros/pcn-website

Length of output: 1377


🏁 Script executed:

# Check if eslint-plugin-react or other eslint plugins are installed
cat package.json | grep "eslint"

Repository: programaconnosotros/pcn-website

Length of output: 171


🏁 Script executed:

# Check eslint-config-next documentation to see what it includes
# Let's look at node_modules if it exists
find node_modules -name "eslint-config-next" -type d 2>/dev/null | head -1

Repository: programaconnosotros/pcn-website

Length of output: 183


🏁 Script executed:

# Search for any mention of typescript in eslint config files
rg "typescript" -t json -t js

Repository: programaconnosotros/pcn-website

Length of output: 111


🏁 Script executed:

# Check if there's a next.config file that might affect ESLint
cat next.config.* 2>/dev/null || echo "No next.config found"

Repository: programaconnosotros/pcn-website

Length of output: 682


🏁 Script executed:

# Check what eslint-config-next includes - look at the base config
cat node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/eslint-config-next/index.js 2>/dev/null | head -100

Repository: programaconnosotros/pcn-website

Length of output: 3067


🏁 Script executed:

# Look for TypeScript-specific config in eslint-config-next
ls -la node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/eslint-config-next/

Repository: programaconnosotros/pcn-website

Length of output: 496


🏁 Script executed:

# Check if `@typescript-eslint` is available in node_modules at all
find node_modules -name "@typescript-eslint" -type d 2>/dev/null | head -1

Repository: programaconnosotros/pcn-website

Length of output: 183


🏁 Script executed:

# Read the rest of the eslint-config-next index.js to see the overrides for TypeScript
cat node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/eslint-config-next/index.js | tail -100

Repository: programaconnosotros/pcn-website

Length of output: 2687


🏁 Script executed:

# Check what's in `@typescript-eslint` directory
ls -la node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/@typescript-eslint/

Repository: programaconnosotros/pcn-website

Length of output: 353


🏁 Script executed:

# Check the core-web-vitals.js file that is being extended
cat node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/eslint-config-next/core-web-vitals.js

Repository: programaconnosotros/pcn-website

Length of output: 168


🏁 Script executed:

# Search the entire eslint-config-next for any mention of no-unused-vars
rg "no-unused-vars" node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/eslint-config-next/

Repository: programaconnosotros/pcn-website

Length of output: 57


🏁 Script executed:

# Search for `@typescript-eslint/no-unused-vars` in the config
rg "@typescript-eslint/no-unused-vars" node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/eslint-config-next/

Repository: programaconnosotros/pcn-website

Length of output: 57


🏁 Script executed:

# Check if there's a typescript.js config in eslint-config-next
ls node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/eslint-config-next/

Repository: programaconnosotros/pcn-website

Length of output: 139


🏁 Script executed:

# Check what `@typescript-eslint` packages are actually available as transitive deps
find node_modules -path "*@typescript-eslint/eslint-plugin*" -type d 2>/dev/null | head -5

Repository: programaconnosotros/pcn-website

Length of output: 57


🏁 Script executed:

# Look at the middleware.ts file mentioned in the review to understand the concern
cat src/middleware.ts 2>/dev/null || echo "File not found"

Repository: programaconnosotros/pcn-website

Length of output: 498


🏁 Script executed:

# Check if `@typescript-eslint/eslint-plugin` is available
ls node_modules/.pnpm/ | grep typescript-eslint

Repository: programaconnosotros/pcn-website

Length of output: 669


🏁 Script executed:

# Get a complete list of what's installed related to `@typescript-eslint`
find node_modules/.pnpm -maxdepth 1 -name "*typescript-eslint*" -type d | sed 's|.*/||' | sort

Repository: programaconnosotros/pcn-website

Length of output: 669


🏁 Script executed:

# Check if eslint-config-next has a typescript.js export in package.json
cat node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/eslint-config-next/package.json | grep -A 20 "exports"

Repository: programaconnosotros/pcn-website

Length of output: 57


🏁 Script executed:

# Look for what Next.js docs recommend for TypeScript ESLint configs
# Check if there's documentation or examples in the node_modules
find node_modules/.pnpm/eslint-config-next@14.2.4_eslint@8.57.0_typescript@5.5.4/node_modules/eslint-config-next -name "*.md" -o -name "README*"

Repository: programaconnosotros/pcn-website

Length of output: 57


Install @typescript-eslint/eslint-plugin or use a different approach; @typescript-eslint/no-unused-vars cannot be used without the plugin.

The recommended rule change has a critical flaw:

  1. Missing plugin dependency. The proposed rule @typescript-eslint/no-unused-vars requires the @typescript-eslint/eslint-plugin package, which is not installed in this project. Only @typescript-eslint/parser is available as a transitive dependency. Without the plugin, ESLint will error on the undefined rule.

  2. next/typescript doesn't exist in eslint-config-next 14.2.4. The web search result mentioned TypeScript rules coming from next/typescript, but this config variant is not available in the installed version. The base Next.js config includes only the parser and settings for .ts/.tsx files, not the TypeScript linting rules.

To fix the rule issue in a TypeScript project, either:

  • Install @typescript-eslint/eslint-plugin and use the corrected config from the review (with the ^_ ignore pattern), or
  • Keep the built-in no-unused-vars rule and add ignore patterns locally in the config
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.eslintrc.json around lines 3 - 5, The current change attempts to use
`@typescript-eslint/no-unused-vars` but the `@typescript-eslint/eslint-plugin` is
not installed and eslint-config-next does not expose next/typescript in this
project version; either install the plugin (add `@typescript-eslint/eslint-plugin`
to devDependencies) and then replace the rule with
`@typescript-eslint/no-unused-vars` and configure its ignore pattern (e.g. '^_')
in the rules block, or keep the built-in no-unused-vars rule and configure its
ignore patterns (varsIgnorePattern/argsIgnorePattern set to '^_') in
.eslintrc.json so ESLint does not error on undefined rule names.

Comment on lines +31 to +43
const sessionId = cookies().get('sessionId')?.value;
let session: (Session & { user: User }) | null = null;

if (sessionId) {
session = await prisma.session.findUnique({
where: {
id: sessionId,
},
include: {
user: true,
},
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify there is no middleware/layout already gating /analiticas so that the missing guard here is not redundant.
rg -nP --type=ts -C3 '\banaliticas\b' -g '!**/node_modules/**'
fd -i middleware.ts
fd -i 'layout.tsx' src/app

Repository: programaconnosotros/pcn-website

Length of output: 675


🏁 Script executed:

#!/bin/bash
# Check middleware.ts to see if it gates /analiticas
cat src/middleware.ts

Repository: programaconnosotros/pcn-website

Length of output: 498


🏁 Script executed:

#!/bin/bash
# Check the full analiticas/page.tsx to verify cookies() and session usage
wc -l src/app/\(platform\)/analiticas/page.tsx

Repository: programaconnosotros/pcn-website

Length of output: 118


🏁 Script executed:

#!/bin/bash
# Get Next.js version from package.json
rg '"next"' package.json

Repository: programaconnosotros/pcn-website

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Read the full analiticas/page.tsx to verify cookies() and session usage
cat -n src/app/\(platform\)/analiticas/page.tsx | head -100

Repository: programaconnosotros/pcn-website

Length of output: 2899


🏁 Script executed:

#!/bin/bash
# Check if 'session' variable is referenced anywhere after line 43
cat -n src/app/\(platform\)/analiticas/page.tsx | tail -n +31

Repository: programaconnosotros/pcn-website

Length of output: 15880


cookies() is not awaited (critical) — must fix before session logic. Also, session is loaded but never used to gate this admin view (major).

  • Line 31 (critical): cookies() is async in Next.js and requires await. Without it, sessionId becomes a Promise, .get(...) fails, and the if-branch never runs, leaving the page unprotected.
  • Lines 32–43 (major): The session variable is fetched from the database but never referenced in the rendered output. This page exposes sensitive aggregate data (user list with emails, totals, engagement metrics, etc.) with no authorization check. Either wire session to an auth gate (verify admin role and redirect/404 if unauthorized), or remove the unused database query.

Middleware does not protect /analiticas (matcher only covers /perfil/:path*), so the authorization check must happen in the page component.

🛡️ Suggested fix — await cookies() and enforce auth gate
-  const sessionId = cookies().get('sessionId')?.value;
-  let session: (Session & { user: User }) | null = null;
-
-  if (sessionId) {
-    session = await prisma.session.findUnique({
-      where: {
-        id: sessionId,
-      },
-      include: {
-        user: true,
-      },
-    });
-  }
+  const cookieStore = await cookies();
+  const sessionId = cookieStore.get('sessionId')?.value;
+  const session = sessionId
+    ? await prisma.session.findUnique({
+        where: { id: sessionId },
+        include: { user: true },
+      })
+    : null;
+
+  if (!session /* || !isAdmin(session.user) */) {
+    redirect('/');
+  }
📝 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 sessionId = cookies().get('sessionId')?.value;
let session: (Session & { user: User }) | null = null;
if (sessionId) {
session = await prisma.session.findUnique({
where: {
id: sessionId,
},
include: {
user: true,
},
});
}
const cookieStore = await cookies();
const sessionId = cookieStore.get('sessionId')?.value;
const session = sessionId
? await prisma.session.findUnique({
where: { id: sessionId },
include: { user: true },
})
: null;
if (!session /* || !isAdmin(session.user) */) {
redirect('/');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(platform)/analiticas/page.tsx around lines 31 - 43, cookies() is
async so change how sessionId is read by awaiting cookies() (use: const
cookieStore = await cookies(); const sessionId =
cookieStore.get('sessionId')?.value) to avoid a Promise being used; then use the
already-loaded session (from prisma.session.findUnique) to enforce an
authorization gate in this page component: verify session exists and that
session.user has the admin role (or required flag) before rendering the
analytics view, and if not authorized perform a redirect or return
notFound/unauthorized response instead of rendering sensitive data; remove the
unused query if you choose not to gate the page.

Comment on lines 3 to 13
type Props = {
params: { id: string };
searchParams: Promise<{ autoRegister?: string }>;
};

/**
* Esta página ahora solo redirige a la página de detalle del evento.
* La inscripción se maneja directamente desde la página de detalle.
*/
const EventRegistrationPage = async ({ params, searchParams }: Props) => {
const id = params.id;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In Next.js 15, is the params prop of an App Router page a Promise that must be awaited?

💡 Result:

Yes, in Next.js 15, the params prop of an App Router page (and layouts, route handlers, etc.) is a Promise that must be awaited before accessing its properties. This is a breaking change introduced in Next.js 15 to support better async rendering, streaming, and caching optimizations. In server components, declare the page as async and await params: export default async function Page({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; return

{slug}
; } In client components ('use client'), use React's use hook or useParams from 'next/navigation' (which returns a plain object). A codemod is available for migration: npx @next/codemod@canary next-async-request-api . Synchronous access still works temporarily for compatibility but triggers warnings and will be removed in future versions.

Citations:


🏁 Script executed:

head -25 src/app/\(platform\)/eventos/\[id\]/inscripcion/page.tsx

Repository: programaconnosotros/pcn-website

Length of output: 823


params must be a Promise in Next.js 15 and awaited before use.

In Next.js 15's App Router, dynamic route params is asynchronous (same as searchParams, which you correctly type as Promise on line 5). Typing params as a plain object and accessing params.id synchronously on line 13 will trigger development warnings and fail in future versions.

Change line 3 from params: { id: string }; to params: Promise<{ id: string }>; and update line 13 to destructure and await: const { id } = await params;.

Proposed fix
 type Props = {
-  params: { id: string };
+  params: Promise<{ id: string }>;
   searchParams: Promise<{ autoRegister?: string }>;
 };

 const EventRegistrationPage = async ({ params, searchParams }: Props) => {
-  const id = params.id;
-  const params_await = await searchParams;
-  const autoRegister = params_await.autoRegister === 'true';
+  const { id } = await params;
+  const { autoRegister: autoRegisterParam } = await searchParams;
+  const autoRegister = autoRegisterParam === 'true';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(platform)/eventos/[id]/inscripcion/page.tsx around lines 3 - 13,
The Props type and EventRegistrationPage should treat params as asynchronous:
change the Props definition so params is Promise<{ id: string }> (instead of {
id: string }), and inside EventRegistrationPage await params and destructure the
id (e.g., const { id } = await params) before using it; update any usages of
params.id to use the awaited id.

@@ -1,3 +1,5 @@
'use server';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm no other page.tsx / layout.tsx in the app router unexpectedly declares 'use server' at the top of the file.
fd -t f -e tsx -e ts . src/app | xargs -I{} sh -c 'head -1 "{}" | grep -q "use server" && echo "ROOT-LEVEL use server in: {}"'

Repository: programaconnosotros/pcn-website

Length of output: 287


🌐 Web query:

Next.js 'use server' directive page.tsx app router Server Component

💡 Result:

In Next.js App Router, page.tsx files are Server Components by default and do not require a 'use server' directive. The 'use server' directive is specifically for marking Server Functions (also called Server Actions), which are asynchronous functions executed on the server, typically for handling mutations like form submissions. Server Components (e.g., default page.tsx) render on the server for data fetching and static UI, with no JS sent to the client. To add interactivity, use 'use client' for Client Components. 'use server' usage: - At top of file: Marks all exported functions as Server Functions (e.g., actions.ts). - Inline in function: Marks that function as a Server Function within a Server Component. Example in page.tsx (Server Component): tsx import { EditPost } from './edit-post' import { revalidatePath } from 'next/cache' export default async function PostPage({ params }: { params: { id: string } }) { const post = await getPost(params.id) async function updatePost(formData: FormData) { 'use server' await savePost(params.id, formData) revalidatePath(/posts/${params.id}) } return } Do not add 'use server' at the top of page.tsx, as pages are already Server Components; it's unnecessary and intended for Server Functions.

Citations:


Critical: 'use server' at the top of a page.tsx is incorrect and must be removed.

Per Next.js documentation, 'use server' at the top of a file marks all exports as Server Functions/Actions intended for handling mutations. Page components are Server Components by default and must export a default component function; placing this directive at the module top would incorrectly attempt to convert the entire module into a Server Actions module, which is incompatible with page routing. The 'use server' directive should only be used inline within async functions within a Server Component, or at the top of dedicated Server Actions files.

Remove the directive entirely from this file.

Proposed fix
-'use server';
-
 import { Suspense } from 'react';
📝 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
'use server';
import { Suspense } from 'react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(platform)/eventos/[id]/page.tsx at line 1, Remove the top-level
'use server' directive from this page module — delete the standalone "'use
server';" so the default page component remains a normal Server Component; if
you need server-only actions, move the 'use server' directive inside the
specific async action/function (not at module top) and keep the default export
(the page component) unchanged.

Comment on lines +40 to +52
const sessionId = cookies().get('sessionId')?.value;
let session: (Session & { user: User }) | null = null;

if (sessionId) {
session = await prisma.session.findUnique({
where: {
id: sessionId,
},
include: {
user: true,
},
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In Next.js 15 App Router, is cookies() from next/headers synchronous or asynchronous?

💡 Result:

In Next.js 15 App Router, cookies from next/headers is asynchronous. It returns a Promise that must be awaited to access the cookie store, as shown in official examples: const cookieStore = await cookies. This change was introduced in Next.js 15 (v15.0.0-RC) to better handle request-specific data and opt routes into dynamic rendering. Prior to version 15, it was synchronous. For backwards compatibility, synchronous access still works in Next.js 15 but triggers warnings and will be deprecated in future versions. A codemod is available to automate migration: npx @next/codemod@canary next-async-request-api ..

Citations:


🏁 Script executed:

# First, check if the file exists and its size
wc -l src/app/\(platform\)/podcast/page.tsx

Repository: programaconnosotros/pcn-website

Length of output: 114


🏁 Script executed:

# Read the file to examine the code at lines 40-52 and surrounding context
cat -n src/app/\(platform\)/podcast/page.tsx | head -70

Repository: programaconnosotros/pcn-website

Length of output: 2818


🏁 Script executed:

# Search for any usage of the 'session' variable in the file
rg 'session' src/app/\(platform\)/podcast/page.tsx -n

Repository: programaconnosotros/pcn-website

Length of output: 292


🏁 Script executed:

# Read the remaining JSX to check if session is used
cat -n src/app/\(platform\)/podcast/page.tsx | tail -45

Repository: programaconnosotros/pcn-website

Length of output: 2265


cookies() must be awaited in Next.js 15, and the loaded session is never used.

Two confirmed issues in this block:

  1. Line 40 (critical): In Next.js 15, cookies() is asynchronous and returns a Promise. Calling .get('sessionId')?.value directly on the promise without awaiting will yield undefined at runtime, so sessionId remains falsy and the if (sessionId) branch never executes. Use await cookies().

  2. Lines 41–52 (major): The session variable is loaded from Prisma but never referenced anywhere in the JSX below—it's dead code that incurs a database round-trip on every request.

Either remove the entire unused block or fix line 40 with const sessionId = (await cookies()).get('sessionId')?.value and actually use session in the page render.

Proposed fixes

Option A — remove unused code:

 const PodcastPage = async () => {
-  const sessionId = cookies().get('sessionId')?.value;
-  let session: (Session & { user: User }) | null = null;
-
-  if (sessionId) {
-    session = await prisma.session.findUnique({
-      where: {
-        id: sessionId,
-      },
-      include: {
-        user: true,
-      },
-    });
-  }
-
   return (

Option B — fix and use:

-  const sessionId = cookies().get('sessionId')?.value;
+  const sessionId = (await cookies()).get('sessionId')?.value;
   let session: (Session & { user: User }) | null = 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
const sessionId = cookies().get('sessionId')?.value;
let session: (Session & { user: User }) | null = null;
if (sessionId) {
session = await prisma.session.findUnique({
where: {
id: sessionId,
},
include: {
user: true,
},
});
}
const sessionId = (await cookies()).get('sessionId')?.value;
let session: (Session & { user: User }) | null = null;
if (sessionId) {
session = await prisma.session.findUnique({
where: {
id: sessionId,
},
include: {
user: true,
},
});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(platform)/podcast/page.tsx around lines 40 - 52, The cookies() call
is async in Next.js 15 and must be awaited, and the loaded session (variable
session from prisma.session.findUnique) is never used; either remove this unused
DB fetch block entirely or change to await cookies() (use (await
cookies()).get('sessionId')?.value to populate sessionId) and then actually use
the session value in the page render (or pass it into child components) to
justify the prisma.session.findUnique call; update references to sessionId,
session, and prisma.session.findUnique accordingly and remove the DB call if you
choose to discard session usage.

Comment on lines +24 to +36
const sessionId = cookies().get('sessionId')?.value;
let session: (Session & { user: User }) | null = null;

if (sessionId) {
session = await prisma.session.findUnique({
where: {
id: sessionId,
},
include: {
user: true,
},
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Dead Prisma query on every FAQ request — remove or actually use the session.

session is fetched but never referenced in the returned JSX. This adds a prisma.session.findUnique round-trip on every request to a static-looking page for no functional benefit, hurting latency and DB load. Either remove the block, or wire the result into the rendering (e.g., to personalize the intro/auth state).

🛡️ Suggested removal if not needed
-import { cookies } from 'next/headers';
-import prisma from '@/lib/prisma';
-import { Session, User } from '@prisma/client';
 import { Heading2 } from '@/components/ui/heading-2';
@@
 const FAQPage = async () => {
-  const sessionId = cookies().get('sessionId')?.value;
-  let session: (Session & { user: User }) | null = null;
-
-  if (sessionId) {
-    session = await prisma.session.findUnique({
-      where: {
-        id: sessionId,
-      },
-      include: {
-        user: true,
-      },
-    });
-  }
-
   const faqs = [
📝 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 sessionId = cookies().get('sessionId')?.value;
let session: (Session & { user: User }) | null = null;
if (sessionId) {
session = await prisma.session.findUnique({
where: {
id: sessionId,
},
include: {
user: true,
},
});
}
const faqs = [
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(platform)/preguntas-frecuentes/page.tsx around lines 24 - 36, The
code fetches session via sessionId and calls prisma.session.findUnique
(sessionId, session) but never uses the result; remove the entire conditional
block that references sessionId/session and the prisma.session.findUnique call
to avoid the unnecessary DB round-trip, or alternatively wire the fetched
session into the page render (e.g., pass session to the component props or use
it in the JSX to show user-specific content or auth state) so the query has a
purpose; update any references to session/sessionId accordingly and ensure
cookies().get('sessionId') usage is removed if you eliminate the query.

Comment thread src/app/layout.tsx
Comment on lines +22 to +28
manifest: '/manifest.json',
themeColor: '#7c3aed',
appleWebApp: {
capable: true,
statusBarStyle: 'default',
title: 'PCN',
},

Copy link
Copy Markdown
Contributor

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:

cd /tmp && find . -name "package.json" -path "*/src/*" -prune -o -name "package.json" -type f -print | head -5

Repository: programaconnosotros/pcn-website

Length of output: 57


🏁 Script executed:

cat -n package.json | grep -A 2 -B 2 "next"

Repository: programaconnosotros/pcn-website

Length of output: 1014


🏁 Script executed:

cat -n src/app/layout.tsx | sed -n '1,70p'

Repository: programaconnosotros/pcn-website

Length of output: 2676


🏁 Script executed:

web_search
Query: Next.js 14 metadata themeColor deprecated viewport export

Repository: programaconnosotros/pcn-website

Length of output: 171


🏁 Script executed:

cat -n src/app/layout.tsx | sed -n '67,150p'

Repository: programaconnosotros/pcn-website

Length of output: 734


🌐 Web query:

Next.js 14 metadata themeColor deprecated viewport export

💡 Result:

In Next.js 14 (App Router), the legacy themeColor, colorScheme, and viewport options inside metadata / generateMetadata are deprecated. To set the browser chrome color (the <meta name="theme-color" ...> tag) you should use the viewport export instead. Deprecated (in metadata / generateMetadata): - metadata.themeColor - metadata.colorScheme - metadata.viewport Use instead (set theme color via the viewport export): 1) Static example (e.g. in app/layout.tsx or app/page.tsx): import type { Viewport } from 'next' export const viewport: Viewport = { themeColor: 'black', } 2) If you need media-specific theme colors (e.g. prefers-color-scheme): import type { Viewport } from 'next' export const viewport: Viewport = { themeColor: [ { media: '(prefers-color-scheme: light)', color: 'cyan' }, { media: '(prefers-color-scheme: dark)', color: 'black' }, ], } If you were getting warnings like “Unsupported metadata themeColor is configured in metadata export. Please move it to viewport export instead.”, this viewport export is the fix. Note: you can alternatively use generateViewport for dynamic values (still returning a Viewport object), but the key point is: do not put themeColor under metadata; move it to viewport.

Citations:


Move themeColor from metadata to a viewport export — it's deprecated in Next.js 14 and won't render otherwise.

In Next.js 14, themeColor in the metadata export is deprecated. Leaving it there causes Next.js to skip emitting the <meta name="theme-color"> tag entirely and logs: "Unsupported metadata themeColor is configured in metadata export. Please move it to viewport export instead." The #7c3aed color won't apply to the browser chrome on mobile.

appleWebApp and manifest stay in metadata; only themeColor moves.

Proposed fix
 import type { Metadata } from 'next';
+import type { Viewport } from 'next';
 import { cookies } from 'next/headers';

+export const viewport: Viewport = {
+  themeColor: '#7c3aed',
+};
+
 export const metadata: Metadata = {
   title: {
     default: 'programaConNosotros',
     template: '%s - PCN',
   },
   description: 'Comunidad de apasionados por la ingeniería de software.',
   icons: [{ rel: 'icon', url: '/favicon.ico' }],
   manifest: '/manifest.json',
-  themeColor: '#7c3aed',
   appleWebApp: {
     capable: true,
     statusBarStyle: 'default',
     title: 'PCN',
   },

Or run: npx @next/codemod metadata-to-viewport-export .

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/layout.tsx` around lines 22 - 28, The metadata export currently
contains themeColor which is deprecated in Next.js 14; remove themeColor from
the metadata export and add it under a separate exported viewport object (export
const viewport) so Next emits <meta name="theme-color">; keep manifest and
appleWebApp in metadata unchanged and use the same color value '#7c3aed' when
moving themeColor to the viewport export (refer to the metadata export and
create/update the viewport export in the same file).

Comment thread src/app/layout.tsx
Comment on lines +54 to +65
let user: User | null = null;

if (sessionId) {
const session = await prisma.session.findUnique({
where: { id: sessionId },
include: { user: true },
});

if (session) {
user = session.user;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Dead code: user is fetched on every request but never used.

user is declared, populated from a Prisma lookup, but never read or passed anywhere in the returned JSX (no user prop on ThemeProvider, ReactQueryProvider, or Toaster, and {children} receives nothing). This adds a full session.findUnique({ include: { user: true } }) round-trip to every request that hits the root layout — including static/anonymous routes — for data that has no consumer.

Either:

  • Remove the block entirely, or
  • Propagate user through a context provider / prop so downstream components can use it (avoiding the pattern in the AI summary where each platform page re-queries session+user independently).

Also note: in Next.js 15 the root layout is re-rendered on navigation; this query will execute more often than you may expect.

🧹 If removing
-  const defaultOpen = (await cookies().get('sidebar_state')?.value) === 'true';
-  const sessionId = await cookies().get('sessionId')?.value;
-
-  let user: User | null = null;
-
-  if (sessionId) {
-    const session = await prisma.session.findUnique({
-      where: { id: sessionId },
-      include: { user: true },
-    });
-
-    if (session) {
-      user = session.user;
-    }
-  }
-
+  const defaultOpen = cookies().get('sidebar_state')?.value === 'true';

And drop the now-unused prisma and User imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/layout.tsx` around lines 54 - 65, The code fetches a User into local
variable `user` via `prisma.session.findUnique` (using `sessionId`) but never
uses it; either remove the entire session lookup block and also remove the
now-unused `prisma` and `User` imports, or thread `user` into the layout JSX via
an appropriate prop/context (e.g., pass `user` into your
`ThemeProvider`/`ReactQueryProvider` or create a `UserProvider` so downstream
components can consume it) to avoid redundant DB round-trips from the root
layout which re-renders frequently; update imports and consumers accordingly and
ensure `sessionId` handling remains correct.

Comment on lines +6 to +10
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { ShareDialog } from '@/components/photo-gallery/share-dialog';
import { formatDate } from '@/lib/date-formatter';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unused imports: DialogTitle and formatDate.

Both DialogTitle (line 6) and formatDate (line 10) are only referenced inside the commented-out JSX block on lines 187–190, so they're effectively unused. Depending on your ESLint config (@typescript-eslint/no-unused-vars / unused-imports) and the repo's tsconfig (noUnusedLocals), this will fail lint or type-check.

Either re-enable the commented block (recommended — see the a11y note below) or drop the imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/photo-gallery/photo-dialog.tsx` around lines 6 - 10, The
imports DialogTitle and formatDate are unused because the JSX that references
them is commented out; either re-enable the commented JSX block that uses
DialogTitle and formatDate (so keep the imports) or remove the unused imports
(DialogTitle, formatDate) from the top of photo-dialog.tsx to satisfy lint/type
checks; locate references to DialogTitle and formatDate in the commented JSX
around the photo-dialog component and act accordingly.

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