PWA - #258
Conversation
WalkthroughThis 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
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
previousLikessnapshot is captured but never used.Line 66 stores a snapshot of
optimisticLikes, but the rollback path on line 75 simply re-invokesaddOptimisticLike(session.user.id)(which toggles the optimistic reducer back). ThepreviousLikesarray 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_previousLikestopreviousLikesalso drops the underscore convention that signaled an intentionally-unused binding, which may tripno-unused-varsstyle 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):
addOptimisticLikeis invoked outsidestartTransition/an action. React 19 surfaces a warning — "An optimistic state update occurred outside a transition or action" — in that case. Consider wrapping the toggle instartTransitionif 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 | 🟡 MinorHardcoded
#04f4beis 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#04f4bevia inline style. Given the PR explicitly aligns on the institutional#7c3aed/ PCN theme (Card border, icon, hover borders all usepcnPurple/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 | 🟠 MajorRemove 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-elementrule is active (inherited via the `next` and `next/core-web-vitals` ESLint presets). These `` elements will now fail lint checks.
Migrate to
next/imagefor 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 | 🟡 MinorMigrate to
next/imageor re-add theeslint-disablesuppression.The
@next/next/no-img-elementsuppression was removed but the raw<img>element remains. Withnext/core-web-vitalsenabled (default for this project), ESLint will flag this as an error. Migrating tonext/imageis 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}underimages.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 | 🟠 MajorAccessibility regression:
Dialogwithout aDialogTitle.Radix UI's
DialogContentrequires aDialogTitledescendant 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-onlytitle 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
ctxparameter is declared but unused — will trigger ESLintno-unused-varswarning.The ESLint configuration enables
no-unused-vars(set towarn), and the function body never referencesctx. Without the underscore-prefix convention (e.g.,_ctx), this will trigger a lint warning innext lint. Either restore the_ctxprefix 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 | 🟡 MinorThis will trigger an ESLint warning, not fail the lint.
The suppression comment was deleted, but the
useEffectat lines 218-236 still has an empty dependency array[]while callingsetup()andresize()(which close over component-scoped constants). Next.js's defaultreact-hooks/exhaustive-depsconfiguration is severity "warn", not "error", so this produces a warning rather than a lint failure.Two reasonable paths:
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 }, []);Make it correct per the rule by memoizing
setup/resizewithuseCallbackand 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 | 🟠 MajorRestore 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:
- Lines 37–41: The
useEffectreadspreviewwithout including it in the dependency array. This triggersreact-hooks/exhaustive-deps. (Intentional to avoid re-running on local preview changes, but now unsilenced.)- Line 119: A raw
<img>is used instead ofnext/image, triggering@next/next/no-img-element.Since
.eslintrc.jsonextends"next"and"next/core-web-vitals", and Next.js runs ESLint duringnext 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>withnext/image(usingfillandsizes), 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 | 🟡 MinorRemove debug
console.logbefore 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 | 🟠 MajorWrap
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, andverificar-email.Refactor by extracting page content into a separate component and wrapping it in
<Suspense>, or addexport 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 | 🟠 MajorRemoving the
<Suspense>wrapper breaksuseSearchParams()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 buildfails with:
useSearchParams() should be wrapped in a suspense boundaryThe refactor needs to either keep a
Suspenseboundary around the component or force the segment dynamic withexport 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
requestis 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
_requestand restoreargsIgnorePattern: '^_'on the rule (preferred — see.eslintrc.jsoncomment). 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-elementnow 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 tonext/imageso 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 innext.config.mjsimage 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.portraitare allow-listed underimages.remotePatternsinnext.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 | 🟡 MinorRest-sibling exclusion will now warn under the new
no-unused-varsconfig.
confirmPasswordexists only to strip it from...cleanedData. Defaultno-unused-varsdoes not consider rest-siblings "used". The previous_confirmPasswordalias 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 | 🟡 MinorMigrate
<img>tonext/imageto resolve@next/next/no-img-elementESLint warning.The ESLint config extends
next/core-web-vitals, which enforces theno-img-elementrule. The raw<img>tag at lines 171-175 will trigger this warning. Migrating tonext/imageis 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
isPopularis 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
isPopularis read from props but never used. Either remove it from the destructuring or render the badge to matchsrc/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.tsxandsoftware-recomendado/page.tsxare 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 | 🟠 MajorMigrate
<img>tags tonext/imageor restoreeslint-disablecomments to prevent CI failure.The
@next/next/no-img-elementrule 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 tonext/imagefor 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 | 🟡 MinorUse
next/imagecomponent instead of plain<img>tags to align with Next.js best practices.The
@next/next/no-img-elementrule is enabled via thenext/core-web-vitalsESLint config extension. Both<img>tags in this file (portrait at lines 103–107 and slides around line 172) should be migrated to thenext/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 | 🟠 MajorSame lint regression as in
file-upload-public.tsx— please apply a consistent fix.The
react-hooks/exhaustive-depssuppression on thevalue→previewsync effect (Line 37–41) and the@next/next/no-img-elementsuppression on the preview<img>(Line 126–130) were removed, but the underlying code still violates both rules:
previewis read inside the effect but intentionally excluded from deps to avoid a self-triggering loop.- A raw
<img>is still used instead ofnext/image.This will produce the same lint errors as the sibling component and is likely to fail
next buildif ESLint-on-build is enabled. Please either restore the targetedeslint-disable-next-linecomments or migrate tonext/imageand refactor the effect (e.g., via a ref guard or derived state) so the rules pass cleanly. Keeping bothFileUploadandFileUploadPublicconsistent 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
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlpublic/zero-to-agent-card.pngis 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.samplenext.config.mjspackage.jsonprisma/migrations/20260422034035_add_external_registration_url/migration.sqlprisma/schema.prismapublic/manifest.jsonsrc/actions/analytics/track-page-visit.tssrc/actions/auth/sign-up.tssrc/actions/errors/log-error.tssrc/actions/events/fetch-recently-added-events.tssrc/app/(platform)/analiticas/loading.tsxsrc/app/(platform)/analiticas/page.tsxsrc/app/(platform)/anuncios/loading.tsxsrc/app/(platform)/charlas/loading.tsxsrc/app/(platform)/charlas/page.tsxsrc/app/(platform)/code-warfare/loading.tsxsrc/app/(platform)/consejos/[id]/loading.tsxsrc/app/(platform)/cursos/[courseId]/loading.tsxsrc/app/(platform)/cursos/loading.tsxsrc/app/(platform)/cursos/page.tsxsrc/app/(platform)/desarrollo/loading.tsxsrc/app/(platform)/desarrollo/page.tsxsrc/app/(platform)/especialidades/loading.tsxsrc/app/(platform)/especialidades/page.tsxsrc/app/(platform)/eventos/[id]/editar/loading.tsxsrc/app/(platform)/eventos/[id]/editar/page.tsxsrc/app/(platform)/eventos/[id]/inscripcion/loading.tsxsrc/app/(platform)/eventos/[id]/inscripcion/page.tsxsrc/app/(platform)/eventos/[id]/inscripciones/loading.tsxsrc/app/(platform)/eventos/[id]/inscripciones/page.tsxsrc/app/(platform)/eventos/[id]/loading.tsxsrc/app/(platform)/eventos/[id]/page.tsxsrc/app/(platform)/eventos/loading.tsxsrc/app/(platform)/eventos/nuevo/loading.tsxsrc/app/(platform)/galeria/loading.tsxsrc/app/(platform)/historia/loading.tsxsrc/app/(platform)/historia/page.tsxsrc/app/(platform)/home-client-side.tsxsrc/app/(platform)/influencers/loading.tsxsrc/app/(platform)/layout.tsxsrc/app/(platform)/lectura/loading.tsxsrc/app/(platform)/lectura/page.tsxsrc/app/(platform)/monitoreo/errors-client.tsxsrc/app/(platform)/monitoreo/loading.tsxsrc/app/(platform)/monitoreo/logs-client.tsxsrc/app/(platform)/monitoreo/page.tsxsrc/app/(platform)/music/loading.tsxsrc/app/(platform)/notificaciones/loading.tsxsrc/app/(platform)/notificaciones/notifications-client.tsxsrc/app/(platform)/notificaciones/page.tsxsrc/app/(platform)/page.tsxsrc/app/(platform)/perfil/[id]/loading.tsxsrc/app/(platform)/perfil/[id]/page.tsxsrc/app/(platform)/perfil/loading.tsxsrc/app/(platform)/podcast/loading.tsxsrc/app/(platform)/podcast/page.tsxsrc/app/(platform)/preguntas-frecuentes/loading.tsxsrc/app/(platform)/preguntas-frecuentes/page.tsxsrc/app/(platform)/software-recomendado/loading.tsxsrc/app/(platform)/software-recomendado/page.tsxsrc/app/(platform)/software-util/loading.tsxsrc/app/(platform)/software-util/page.tsxsrc/app/(platform)/testimonials.tsxsrc/app/(platform)/testimonios/[id]/loading.tsxsrc/app/(platform)/testimonios/[id]/page.tsxsrc/app/(platform)/testimonios/loading.tsxsrc/app/(platform)/testimonios/testimonials-client.tsxsrc/app/(platform)/usuarios/loading.tsxsrc/app/(platform)/visitas/loading.tsxsrc/app/(platform)/visitas/page.tsxsrc/app/(platform)/zero-to-agent-sponsors/loading.tsxsrc/app/(platform)/zero-to-agent-sponsors/page.tsxsrc/app/autenticacion/iniciar-sesion/page.tsxsrc/app/autenticacion/recuperar-clave/page.tsxsrc/app/autenticacion/registro/page.tsxsrc/app/autenticacion/verificar-email/page.tsxsrc/app/cowork/page.tsxsrc/app/layout.tsxsrc/app/meetup/page.tsxsrc/components/advises/advise-card.tsxsrc/components/advises/delete-advise-dialog.tsxsrc/components/advises/edit-advise-dialog.tsxsrc/components/announcements/announcement-card.tsxsrc/components/announcements/announcement-form.tsxsrc/components/announcements/delete-announcement-dialog.tsxsrc/components/announcements/event-announcements.tsxsrc/components/comunity/user-card.tsxsrc/components/especialidades/table-of-contents.tsxsrc/components/events/delete-event-dialog.tsxsrc/components/events/event-card.tsxsrc/components/events/event-detail-client.tsxsrc/components/events/event-flyer.tsxsrc/components/events/event-form.tsxsrc/components/events/event-photos.tsxsrc/components/events/register-event-button.tsxsrc/components/home/asz-software-logo.tsxsrc/components/home/bowery-logo.tsxsrc/components/home/discord-card.tsxsrc/components/home/recently-added-events-section.tsxsrc/components/home/sponsors-section.tsxsrc/components/influencers/influencer-card.tsxsrc/components/landing/activities.tsxsrc/components/landing/discord.tsxsrc/components/landing/lightning-talks.tsxsrc/components/landing/motivation.tsxsrc/components/landing/platform-features-large.tsxsrc/components/landing/team.tsxsrc/components/landing/testimonial.tsxsrc/components/photo-gallery/photo-card.tsxsrc/components/photo-gallery/photo-dialog.tsxsrc/components/photo-gallery/search-bar.tsxsrc/components/photo-gallery/sort-selector.tsxsrc/components/profile/language-coin.tsxsrc/components/profile/language-coins-container.tsxsrc/components/profile/profile-form.tsxsrc/components/skeletons/page-skeletons.tsxsrc/components/testimonials/testimonial-card.tsxsrc/components/ui/app-sidebar.tsxsrc/components/ui/button.tsxsrc/components/ui/carousel.tsxsrc/components/ui/content-card.tsxsrc/components/ui/file-upload-public.tsxsrc/components/ui/file-upload.tsxsrc/components/ui/image-carousel.tsxsrc/components/ui/nav-main.tsxsrc/components/ui/nav-projects.tsxsrc/components/ui/pagination.tsxsrc/components/ui/platform-header.tsxsrc/components/ui/sidebar.tsxsrc/components/ui/text-generate-effect.tsxsrc/components/ui/vortex.tsxsrc/middleware.tssrc/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
| "rules": { | ||
| "no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }] | ||
| "no-unused-vars": "warn" | ||
| } |
There was a problem hiding this comment.
🧩 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:
- 1: https://nextjs.org/docs/app/api-reference/config/eslint
- 2: How Can I config `@typescript-eslint/no-unused-vars` correctly? vercel/next.js#52871
🏁 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 -20Repository: programaconnosotros/pcn-website
Length of output: 246
🏁 Script executed:
# Check if next/typescript is mentioned anywhere in config or package.json
rg "typescript" --type jsonRepository: programaconnosotros/pcn-website
Length of output: 111
🏁 Script executed:
# Find and read .eslintrc.json
cat .eslintrc.jsonRepository: programaconnosotros/pcn-website
Length of output: 184
🏁 Script executed:
# Check for TypeScript files (using correct fd syntax)
fd "\.(ts|tsx)$" | head -20Repository: 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.jsonRepository: 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 -40Repository: 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 -1Repository: programaconnosotros/pcn-website
Length of output: 183
🏁 Script executed:
# Search for any mention of typescript in eslint config files
rg "typescript" -t json -t jsRepository: 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 -100Repository: 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 -1Repository: 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 -100Repository: 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.jsRepository: 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 -5Repository: 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-eslintRepository: 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|.*/||' | sortRepository: 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:
-
Missing plugin dependency. The proposed rule
@typescript-eslint/no-unused-varsrequires the@typescript-eslint/eslint-pluginpackage, which is not installed in this project. Only@typescript-eslint/parseris available as a transitive dependency. Without the plugin, ESLint will error on the undefined rule. -
next/typescriptdoesn't exist in eslint-config-next 14.2.4. The web search result mentioned TypeScript rules coming fromnext/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/.tsxfiles, not the TypeScript linting rules.
To fix the rule issue in a TypeScript project, either:
- Install
@typescript-eslint/eslint-pluginand use the corrected config from the review (with the^_ignore pattern), or - Keep the built-in
no-unused-varsrule 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.
| 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, | ||
| }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
🧩 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/appRepository: programaconnosotros/pcn-website
Length of output: 675
🏁 Script executed:
#!/bin/bash
# Check middleware.ts to see if it gates /analiticas
cat src/middleware.tsRepository: 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.tsxRepository: programaconnosotros/pcn-website
Length of output: 118
🏁 Script executed:
#!/bin/bash
# Get Next.js version from package.json
rg '"next"' package.jsonRepository: 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 -100Repository: 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 +31Repository: 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 requiresawait. Without it,sessionIdbecomes a Promise,.get(...)fails, and the if-branch never runs, leaving the page unprotected. - Lines 32–43 (major): The
sessionvariable 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 wiresessionto 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.
| 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.
| 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; |
There was a problem hiding this comment.
🧩 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
@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:
- 1: https://nextjs.org/docs/messages/sync-dynamic-apis
- 2: https://nextjs.im/docs/15/app/api-reference/file-conventions/dynamic-routes/
- 3: https://nextjs.org/blog/next-15
- 4: https://nextjs.org/docs/app/building-your-application/upgrading/version-15
🏁 Script executed:
head -25 src/app/\(platform\)/eventos/\[id\]/inscripcion/page.tsxRepository: 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'; | |||
There was a problem hiding this comment.
🧩 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:
- 1: https://nextjs.org/docs/app/api-reference/directives/use-server
- 2: https://nextjs.org/docs/15/app/api-reference/directives/use-server
- 3: https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns
- 4: https://nextjs.org/docs/app/getting-started/server-and-client-components
- 5: https://stackoverflow.com/questions/77971965/in-next-js-app-router-should-the-page-js-be-a-server-component
- 6: https://www.reddit.com/r/nextjs/comments/1azz5j0/any_drawbacks_if_i_add_use_server_to_every_server/
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.
| '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.
| 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, | ||
| }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
🧩 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:
- 1: https://nextjs.org/docs/app/api-reference/functions/cookies
- 2: https://nextjs.org/docs/15/app/api-reference/functions/cookies
- 3: https://nextjs.org/docs/messages/next-prerender-sync-headers
- 4: https://nextjs.org/blog/next-15
- 5: https://nextjs.org/docs/messages/sync-dynamic-apis
🏁 Script executed:
# First, check if the file exists and its size
wc -l src/app/\(platform\)/podcast/page.tsxRepository: 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 -70Repository: 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 -nRepository: 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 -45Repository: 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:
-
Line 40 (critical): In Next.js 15,
cookies()is asynchronous and returns aPromise. Calling.get('sessionId')?.valuedirectly on the promise without awaiting will yieldundefinedat runtime, sosessionIdremains falsy and theif (sessionId)branch never executes. Useawait cookies(). -
Lines 41–52 (major): The
sessionvariable 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.
| 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.
| 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, | ||
| }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| manifest: '/manifest.json', | ||
| themeColor: '#7c3aed', | ||
| appleWebApp: { | ||
| capable: true, | ||
| statusBarStyle: 'default', | ||
| title: 'PCN', | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && find . -name "package.json" -path "*/src/*" -prune -o -name "package.json" -type f -print | head -5Repository: 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 exportRepository: 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:
- 1: https://nextjs.org/docs/app/api-reference/functions/generate-metadata
- 2: https://nextjs.org/blog/next-14
- 3: https://nextjs.org/docs/app/api-reference/functions/generate-viewport
- 4: https://nextjs.org/docs/14/app/api-reference/functions/generate-viewport
- 5: Meta theme-color is missing in the html, while meta viewport value is incorrect vercel/next.js#57680
- 6: https://stackoverflow.com/questions/77566673/how-to-use-the-meta-viewport-tag-in-nextjs-14-app-router
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).
| let user: User | null = null; | ||
|
|
||
| if (sessionId) { | ||
| const session = await prisma.session.findUnique({ | ||
| where: { id: sessionId }, | ||
| include: { user: true }, | ||
| }); | ||
|
|
||
| if (session) { | ||
| user = session.user; | ||
| } | ||
| } |
There was a problem hiding this comment.
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
userthrough 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.
| 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'; |
There was a problem hiding this comment.
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.
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.jsonen/publiccon soporte para iconos maskable.@ducanh2912/next-pwapara habilitar caché y modo offline básico.#7c3aedpara la barra de estado y el fondo de carga.¿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
Bug Fixes
Refactor
Chores