Skip to content

Migrate stats-web from Next.js to Vite with React Router#2801

Draft
baktun14 wants to merge 7 commits intomainfrom
claude/slack-migrate-stats-vite-242OX
Draft

Migrate stats-web from Next.js to Vite with React Router#2801
baktun14 wants to merge 7 commits intomainfrom
claude/slack-migrate-stats-vite-242OX

Conversation

@baktun14
Copy link
Copy Markdown
Contributor

@baktun14 baktun14 commented Feb 20, 2026

Why

This PR migrates the stats-web application from Next.js (App Router) to Vite with React Router. This modernizes the build tooling and provides a more lightweight client-side rendering approach better suited for a statistics dashboard application.

What

Framework Migration

  • Replaced Next.js App Router with React Router v6 for client-side routing
  • Migrated from Next.js build system to Vite for faster development and optimized production builds
  • Converted server-side rendered pages to client-side React components

Key Changes

  • Routing: Moved all pages from src/app/ directory structure to src/pages/ with React Router configuration in src/main.tsx
  • Entry Point: Created new src/main.tsx as the application entry point with Vite configuration
  • Data Fetching: Replaced server-side data fetching with client-side React Query hooks via new useDetailQueries.ts (useBlock, useTransaction, useAddress, useValidator, useDeployment)
  • Metadata: Replaced Next.js metadata exports with react-helmet-async for dynamic head management
  • Navigation: Updated all next/link imports to react-router-dom Link component
  • Analytics: Migrated from nextjs-google-analytics to react-ga4
  • Theme: Simplified theme provider by removing Next.js specific dependencies
  • Environment Config: Updated environment variable handling for Vite (VITE_ prefix)

Build Configuration

  • Added vite.config.ts with React plugin and path alias configuration
  • Updated tsconfig.json for Vite/ESM compatibility
  • Added tsconfig.node.json for Vite config type checking
  • Created index.html as Vite entry point
  • Updated package.json scripts: build now runs vite build instead of next build
  • Removed Next.js specific files: next.config.js, middleware.ts, instrumentation.ts

Component Updates

  • Removed "use client" directives (no longer needed in client-side app)
  • Updated dynamic imports to use lazy and Suspense from React
  • Converted page components to named exports (e.g., HomePage, BlockDetailPage)
  • Updated all internal navigation links to use React Router

Removed Dependencies

  • Next.js and related packages (next, @sentry/nextjs, @unleash/nextjs)
  • Server-side utilities (serverFetch, server-env.config)
  • Next.js specific error boundaries and layouts

https://claude.ai/code/session_01LghCUqfxhqgCNahb3zN9DW

Summary by CodeRabbit

  • New Features

    • SPA entry with React Router, new client entry and rewritten pages: Home, Blocks, Block/Transaction/Validator/Address/Deployment details, Graphs, Maintenance, and 404; loading/error states and spinners added.
  • Bug Fixes

    • Improved loading, error and not-found feedback; navigation progress and pageview analytics (GA4) integrated.
  • Refactor

    • Frontend migrated from Next.js to a Vite-based React SPA; env vars moved to VITE_*, Sentry/analytics adjusted for client.
  • Chores

    • Tooling, deps, Dockerfile/nginx, TypeScript, ESLint and Vitest test setup added/updated.

Migrate the stats-web application from Next.js to a Vite-based SPA. This
addresses the team's concerns about SSR complexity, IP proxying issues,
observability challenges, and memory leaks.

Key changes:
- Replace Next.js with Vite as the build tool
- Replace Next.js App Router with react-router-dom for client-side routing
- Replace next/link with react-router-dom Link throughout components
- Convert server-side data fetching to client-side React Query hooks
- Replace @sentry/nextjs with @sentry/react
- Replace nextjs-google-analytics with react-ga4
- Update environment variables from NEXT_PUBLIC_ to VITE_ prefix
- Add react-helmet-async for SEO/meta tag management
- Remove Next.js middleware, server config, and SSR-specific code
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces Next.js app-router with a Vite-powered React SPA: adds index.html, Vite config, main.tsx and React Router pages; migrates env variables from NEXT_PUBLIC_* → VITE_*; removes Next.js server/middleware/instrumentation; converts many components to client-side React + React Query and updates build/deploy (Docker + nginx).

Changes

Cohort / File(s) Summary
Vite & Tooling
apps/stats-web/index.html, apps/stats-web/vite.config.ts, apps/stats-web/vite-env.d.ts, apps/stats-web/tsconfig.json, apps/stats-web/tsconfig.node.json, apps/stats-web/vitest.config.ts, apps/stats-web/.eslintrc.cjs
Add Vite entry, config, TS/node configs, Vite env typings, Vitest and ESLint for Vite workflow.
App Bootstrap & Routing
apps/stats-web/src/main.tsx, apps/stats-web/src/App.tsx, apps/stats-web/package.json
New SPA entry and App using react-router, Sentry browser init, switch package.json scripts/deps to Vite ecosystem.
Next.js removal
apps/stats-web/next.config.js, apps/stats-web/src/middleware.ts, apps/stats-web/src/instrumentation.ts, apps/stats-web/src/app/...
Remove Next.js config, middleware, server instrumentation and the app-router tree (server pages, metadata, API routes).
Pages: app → pages migration
apps/stats-web/src/pages/**, apps/stats-web/src/pages/*/*/page.tsx, apps/stats-web/src/pages/*/page.tsx
Add client-side pages (home, blocks, block detail, transactions, transaction detail, validators, addresses with nested routes, graphs, maintenance, 404) using Helmet, react-query hooks, and loading/error states.
Routing & Link migration
apps/stats-web/src/components/**, apps/stats-web/src/pages/**
Replace next/link and Next router APIs with react-router-dom (Link/to, useNavigate, useParams); external links converted to anchors where appropriate.
Env & Schema
apps/stats-web/src/config/browser-env.config.ts, apps/stats-web/src/config/env-config.schema.ts, apps/stats-web/env/*, .env*
Migrate public env surface from NEXT_PUBLIC_* to VITE_*; update schema, typings and .env samples.
Sentry & instrumentation
apps/stats-web/sentry.client.config.ts, apps/stats-web/src/services/error-handler/*, apps/stats-web/src/lib/createLogger/*
Switch Sentry usage from @sentry/nextjs to @sentry/react; remove server tracing/instrumentation and trace mixins from logger.
Data layer & queries
apps/stats-web/src/queries/useDetailQueries.ts, apps/stats-web/src/queries/queryKeys.ts, apps/stats-web/src/lib/apiUtils.ts, apps/stats-web/src/services/api-url/*.ts
Add React Query detail hooks and keys; extend ApiUrlService with resource builders; refactor URL service to VITE_* base URLs and remove server singleton.
Feature flags & analytics
apps/stats-web/src/context/FlagProvider/*, apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx
Replace Unleash provider with a no-op FlagProvider; replace Next.js analytics with React GA4 route-tracking implementation.
Dynamic imports & client behavior
apps/stats-web/src/components/DynamicJsonView.tsx, apps/stats-web/src/components/graph/Graph.tsx, apps/stats-web/src/pages/**/GraphContainer.tsx, many src/components & src/pages
Replace Next.js dynamic imports with React.lazy + Suspense; tighten Graph typings; swap QR to react-qrcode-logo; remove many "use client" directives and Next-specific progress integrations.
Docker & deployment
packages/docker/Dockerfile.vite, packages/docker/docker-compose.*.yml, apps/stats-web/nginx.conf, apps/stats-web/.dockerignore
Add multi-stage Dockerfile for Vite + nginx, update compose files, add nginx SPA config, adjust dockerignore and deploy comments.
Tests, styles & misc
apps/stats-web/tests/unit/setup.ts, apps/stats-web/src/styles/index.css, various small files
Add Vitest/jest-like setup (jsdom mocks), font-face/style, many component tweaks (directive removals, minor guards, link/type fixes).
sequenceDiagram
    participant Browser as Browser
    participant HTML as index.html
    participant Main as /src/main.tsx
    participant Sentry as Sentry (browser)
    participant Router as RouterProvider
    participant Page as Page Component
    participant API as Backend API

    Browser->>HTML: request index.html + module script
    HTML->>Main: load /src/main.tsx
    Main->>Sentry: init (VITE_SENTRY_DSN, VITE_SENTRY_ENABLED)
    Main->>Router: mount RouterProvider / App
    Router->>Page: navigate (useParams/useNavigate)
    Page->>API: fetch via useDetailQueries (Axios → ApiUrlService)
    API-->>Page: return data
    Page-->>Browser: render UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • ygrishajev

Poem

🐰 I hopped from Next to Vite today,
Routes re-routed, envs found a new way,
Lazy sprites wake, queries fetch the news,
Sentry hums, nginx serves the views,
A rabbit cheers the SPA — hop, hooray! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: migrating stats-web from Next.js to Vite with React Router, which is the primary focus of the entire changeset.
Description check ✅ Passed The description comprehensively covers the migration scope with clear sections explaining Why (modernization rationale), What (detailed framework migration, key changes, build config, component updates, removed dependencies), and includes supporting links. All major aspects of the changeset are addressed.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/slack-migrate-stats-vite-242OX

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

@socket-security
Copy link
Copy Markdown

socket-security bot commented Feb 20, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedvite@​6.4.1911008399100
Addednext-themes@​0.4.61001009883100
Added@​unleash/​proxy-client-react@​4.5.210010010088100

View full report

@socket-security
Copy link
Copy Markdown

socket-security bot commented Feb 20, 2026

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location: Package overview

From: apps/stats-web/package.jsonnpm/vite@6.4.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@6.4.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 53.73%. Comparing base (d73a225) to head (8222f15).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2801      +/-   ##
==========================================
- Coverage   53.73%   53.73%   -0.01%     
==========================================
  Files        1018     1018              
  Lines       23586    23586              
  Branches     5757     5757              
==========================================
- Hits        12675    12674       -1     
+ Misses       9517     9509       -8     
- Partials     1394     1403       +9     
Flag Coverage Δ
api 77.27% <ø> (ø)
deploy-web 35.91% <ø> (-0.01%) ⬇️
log-collector 75.35% <ø> (ø)
notifications 85.56% <ø> (ø)
provider-console 81.48% <ø> (ø)
provider-proxy 82.41% <ø> (ø)
tx-signer 79.29% <ø> (ø)
see 5 files with indirect coverage changes
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🧹 Nitpick comments (9)
apps/stats-web/src/components/transactions/akash/MsgDeleteProviderAttributes.tsx (1)

13-13: TODO: Add link to provider page.

There's a TODO comment indicating a link to the provider page should be added.

Would you like me to help implement this provider page link, or open a new issue to track this task?

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

In
`@apps/stats-web/src/components/transactions/akash/MsgDeleteProviderAttributes.tsx`
at line 13, The component MsgDeleteProviderAttributes.tsx contains a TODO to add
a link to the provider page; replace that comment with a clickable link (using
your project's Link/router component) that navigates to the provider detail
route, e.g., /providers/:address, using the provider address available on the
message (e.g., msg.provider or props.provider.address) and render the address
(or a shortened form) as the link text; ensure you import the Link component if
not present and preserve existing styling/aria attributes used in
MsgDeleteProviderAttributes.
apps/stats-web/src/services/error-handler/error-handler.service.ts (1)

74-89: Remove sentryTraceToW3C to eliminate dead code in stats-web.

The function is exported but never used within this app. Since getTraceData() always returns undefined for traceId, this helper function has no purpose here. Remove it to keep the codebase clean. (Note: This function is actively used in apps/deploy-web and should remain there.)

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

In `@apps/stats-web/src/services/error-handler/error-handler.service.ts` around
lines 74 - 89, Remove the dead exported helper sentryTraceToW3C from
error-handler.service.ts: delete the entire sentryTraceToW3C function definition
and its export, ensure there are no local references to sentryTraceToW3C (search
the app) and run TypeScript/ESLint checks to confirm compilation; leave usage in
other apps (e.g., deploy-web) untouched and do not change getTraceData.
apps/stats-web/src/components/transactions/generic/MsgDelegate.tsx (1)

28-37: Consider removing commented-out code.

This commented-out JSX block appears to be dead code. If the "Auto Claim Reward" feature is planned, consider tracking it via an issue rather than leaving commented code in the source.

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

In `@apps/stats-web/src/components/transactions/generic/MsgDelegate.tsx` around
lines 28 - 37, The file contains a commented-out JSX block inside the
MsgDelegate component (the MessageLabelValue with label "Auto Claim Reward"
using coinsToAmount and AKTLabel) which is dead code; remove this commented
block to clean up the component or, if the feature is planned, instead create a
tracked issue and add a TODO referencing that issue number; ensure you remove
the commented lines around MessageLabelValue, coinsToAmount(...) and AKTLabel
usage so the component contains only active code.
apps/stats-web/src/pages/transactions/[hash]/TransactionInfo.tsx (1)

16-17: Lazy loading react-intl components may not provide meaningful benefit.

FormattedRelativeTime and FormattedDate are lightweight components from react-intl. Lazy loading them adds:

  1. Potential visual flicker ("Loading..." fallback)
  2. Extra network requests for small chunks
  3. Complexity in the codebase

If react-intl is used elsewhere in the app (which it likely is, given other components), it will already be in the main bundle. Consider using static imports for these ubiquitous formatting components.

♻️ Suggested simplification
-import React, { Suspense, lazy } from "react";
+import React from "react";
+import { FormattedDate, FormattedRelativeTime } from "react-intl";
 import { Alert, AlertDescription, AlertTitle, Card, CardContent } from "@akashnetwork/ui/components";
 import { WarningCircle } from "iconoir-react";
 import { Link } from "react-router-dom";
 
 import { AddressLink } from "@/components/AddressLink";
 import { AKTAmount } from "@/components/AKTAmount";
 import { LabelValue } from "@/components/LabelValue";
 import { UrlService } from "@/lib/urlUtils";
 import type { TransactionDetail } from "@/types";
 
-const FormattedRelativeTime = lazy(() => import("react-intl").then(mod => ({ default: mod.FormattedRelativeTime })));
-const FormattedDate = lazy(() => import("react-intl").then(mod => ({ default: mod.FormattedDate })));

And remove the Suspense wrapper around the time display:

         <LabelValue
           label="Time"
           value={
-            <Suspense fallback={<span>Loading...</span>}>
               <FormattedRelativeTime
                 value={(new Date(transaction.datetime).getTime() - new Date().getTime()) / 1000}
                 numeric="auto"
                 unit="second"
                 updateIntervalInSeconds={7}
               />
               &nbsp;(
               <FormattedDate value={transaction.datetime} year="numeric" month="2-digit" day="2-digit" hour="2-digit" minute="2-digit" second="2-digit" />)
-            </Suspense>
           }
         />

Also applies to: 29-38

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

In `@apps/stats-web/src/pages/transactions/`[hash]/TransactionInfo.tsx around
lines 16 - 17, The code lazily imports FormattedRelativeTime and FormattedDate
which are trivial react-intl components and cause unnecessary Suspense/fallbacks
and extra bundles; replace the lazy(...) imports with direct static imports from
"react-intl" (use import { FormattedRelativeTime, FormattedDate } from
"react-intl") and remove the surrounding Suspense wrapper used around the time
display so the components render normally without the loading fallback.
apps/stats-web/src/pages/home/StatsCard.tsx (1)

34-36: diffNumber is checked but not rendered.

The condition !!diffNumber || !!diffPercent still checks diffNumber, but only diffPercent is used for rendering DiffPercentageChip. If diffNumber display was intentionally removed, consider also removing it from the condition and props interface, or clarify if it should still control visibility.

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

In `@apps/stats-web/src/pages/home/StatsCard.tsx` around lines 34 - 36, The
visibility condition in the StatsCard component still checks diffNumber but only
renders DiffPercentageChip for diffPercent; update the component so the
condition and props match: either remove diffNumber from the visibility check
and from the StatsCard props/interface if you no longer need it, or render the
diffNumber output (e.g., add a DiffNumberChip/span next to DiffPercentageChip)
and ensure the StatsCard props/interface includes diffNumber. Update the
conditional line that currently reads {(!!diffNumber || !!diffPercent) && (...)
} and the StatsCard props/type accordingly, and ensure any styling/className
passed to DiffPercentageChip remains correct.
apps/stats-web/src/lib/createLogger/createLogger.ts (1)

4-7: Trace context removed from logger.

The Sentry trace data injection was removed. For client-side logging, this may be acceptable, but trace correlation in logs can be valuable for debugging user-reported issues. Consider whether trace context from @sentry/react should be re-integrated if log correlation with Sentry traces is desired.

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

In `@apps/stats-web/src/lib/createLogger/createLogger.ts` around lines 4 - 7,
createLogger currently returns a LoggerService without Sentry trace context
(trace correlation was removed); restore trace propagation by extracting the
current Sentry trace/span info from `@sentry/react` (e.g., via
Sentry.getCurrentHub().getScope() / getSpan() or the SDK's traceparent) and
inject that data into the LoggerService (either by adding trace fields to the
options passed into createLogger or by registering a log processor on
LoggerService to append trace_id/span_id/traceparent). Update createLogger to
obtain the Sentry trace info when available and attach it to each log event so
logs can be correlated with Sentry traces.
apps/stats-web/src/pages/addresses/[address]/deployments/[dseq]/EventRow.tsx (1)

21-23: Consider using an anchor tag for target="_blank" links.

While React Router's Link with target="_blank" works, it's more idiomatic to use a regular anchor tag when intentionally opening in a new tab, as this bypasses client-side routing anyway.

Optional refactor
-        <Link to={UrlService.transaction(event.txHash)} target="_blank">
+        <a href={UrlService.transaction(event.txHash)} target="_blank" rel="noreferrer">
           {getSplitText(event.txHash, 6, 6)}
-        </Link>
+        </a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/pages/addresses/`[address]/deployments/[dseq]/EventRow.tsx
around lines 21 - 23, In EventRow, replace the React Router Link used to open a
new tab with a plain anchor: use an <a> element with
href={UrlService.transaction(event.txHash)}, target="_blank" and rel="noopener
noreferrer" and render getSplitText(event.txHash, 6, 6) as its child; update
imports if needed to remove the Link import to avoid unused symbols.
apps/stats-web/src/pages/addresses/[address]/transactions/columns.tsx (1)

20-22: Consider using an anchor tag for target="_blank" links.

Similar to EventRow.tsx, when opening links in a new tab, a regular anchor tag is more semantic since client-side routing is bypassed anyway.

Optional refactor
-      <Link to={UrlService.transaction(row.getValue("hash"))} target="_blank">
+      <a href={UrlService.transaction(row.getValue("hash"))} target="_blank" rel="noreferrer">
         {getSplitText(row.getValue("hash"), 6, 6)}
-      </Link>
+      </a>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/pages/addresses/`[address]/transactions/columns.tsx around
lines 20 - 22, The current code uses the Link component for an external/new-tab
navigation (Link to={UrlService.transaction(row.getValue("hash"))}
target="_blank"), but this should be a plain anchor to be semantic and avoid
client-side routing; replace the Link with an <a> element using
href={UrlService.transaction(row.getValue("hash"))}, keep the displayed text
from getSplitText(row.getValue("hash"), 6, 6), and add target="_blank" plus
rel="noopener noreferrer" for security and performance.
apps/stats-web/src/pages/addresses/[address]/deployments/[dseq]/page.tsx (1)

10-10: Remove unused import.

The DeploymentDetail type is imported but never used in this file. The deployment variable is already typed by the useDeployment hook.

🧹 Proposed fix
-import type { DeploymentDetail } from "@/types";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/pages/addresses/`[address]/deployments/[dseq]/page.tsx at
line 10, Remove the unused type import DeploymentDetail from the top of the
file; locate the import line that reads "import type { DeploymentDetail } from
'@/types';" and delete it since the deployment variable is typed by
useDeployment and DeploymentDetail is not referenced anywhere in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/stats-web/package.json`:
- Around line 5-15: The repo sets "type": "module" in package.json which makes
Node treat .js as ESM, but apps/stats-web/.eslintrc.js and
apps/stats-web/.prettierrc.js currently use CommonJS (module.exports/require),
causing runtime load errors; either rename those two files to .cjs to keep
CommonJS behavior, or convert them to ESM by replacing the CommonJS
require/module.exports with ESM import/export (e.g., import the shared config
from "@akashnetwork/dev-config" and export it as default), ensuring ESLint and
Prettier can load under the ESM package type.

In `@apps/stats-web/src/components/blockchain/TransactionRow.tsx`:
- Around line 26-28: The Link in the TransactionRow component that renders the
transaction hash (Link to={UrlService.transaction(transaction.hash)} displaying
{txHash}) opens a new tab via target="_blank" but lacks rel attributes; update
that Link to include rel="noopener noreferrer" to prevent reverse tabnabbing
(i.e., add rel="noopener noreferrer" to the Link element that wraps {txHash}).

In `@apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx`:
- Around line 7-23: Replace the module-level initialized flag with React state
inside the GoogleAnalytics component (e.g., const [initialized, setInitialized]
= useState(false)); in the init useEffect call ReactGA.initialize(...) when in
production and measurement id exists, then call setInitialized(true); ensure the
init effect includes relevant dependencies (browserEnvConfig.VITE_NODE_ENV and
browserEnvConfig.VITE_GA_MEASUREMENT_ID) and the pageview effect depends on
[location, initialized] so ReactGA.send runs only after state reflects
initialization; update references to the old module variable (initialized) to
use the state variable instead.

In `@apps/stats-web/src/components/layout/MobileNav.tsx`:
- Line 7: Remove the unused import of Link from react-router-dom in the
MobileNav component: delete the import specifier "Link" from the import
statement at the top of MobileNav.tsx so the file only imports what it actually
uses (e.g., keep other imports if any); this cleans up the unused symbol since
navigation is handled by NavLinks and external links use <a> elements.

In `@apps/stats-web/src/components/layout/Nav.tsx`:
- Around line 63-68: In Nav.tsx the Discord link's screen-reader-only text is
incorrect ("Twitter"); update the sr-only span inside the anchor wrapping the
<Button> and <Discord> icon so it reads a correct accessible label like
"Discord" or "Discord server" (keep the <Discord> icon and Button variant
unchanged) to ensure assistive tech announces the link properly.

In
`@apps/stats-web/src/components/transactions/akash/MsgSignProviderAttributes.tsx`:
- Line 13: In the MsgSignProviderAttributes component, guard the Link creation
against a missing owner by conditionally rendering LabelValue.value: check
message?.data?.owner before calling UrlService.address and rendering <Link>, and
fall back to a plain text owner or empty state when undefined (mirroring the
pattern used in MsgCreateDeployment.tsx); update the usage site where LabelValue
is passed (the line that currently calls
UrlService.address(message?.data?.owner)) to perform that null check so
UrlService.address is never called with undefined.

In `@apps/stats-web/src/context/FlagProvider/FlagProvider.tsx`:
- Around line 3-21: The conditional in FlagProvider is dead: both branches
return children, so remove the if-check referencing
browserEnvConfig.VITE_UNLEASH_ENABLE_ALL and drop the now-unused import
browserEnvConfig; keep a single concise comment explaining this is a simplified
provider for the Vite SPA and return children unconditionally from the
FlagProvider component (Props, FlagProvider, VITE_UNLEASH_ENABLE_ALL referenced
to locate the conditional).
- Around line 5-12: The FlagProvider contains redundant comments and an
unnecessary conditional: remove the block comment and inline comments and
replace the if/else that checks VITE_UNLEASH_ENABLE_ALL with a single return of
the children; keep the exported Props type and the FlagProvider signature intact
(Props, FlagProvider, and VITE_UNLEASH_ENABLE_ALL are the symbols to locate),
i.e., delete the comment block and conditional branches and simply return
<>{children}</> from FlagProvider.

In `@apps/stats-web/src/hooks/useTopBanner.tsx`:
- Line 3: The app is importing useVariant from `@unleash/proxy-client-react` but
FlagProvider does not supply the required Unleash context; update FlagProvider
(the component named FlagProvider) to wrap its children with UnleashProvider
from `@unleash/proxy-client-react` and pass appropriate props (e.g., clientKey /
url / refreshInterval or the existing config you use elsewhere) so useVariant
will find the context, or if you don't intend to integrate Unleash yet, revert
the import in useTopBanner to the previous feature-flag package instead of
`@unleash/proxy-client-react`; ensure the change targets the FlagProvider
component and the useVariant imports to restore runtime context availability for
useVariant.

In `@apps/stats-web/src/pages/addresses/`[address]/page.tsx:
- Around line 14-35: AddressDetailPage currently uses useParams().address
directly (address || "") which can produce empty strings and render “Account
undefined” or invalid tab URLs; create a single safe address variable (e.g.,
safeAddress = address ?? "") and add an early guard that returns a short-circuit
UI (or 404/redirect) when safeAddress is empty before rendering AddressLayout;
then use safeAddress everywhere (Helmet title, AddressLayout props, and any
tab/link generation) and apply the same guard/update in the loading and error
branches so no layout or links are rendered with an empty/undefined address.

In `@apps/stats-web/src/pages/blocks/`[height]/BlockInfo.tsx:
- Line 18: Fix the label typo in BlockInfo by changing the LabelValue for the
proposer from "Poposer" to "Proposer"; locate the LabelValue usage that renders
block.proposer (see component BlockInfo and the LabelValue instance that uses
block.proposer.operatorAddress and block.proposer.moniker) and update the label
string to "Proposer".

In `@apps/stats-web/src/pages/home/page.tsx`:
- Line 5: The import of PageContainer is wrong: it is a named export but is
being imported as a default; update the import to use a named import (e.g.,
change the default-style import to import { PageContainer } from the module) so
it matches the export in PageContainer.tsx and compiles correctly; verify usages
of PageContainer remain unchanged.

In `@apps/stats-web/src/services/error-handler/error-handler.service.ts`:
- Around line 11-18: Replace the stubbed getTraceData() in
error-handler.service.ts with the real Sentry trace data by importing
getTraceData from '@sentry/react' (e.g., import { getTraceData as
sentryGetTraceData } from '@sentry/react') and returning sentryGetTraceData()
inside the getTraceData() method; then add an axios request interceptor (using
axios.interceptors.request.use) where you call the service.getTraceData() and,
if present, set the outgoing request headers 'traceparent' (use traceIdW3C from
the returned object) and 'baggage' (use baggage), following the same pattern
used in apps/deploy-web/src/services/app-di-container/app-di-container.ts so
distributed traces are propagated to backend requests.

In `@apps/stats-web/tsconfig.json`:
- Around line 23-24: Update the tsconfig.json "include" setting to broaden IDE
type-checking and navigation: replace the narrow ["src"] array with
["**/*.ts","**/*.tsx"] so tests, generated types, and config files are covered
while leaving tsconfig.build.json to narrow build scope; modify the "include"
property in tsconfig.json accordingly.

In `@apps/stats-web/vite.config.ts`:
- Around line 5-17: The define in vite config currently sets
"process.env.VITE_APP_VERSION" which doesn't match the app's usage of
import.meta.env.VITE_APP_VERSION (see defineConfig and loadEnv); update the
define key to "import.meta.env.VITE_APP_VERSION" and assign
JSON.stringify(env.npm_package_version || "0.0.0") so the app (e.g., App.tsx
line using import.meta.env.VITE_APP_VERSION) receives the version at runtime.

---

Nitpick comments:
In
`@apps/stats-web/src/components/transactions/akash/MsgDeleteProviderAttributes.tsx`:
- Line 13: The component MsgDeleteProviderAttributes.tsx contains a TODO to add
a link to the provider page; replace that comment with a clickable link (using
your project's Link/router component) that navigates to the provider detail
route, e.g., /providers/:address, using the provider address available on the
message (e.g., msg.provider or props.provider.address) and render the address
(or a shortened form) as the link text; ensure you import the Link component if
not present and preserve existing styling/aria attributes used in
MsgDeleteProviderAttributes.

In `@apps/stats-web/src/components/transactions/generic/MsgDelegate.tsx`:
- Around line 28-37: The file contains a commented-out JSX block inside the
MsgDelegate component (the MessageLabelValue with label "Auto Claim Reward"
using coinsToAmount and AKTLabel) which is dead code; remove this commented
block to clean up the component or, if the feature is planned, instead create a
tracked issue and add a TODO referencing that issue number; ensure you remove
the commented lines around MessageLabelValue, coinsToAmount(...) and AKTLabel
usage so the component contains only active code.

In `@apps/stats-web/src/lib/createLogger/createLogger.ts`:
- Around line 4-7: createLogger currently returns a LoggerService without Sentry
trace context (trace correlation was removed); restore trace propagation by
extracting the current Sentry trace/span info from `@sentry/react` (e.g., via
Sentry.getCurrentHub().getScope() / getSpan() or the SDK's traceparent) and
inject that data into the LoggerService (either by adding trace fields to the
options passed into createLogger or by registering a log processor on
LoggerService to append trace_id/span_id/traceparent). Update createLogger to
obtain the Sentry trace info when available and attach it to each log event so
logs can be correlated with Sentry traces.

In
`@apps/stats-web/src/pages/addresses/`[address]/deployments/[dseq]/EventRow.tsx:
- Around line 21-23: In EventRow, replace the React Router Link used to open a
new tab with a plain anchor: use an <a> element with
href={UrlService.transaction(event.txHash)}, target="_blank" and rel="noopener
noreferrer" and render getSplitText(event.txHash, 6, 6) as its child; update
imports if needed to remove the Link import to avoid unused symbols.

In `@apps/stats-web/src/pages/addresses/`[address]/deployments/[dseq]/page.tsx:
- Line 10: Remove the unused type import DeploymentDetail from the top of the
file; locate the import line that reads "import type { DeploymentDetail } from
'@/types';" and delete it since the deployment variable is typed by
useDeployment and DeploymentDetail is not referenced anywhere in this file.

In `@apps/stats-web/src/pages/addresses/`[address]/transactions/columns.tsx:
- Around line 20-22: The current code uses the Link component for an
external/new-tab navigation (Link
to={UrlService.transaction(row.getValue("hash"))} target="_blank"), but this
should be a plain anchor to be semantic and avoid client-side routing; replace
the Link with an <a> element using
href={UrlService.transaction(row.getValue("hash"))}, keep the displayed text
from getSplitText(row.getValue("hash"), 6, 6), and add target="_blank" plus
rel="noopener noreferrer" for security and performance.

In `@apps/stats-web/src/pages/home/StatsCard.tsx`:
- Around line 34-36: The visibility condition in the StatsCard component still
checks diffNumber but only renders DiffPercentageChip for diffPercent; update
the component so the condition and props match: either remove diffNumber from
the visibility check and from the StatsCard props/interface if you no longer
need it, or render the diffNumber output (e.g., add a DiffNumberChip/span next
to DiffPercentageChip) and ensure the StatsCard props/interface includes
diffNumber. Update the conditional line that currently reads {(!!diffNumber ||
!!diffPercent) && (...) } and the StatsCard props/type accordingly, and ensure
any styling/className passed to DiffPercentageChip remains correct.

In `@apps/stats-web/src/pages/transactions/`[hash]/TransactionInfo.tsx:
- Around line 16-17: The code lazily imports FormattedRelativeTime and
FormattedDate which are trivial react-intl components and cause unnecessary
Suspense/fallbacks and extra bundles; replace the lazy(...) imports with direct
static imports from "react-intl" (use import { FormattedRelativeTime,
FormattedDate } from "react-intl") and remove the surrounding Suspense wrapper
used around the time display so the components render normally without the
loading fallback.

In `@apps/stats-web/src/services/error-handler/error-handler.service.ts`:
- Around line 74-89: Remove the dead exported helper sentryTraceToW3C from
error-handler.service.ts: delete the entire sentryTraceToW3C function definition
and its export, ensure there are no local references to sentryTraceToW3C (search
the app) and run TypeScript/ESLint checks to confirm compilation; leave usage in
other apps (e.g., deploy-web) untouched and do not change getTraceData.

Comment on lines +5 to 15
"type": "module",
"scripts": {
"build": "npm run build-env-schemas && next build",
"build": "vite build",
"typecheck": "tsc --noEmit",
"build:container": "dc build stats-web --build-arg DEPLOYMENT_ENV=production",
"build-env-schemas": "tsc src/config/env-config.schema.ts --outDir . --skipLibCheck --target es2023 --moduleResolution node",
"dev": "next dev",
"dev:inspect": "cross-env NODE_OPTIONS=\"--inspect\" ../../node_modules/.bin/next start -p 3001",
"dev": "vite",
"format": "prettier --write ./*.{ts,js,json} **/*.{ts,tsx,js,json}",
"lint": "eslint .",
"start": "next start"
"preview": "vite preview",
"start": "vite preview"
},
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Feb 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all .js files under apps/stats-web
echo "=== All .js files in apps/stats-web ==="
find apps/stats-web -name "*.js" -type f

echo -e "\n=== Searching for CommonJS patterns in .js files ==="
rg -n "module\.exports|require\(" apps/stats-web -g '*.js' 2>/dev/null || echo "No CommonJS patterns found"

Repository: akash-network/console

Length of output: 413


Rename .eslintrc.js and .prettierrc.js to .cjs or convert them to ESM syntax.

Setting "type": "module" causes Node to treat all .js files as ESM modules. Both .eslintrc.js and .prettierrc.js currently use CommonJS (module.exports and require()), which will fail under ESM. Either rename them to .cjs to preserve CommonJS behavior, or convert them to ESM equivalents:

apps/stats-web/.eslintrc.js:1: module.exports = require("@akashnetwork/dev-config/.eslintrc.next");
apps/stats-web/.prettierrc.js:1: module.exports = require("@akashnetwork/dev-config/.prettierrc");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/package.json` around lines 5 - 15, The repo sets "type":
"module" in package.json which makes Node treat .js as ESM, but
apps/stats-web/.eslintrc.js and apps/stats-web/.prettierrc.js currently use
CommonJS (module.exports/require), causing runtime load errors; either rename
those two files to .cjs to keep CommonJS behavior, or convert them to ESM by
replacing the CommonJS require/module.exports with ESM import/export (e.g.,
import the shared config from "@akashnetwork/dev-config" and export it as
default), ensuring ESLint and Prettier can load under the ESM package type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@claude why remove the build-env-schemas scripts?

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


import { DashboardContainer } from "./DashboardContainer";

import PageContainer from "@/components/PageContainer";
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 | 🟡 Minor

Import mismatch: PageContainer is a named export.

Based on the relevant code snippet from PageContainer.tsx, PageContainer is exported as a named export (export const PageContainer), but here it's imported as a default import.

Proposed fix
-import PageContainer from "@/components/PageContainer";
+import { PageContainer } from "@/components/PageContainer";
📝 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
import PageContainer from "@/components/PageContainer";
import { PageContainer } from "@/components/PageContainer";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/pages/home/page.tsx` at line 5, The import of
PageContainer is wrong: it is a named export but is being imported as a default;
update the import to use a named import (e.g., change the default-style import
to import { PageContainer } from the module) so it matches the export in
PageContainer.tsx and compiles correctly; verify usages of PageContainer remain
unchanged.

Comment on lines +23 to +24
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
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:

#!/bin/bash
# Check current tsconfig.json structure in apps/stats-web
echo "=== apps/stats-web/tsconfig.json ==="
cat -n apps/stats-web/tsconfig.json

echo -e "\n=== Check for separate tsconfig files in apps/stats-web ==="
fd -a 'tsconfig.*\.json' apps/stats-web

echo -e "\n=== Compare with apps/api/tsconfig.json ==="
cat -n apps/api/tsconfig.json | head -30

echo -e "\n=== Check for tsconfig.build.json in apps/api ==="
fd -a 'tsconfig.*\.json' apps/api

Repository: akash-network/console

Length of output: 1450


🏁 Script executed:

#!/bin/bash
# Check the tsconfig.build.json structure in both apps
echo "=== apps/stats-web/tsconfig.build.json ==="
cat -n apps/stats-web/tsconfig.build.json

echo -e "\n=== apps/api/tsconfig.build.json ==="
cat -n apps/api/tsconfig.build.json

Repository: akash-network/console

Length of output: 1508


Broaden include to match IDE coverage conventions used in other apps.
The narrow ["src"] pattern limits IDE type checking and navigation. Use ["**/*.ts", "**/*.tsx"] to provide IDE coverage across test files, generated types, and configuration files, while the separate tsconfig.build.json narrows the scope for build tooling.

Recommended change
-  "include": ["src"],
+  "include": ["**/*.ts", "**/*.tsx"],
📝 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
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": ["**/*.ts", "**/*.tsx"],
"references": [{ "path": "./tsconfig.node.json" }]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/tsconfig.json` around lines 23 - 24, Update the tsconfig.json
"include" setting to broaden IDE type-checking and navigation: replace the
narrow ["src"] array with ["**/*.ts","**/*.tsx"] so tests, generated types, and
config files are covered while leaving tsconfig.build.json to narrow build
scope; modify the "include" property in tsconfig.json accordingly.

- Add missing GoogleAnalytics component to App.tsx
- Add Geist font @font-face CSS (replaces next/font/local)
- Remove 56 unnecessary "use client" directives
- Create Dockerfile.vite for static SPA builds with nginx
- Create nginx.conf for SPA routing with gzip and caching
- Update docker-compose to use Dockerfile.vite for stats-web
- Migrate env files from NEXT_PUBLIC_ to VITE_ prefix
- Fix ESLint config for ESM compatibility (.js -> .cjs)
- Switch ESLint from next config to ts config
- Remove unused imports (Link, DeploymentDetail)
- Fix import sorting across all modified files
- Update .gitignore for Vite output (dist instead of .next)
- Set up vitest with jsdom environment and React Testing Library
- Add test setup file with matchMedia mock and cleanup
- Add test scripts (test, test:unit, test:unit:watch)
- Add test dependencies (vitest, @testing-library/react, jsdom)
- Update .dockerignore to reference dist instead of .next
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (6)
apps/stats-web/src/pages/transactions/[hash]/page.tsx (1)

14-16: Consider defensive handling for hash parameter.

useParams<{ hash: string }>() types hash as string, but React Router's useParams actually returns string | undefined for each param. The current code handles this with hash || "", which works because useTransaction has enabled: !!hash that prevents fetching with an empty string.

However, you could make the typing more explicit:

-  const { hash } = useParams<{ hash: string }>();
-  const { data: transaction, isLoading, error } = useTransaction(hash || "");
-  const splitTxHash = getSplitText(hash || "", 6, 6);
+  const { hash = "" } = useParams<{ hash: string }>();
+  const { data: transaction, isLoading, error } = useTransaction(hash);
+  const splitTxHash = getSplitText(hash, 6, 6);

This is optional since the current approach is functionally correct.

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

In `@apps/stats-web/src/pages/transactions/`[hash]/page.tsx around lines 14 - 16,
The param `hash` should be typed as possibly undefined to reflect React Router
behavior; update the useParams generic to accept an optional/undefined hash
(e.g., useParams<{ hash?: string }> or useParams<{ hash: string | undefined }>)
and then keep the existing defensive logic (hash || "") where you call
useTransaction(hash || "") and getSplitText(hash || "", 6, 6) so types align and
no runtime change is needed; reference useParams, hash, useTransaction,
getSplitText, and splitTxHash when making the change.
apps/stats-web/src/pages/transactions/[hash]/TransactionInfo.tsx (1)

1-2: Minor formatting: missing space after lazy,

-import React, { lazy,Suspense } from "react";
+import React, { lazy, Suspense } from "react";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/pages/transactions/`[hash]/TransactionInfo.tsx around
lines 1 - 2, The import statement in TransactionInfo.tsx has a formatting issue:
there's no space after the comma in "lazy,Suspense"; update the import so the
named imports read "lazy, Suspense" (i.e., adjust the import that includes
React, lazy and Suspense) to restore consistent spacing.
apps/stats-web/src/pages/addresses/[address]/AddressLayout.tsx (1)

20-33: Guard newValue to AddressTab to make invalid tabs explicit.

Right now any string falls through to the default route. A small guard makes the assumption explicit and avoids silent fallbacks if a future tab value changes.

♻️ Suggested change
+const isAddressTab = (value: string): value is AddressTab =>
+  value === "address" || value === "transactions" || value === "deployments";
+
 const handleTabChange = (newValue: string) => {
+  if (!isAddressTab(newValue)) return;
   switch (newValue) {
     case "transactions":
       navigate(UrlService.addressTransactions(address));
       break;
     case "deployments":
       navigate(UrlService.addressDeployments(address));
       break;
     case "address":
     default:
       navigate(UrlService.address(address));
       break;
   }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/pages/addresses/`[address]/AddressLayout.tsx around lines
20 - 33, The switch in handleTabChange currently accepts any string and silently
falls back to the default; add an explicit guard that narrows newValue to the
AddressTab union (e.g., implement an isAddressTab(type: string): type is
AddressTab helper or use a Set of allowed values) and early-return or warn/throw
on invalid values before the switch, then use the guarded AddressTab in the
existing cases that call navigate(UrlService.address(...)),
navigate(UrlService.addressTransactions(...)) and
navigate(UrlService.addressDeployments(...)); this makes invalid tab values
explicit and prevents silent fallthrough to the default route.
apps/stats-web/src/components/NavLinks.tsx (1)

23-53: Consider extracting shared link content to reduce duplication.

The anchor and Link branches share identical className logic and inner content (icon, title, label). Extracting the shared parts into a helper component or variable would improve maintainability.

♻️ Optional refactor to reduce duplication
+const linkClassName = (variant: "default" | "ghost", index: number) =>
+  cn(buttonVariants({ variant, size: "sm" }), "text-md flex w-full items-center justify-start", {
+    ["mt-2"]: index > 0,
+    ["text-foreground"]: variant === "ghost"
+  });
+
+const LinkContent = ({ link }: { link: NavLinksProps["links"][number] }) => (
+  <>
+    {!!link.icon && <span className="mr-2">{link.icon}</span>}
+    {link.title}
+    {link.label && <span className={cn("ml-auto", link.variant === "default" && "text-background dark:text-white")}>{link.label}</span>}
+  </>
+);
+
 export function NavLinks({ links }: NavLinksProps) {
   return (
     <div className="group flex flex-col gap-4 py-2">
       <nav className="grid gap-1 px-2">
         {links.map((link, index) =>
           link.isExternal ? (
             <a
               key={index}
               target="_blank"
               rel={link.rel || "noreferrer"}
               href={link.href}
-              className={cn(buttonVariants({ variant: link.variant, size: "sm" }), "text-md flex w-full items-center justify-start", {
-                ["mt-2"]: index > 0,
-                ["text-foreground"]: link.variant === "ghost"
-              })}
+              className={linkClassName(link.variant, index)}
             >
-              {!!link.icon && <span className="mr-2">{link.icon}</span>}
-              {link.title}
-              {link.label && <span className={cn("ml-auto", link.variant === "default" && "text-background dark:text-white")}>{link.label}</span>}
+              <LinkContent link={link} />
             </a>
           ) : (
             <Link
               key={index}
               to={link.href}
-              className={cn(buttonVariants({ variant: link.variant, size: "sm" }), "text-md flex w-full items-center justify-start", {
-                ["mt-2"]: index > 0,
-                ["text-foreground"]: link.variant === "ghost"
-              })}
+              className={linkClassName(link.variant, index)}
             >
-              {!!link.icon && <span className="mr-2">{link.icon}</span>}
-              {link.title}
-              {link.label && <span className={cn("ml-auto", link.variant === "default" && "text-background dark:text-white")}>{link.label}</span>}
+              <LinkContent link={link} />
             </Link>
           )
         )}
       </nav>
     </div>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/components/NavLinks.tsx` around lines 23 - 53, The map
branches for external <a> and internal <Link> in NavLinks.tsx duplicate
className construction and inner content (icon, title, label); extract the
shared parts into a small helper component or render function (e.g., LinkContent
or renderLinkContent) that takes the link object and returns the
icon/title/label JSX and shared className string, then use that helper in both
the external anchor branch and the <Link> branch so only the wrapper element
differs while className and inner markup are centralized.
apps/stats-web/env/.env (1)

3-3: Remove quotes to satisfy dotenv-linter.

The linter flags the quoted DSN value; unquoted values are the expected style here.

♻️ Suggested change
-VITE_SENTRY_DSN="https://c2af143d11c20d4bf20e7b668d493aaf@o877251.ingest.us.sentry.io/4510740211105792"
+VITE_SENTRY_DSN=https://c2af143d11c20d4bf20e7b668d493aaf@o877251.ingest.us.sentry.io/4510740211105792
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/env/.env` at line 3, The VITE_SENTRY_DSN value in the
environment file is quoted which triggers dotenv-linter; remove the surrounding
double quotes so the VITE_SENTRY_DSN entry is an unquoted value (update the
VITE_SENTRY_DSN line to use the raw DSN string without " characters) to satisfy
the linter.
apps/stats-web/src/pages/addresses/[address]/deployments/[dseq]/page.tsx (1)

15-22: Consider adding Helmet to the error path for consistent page titles.

The error path returns early without the <Helmet> component, so the browser tab title won't update when an error occurs. For consistency, consider moving the Helmet outside the conditional or adding it to the error state as well.

♻️ Proposed fix
+import { Helmet } from "react-helmet-async";
+import { useParams } from "react-router-dom";
+import { Alert, Spinner } from "@akashnetwork/ui/components";
+
+import { DeploymentInfo } from "./DeploymentInfo";
+
+import PageContainer from "@/components/PageContainer";
+import { Title } from "@/components/Title";
+import { useDeployment } from "@/queries";
+
 export function DeploymentDetailPage() {
   const { address, dseq } = useParams<{ address: string; dseq: string }>();
   const { data: deployment, isLoading, error } = useDeployment(address || "", dseq || "");
 
+  const pageTitle = `Deployment ${address}/${dseq} - Akash Network Stats`;
+
   if (error) {
     return (
-      <PageContainer>
-        <Title className="mb-8">Deployment Details</Title>
-        <Alert variant="destructive">Error loading deployment data. Please try again.</Alert>
-      </PageContainer>
+      <>
+        <Helmet>
+          <title>{pageTitle}</title>
+        </Helmet>
+        <PageContainer>
+          <Title className="mb-8">Deployment Details</Title>
+          <Alert variant="destructive">Error loading deployment data. Please try again.</Alert>
+        </PageContainer>
+      </>
     );
   }
 
   return (
     <>
       <Helmet>
-        <title>Deployment {address}/{dseq} - Akash Network Stats</title>
+        <title>{pageTitle}</title>
       </Helmet>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/pages/addresses/`[address]/deployments/[dseq]/page.tsx
around lines 15 - 22, The error branch returns early without rendering the
<Helmet> (page title) so the browser tab isn't updated on error; update the
error return to include the same <Helmet> (setting the "Deployment Details"
title) or move the <Helmet> component above the if (error) check so both the
normal render and the error path render it; locate the error conditional and the
return that renders <PageContainer>, <Title>, and <Alert> and add the <Helmet>
there (or hoist it) to ensure consistent titles.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/stats-web/.eslintrc.cjs`:
- Line 1: Remove the unused import statement "const path = require('path');"
from the ESLint config; locate the top-level require for the path module (the
const path variable) and delete that line so the module is not imported when
unused.

In `@apps/stats-web/env/.env.production`:
- Around line 1-9: Reorder the env keys in apps/stats-web/env/.env.production to
satisfy dotenv-linter (e.g., sort the variables like VITE_API_BASE_URL,
VITE_BASE_API_MAINNET_URL, VITE_BASE_API_SANDBOX_URL, VITE_BASE_API_TESTNET_URL,
VITE_GA_MEASUREMENT_ID, VITE_NODE_ENV, VITE_SENTRY_ENABLED) and remove the
quotes around the boolean assignment for VITE_SENTRY_ENABLED so it becomes
unquoted true; keep the existing variable references (e.g., VITE_API_BASE_URL
referencing VITE_BASE_API_MAINNET_URL) intact.

In `@apps/stats-web/env/.env.sample`:
- Around line 1-4: The dotenv-linter expects a specific key order; reorder the
variables in apps/stats-web/env/.env.sample so that VITE_BASE_API_SANDBOX_URL
appears before VITE_BASE_API_TESTNET_URL (keep the rest intact:
VITE_API_BASE_URL, VITE_BASE_API_MAINNET_URL, VITE_BASE_API_SANDBOX_URL,
VITE_BASE_API_TESTNET_URL) and re-run the linter to verify the file now passes;
modify the file where these symbols are defined to reflect the new ordering.

In `@apps/stats-web/env/.env.staging`:
- Around line 1-7: Reorder the environment variables in .env.staging to satisfy
dotenv-linter's expected key ordering (e.g., sort the VITE_ keys alphabetically:
VITE_API_BASE_URL, VITE_BASE_API_MAINNET_URL, VITE_BASE_API_SANDBOX_URL,
VITE_BASE_API_TESTNET_URL, VITE_NODE_ENV) and remove the extraneous quotes from
the boolean value for VITE_SENTRY_ENABLED so it is unquoted (true) instead of
"true"; update the lines referencing variables (VITE_BASE_API_TESTNET_URL and
VITE_API_BASE_URL) to remain correct after reordering.

In
`@apps/stats-web/src/pages/addresses/`[address]/deployments/[dseq]/EventRow.tsx:
- Line 21: The Link usage with target="_blank" (Link component calling
UrlService.transaction(event.txHash)) is vulnerable to reverse-tabnabbing;
update the Link element to include rel="noopener noreferrer" when
target="_blank" is present (e.g., on the Link that wraps
UrlService.transaction(event.txHash)) so the new tab cannot access
window.opener.

In `@apps/stats-web/src/pages/graph/`[snapshot]/page.tsx:
- Around line 16-39: The code unsafely casts snapshotUrlParam from useParams to
SnapshotsUrlParam which hides possible undefined; update the useParams type to
useParams<{ snapshot?: string }>() (or otherwise treat snapshotUrlParam as
possibly undefined), then explicitly validate snapshotUrlParam before calling
urlParamToSnapshot/getTitle and before rendering GraphContainer; add a guard
that redirects or uses the NOT_FOUND fallback when snapshotUrlParam is
missing/invalid so urlParamToSnapshot and GraphContainer always receive a
properly validated Snapshots value.

In `@apps/stats-web/src/pages/provider-graph/`[snapshot]/page.tsx:
- Around line 16-39: useParams is being asserted to non-null which hides that it
can return undefined; change the useParams typing to useParams<{ snapshot?:
string }> (remove the "as ProviderSnapshotsUrlParam" and "as ProviderSnapshots"
assertions), call urlParamToProviderSnapshot with the possibly-undefined param
(e.g., pass snapshotUrlParam directly) and add an explicit guard using the
parsed snapshot (from urlParamToProviderSnapshot) before rendering the
page/GraphContainer (handle SNAPSHOT_NOT_FOUND or render a fallback/error
state), ensuring getTitle and GraphContainer receive the correctly-typed
snapshot without assertions.

In `@packages/docker/Dockerfile.vite`:
- Around line 24-27: The COPY command using a glob (/packages/*/package.json)
needs the destination to be a directory, so update the COPY --parents
/packages/*/package.json /app to target a directory with a trailing slash (i.e.,
/app/) to avoid Docker build errors; locate the COPY --parents line in the
Dockerfile.vite and add the trailing slash to the destination path.
- Around line 58-65: The production stage is running nginx as root; change it to
use the non‑root nginx user and ensure static files and config are readable by
that user: after copying assets and nginx.conf (the COPY lines referencing
/app/$WORKSPACE/dist and $WORKSPACE/nginx.conf) change ownership of
/usr/share/nginx/html and /etc/nginx/conf.d/default.conf to the nginx user
(UID/GID used by the base image) and add a USER directive (use the nginx user
name or UID) before the CMD so nginx runs as the non‑root user.

---

Duplicate comments:
In `@apps/stats-web/src/components/blockchain/TransactionRow.tsx`:
- Around line 26-28: The Link in TransactionRow.tsx that renders {txHash} and
uses UrlService.transaction(transaction.hash) opens in a new tab
(target="_blank") but lacks rel attributes; update the Link component props in
the TransactionRow component to include rel="noopener noreferrer" (i.e., <Link
to={UrlService.transaction(transaction.hash)} target="_blank" rel="noopener
noreferrer">) to prevent reverse tabnabbing.

In `@apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx`:
- Around line 7-17: The module-level `initialized` flag used in the
GoogleAnalytics component is intentional and correct: ensure the singleton
initialization behavior by keeping the `initialized` variable (module scope) and
the existing guard around ReactGA.initialize in the GoogleAnalytics function
(useEffect) so the analytics SDK is only initialized once per page load; add a
short in-code comment near the `initialized` declaration referencing why
module-level state is used (prevents duplicate ReactGA.initialize calls across
remounts) and keep the existing useEffect guard that checks
browserEnvConfig.VITE_NODE_ENV, VITE_GA_MEASUREMENT_ID, and !initialized before
calling ReactGA.initialize.

In `@apps/stats-web/src/components/layout/Nav.tsx`:
- Around line 63-68: The screen-reader label for the Discord link is incorrect;
in the Nav component update the visually-hidden <span> inside the Discord anchor
(the <a href="https://discord.akash.network"> block that renders <Button
variant="ghost" size="icon"> with the <Discord/> icon) to read "Discord" instead
of "Twitter" so the sr-only text matches the link and icon.

---

Nitpick comments:
In `@apps/stats-web/env/.env`:
- Line 3: The VITE_SENTRY_DSN value in the environment file is quoted which
triggers dotenv-linter; remove the surrounding double quotes so the
VITE_SENTRY_DSN entry is an unquoted value (update the VITE_SENTRY_DSN line to
use the raw DSN string without " characters) to satisfy the linter.

In `@apps/stats-web/src/components/NavLinks.tsx`:
- Around line 23-53: The map branches for external <a> and internal <Link> in
NavLinks.tsx duplicate className construction and inner content (icon, title,
label); extract the shared parts into a small helper component or render
function (e.g., LinkContent or renderLinkContent) that takes the link object and
returns the icon/title/label JSX and shared className string, then use that
helper in both the external anchor branch and the <Link> branch so only the
wrapper element differs while className and inner markup are centralized.

In `@apps/stats-web/src/pages/addresses/`[address]/AddressLayout.tsx:
- Around line 20-33: The switch in handleTabChange currently accepts any string
and silently falls back to the default; add an explicit guard that narrows
newValue to the AddressTab union (e.g., implement an isAddressTab(type: string):
type is AddressTab helper or use a Set of allowed values) and early-return or
warn/throw on invalid values before the switch, then use the guarded AddressTab
in the existing cases that call navigate(UrlService.address(...)),
navigate(UrlService.addressTransactions(...)) and
navigate(UrlService.addressDeployments(...)); this makes invalid tab values
explicit and prevents silent fallthrough to the default route.

In `@apps/stats-web/src/pages/addresses/`[address]/deployments/[dseq]/page.tsx:
- Around line 15-22: The error branch returns early without rendering the
<Helmet> (page title) so the browser tab isn't updated on error; update the
error return to include the same <Helmet> (setting the "Deployment Details"
title) or move the <Helmet> component above the if (error) check so both the
normal render and the error path render it; locate the error conditional and the
return that renders <PageContainer>, <Title>, and <Alert> and add the <Helmet>
there (or hoist it) to ensure consistent titles.

In `@apps/stats-web/src/pages/transactions/`[hash]/page.tsx:
- Around line 14-16: The param `hash` should be typed as possibly undefined to
reflect React Router behavior; update the useParams generic to accept an
optional/undefined hash (e.g., useParams<{ hash?: string }> or useParams<{ hash:
string | undefined }>) and then keep the existing defensive logic (hash || "")
where you call useTransaction(hash || "") and getSplitText(hash || "", 6, 6) so
types align and no runtime change is needed; reference useParams, hash,
useTransaction, getSplitText, and splitTxHash when making the change.

In `@apps/stats-web/src/pages/transactions/`[hash]/TransactionInfo.tsx:
- Around line 1-2: The import statement in TransactionInfo.tsx has a formatting
issue: there's no space after the comma in "lazy,Suspense"; update the import so
the named imports read "lazy, Suspense" (i.e., adjust the import that includes
React, lazy and Suspense) to restore consistent spacing.

Comment on lines +1 to +4
VITE_API_BASE_URL=
VITE_BASE_API_MAINNET_URL=
VITE_BASE_API_TESTNET_URL=
VITE_BASE_API_SANDBOX_URL=
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 | 🟡 Minor

Keep key order consistent with dotenv-linter.

The linter expects VITE_BASE_API_SANDBOX_URL before VITE_BASE_API_TESTNET_URL.

♻️ Suggested change
 VITE_API_BASE_URL=
 VITE_BASE_API_MAINNET_URL=
-VITE_BASE_API_TESTNET_URL=
 VITE_BASE_API_SANDBOX_URL=
+VITE_BASE_API_TESTNET_URL=
📝 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
VITE_API_BASE_URL=
VITE_BASE_API_MAINNET_URL=
VITE_BASE_API_TESTNET_URL=
VITE_BASE_API_SANDBOX_URL=
VITE_API_BASE_URL=
VITE_BASE_API_MAINNET_URL=
VITE_BASE_API_SANDBOX_URL=
VITE_BASE_API_TESTNET_URL=
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 4-4: [UnorderedKey] The VITE_BASE_API_SANDBOX_URL key should go before the VITE_BASE_API_TESTNET_URL key

(UnorderedKey)

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

In `@apps/stats-web/env/.env.sample` around lines 1 - 4, The dotenv-linter expects
a specific key order; reorder the variables in apps/stats-web/env/.env.sample so
that VITE_BASE_API_SANDBOX_URL appears before VITE_BASE_API_TESTNET_URL (keep
the rest intact: VITE_API_BASE_URL, VITE_BASE_API_MAINNET_URL,
VITE_BASE_API_SANDBOX_URL, VITE_BASE_API_TESTNET_URL) and re-run the linter to
verify the file now passes; modify the file where these symbols are defined to
reflect the new ordering.

Comment on lines +16 to +39
const { snapshot: snapshotUrlParam } = useParams<{ snapshot: string }>();
const snapshot = urlParamToSnapshot(snapshotUrlParam as SnapshotsUrlParam);
const title = getTitle(snapshot as Snapshots);

return (
<PageContainer>
<div className="m-auto max-w-3xl">
<div className="mb-6 mt-6">
<Link href={UrlService.home()}>
<Button variant="ghost">
<ArrowLeft className="mr-2 h-4 w-4" /> Back
</Button>
</Link>
</div>
<>
<Helmet>
<title>{title} - Akash Network Stats</title>
</Helmet>
<PageContainer>
<div className="m-auto max-w-3xl">
<div className="mb-6 mt-6">
<Link to={UrlService.home()}>
<Button variant="ghost">
<ArrowLeft className="mr-2 h-4 w-4" /> Back
</Button>
</Link>
</div>

<div className="mb-2">
<h1 className="text-center text-3xl font-normal tracking-tight sm:text-left">{title}</h1>
</div>
<div className="mb-2">
<h1 className="text-center text-3xl font-normal tracking-tight sm:text-left">{title}</h1>
</div>

<GraphContainer snapshot={snapshot} />
</div>
</PageContainer>
<GraphContainer snapshot={snapshot} />
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:

#!/bin/bash
set -euo pipefail

# Find and inspect snapshotsUrlHelpers to understand urlParamToSnapshot
echo "=== Searching for snapshotsUrlHelpers ==="
fd 'snapshotsUrlHelpers' apps -t f

for f in $(fd 'snapshotsUrlHelpers' apps -t f 2>/dev/null || true); do
  echo -e "\n=== File: $f ==="
  cat -n "$f" | head -100
done

# Find GraphContainer definition
echo -e "\n\n=== Searching for GraphContainer ==="
fd 'GraphContainer' apps/stats-web/src -t f

for f in $(fd 'GraphContainer' apps/stats-web/src -t f 2>/dev/null || true); do
  echo -e "\n=== File: $f ==="
  wc -l "$f"
  cat -n "$f" | head -80
done

Repository: akash-network/console

Length of output: 11390


🏁 Script executed:

#!/bin/bash
# Additional search to understand Snapshots and SnapshotsUrlParam types
echo "=== Searching for Snapshots and SnapshotsUrlParam types ==="
rg "type Snapshots|type SnapshotsUrlParam|enum Snapshots|enum SnapshotsUrlParam" apps/stats-web/src -n

Repository: akash-network/console

Length of output: 261


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the full page.tsx file
echo "=== Full page.tsx content ==="
cat -n "apps/stats-web/src/pages/graph/[snapshot]/page.tsx"

echo -e "\n\n=== Searching for getTitle function ==="
rg -n "getTitle\|export.*getTitle" apps/stats-web/src -A 5

Repository: akash-network/console

Length of output: 3093


🏁 Script executed:

#!/bin/bash
# Check if there are any error boundaries or error handling for NOT_FOUND
echo "=== Searching for NOT_FOUND usage in page.tsx ==="
rg -n "NOT_FOUND" "apps/stats-web/src/pages/graph/[snapshot]/page.tsx"

echo -e "\n=== Checking useGraphSnapshot behavior ==="
rg -n "useGraphSnapshot" apps/stats-web/src -A 3 | head -50

Repository: akash-network/console

Length of output: 1671


Fix type casting to properly handle missing/undefined snapshot parameter.

useParams() can return undefined for the snapshot param. Casting undefined as SnapshotsUrlParam (line 17) is type-unsafe. While the current code handles this at runtime via the NOT_FOUND fallback and getTitle's default case, the type assertions mask the actual data flow. Either change the useParams type hint to { snapshot?: string } and check for undefined explicitly, or use a type guard to ensure the snapshot is valid before passing to dependent components. This improves type safety and makes the intent clearer.

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

In `@apps/stats-web/src/pages/graph/`[snapshot]/page.tsx around lines 16 - 39, The
code unsafely casts snapshotUrlParam from useParams to SnapshotsUrlParam which
hides possible undefined; update the useParams type to useParams<{ snapshot?:
string }>() (or otherwise treat snapshotUrlParam as possibly undefined), then
explicitly validate snapshotUrlParam before calling urlParamToSnapshot/getTitle
and before rendering GraphContainer; add a guard that redirects or uses the
NOT_FOUND fallback when snapshotUrlParam is missing/invalid so
urlParamToSnapshot and GraphContainer always receive a properly validated
Snapshots value.

Comment on lines +16 to +39
const { snapshot: snapshotUrlParam } = useParams<{ snapshot: string }>();
const snapshot = urlParamToProviderSnapshot(snapshotUrlParam as ProviderSnapshotsUrlParam);
const title = getTitle(snapshot as ProviderSnapshots);

return (
<PageContainer>
<div className="m-auto max-w-3xl">
<div className="mb-8">
<Link href={UrlService.home()}>
<Button variant="ghost">
<ArrowLeft className="mr-2 h-4 w-4" /> Back
</Button>
</Link>
<>
<Helmet>
<title>{title} - Akash Network Stats</title>
</Helmet>
<PageContainer>
<div className="m-auto max-w-3xl">
<div className="mb-8">
<Link to={UrlService.home()}>
<Button variant="ghost">
<ArrowLeft className="mr-2 h-4 w-4" /> Back
</Button>
</Link>
</div>

<div className="mb-2">
<h1 className="text-center text-3xl font-normal tracking-tight sm:text-left">{title}</h1>
</div>

<GraphContainer snapshot={snapshot} />
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find useParams import and check how it's used
rg -n "useParams" apps/stats-web/src/pages/provider-graph/[snapshot]/page.tsx -B 2 -A 2

# Find the snapshots URL helpers
fd "snapshots" apps/stats-web/src -t f | head -20

# Find urlParamToProviderSnapshot function
rg -rn "urlParamToProviderSnapshot" apps/stats-web/src --type ts --type tsx -B 3 -A 5

Repository: akash-network/console

Length of output: 708


🏁 Script executed:

#!/bin/bash
# Find GraphContainer component
fd "GraphContainer" apps/stats-web/src -t f

# Check the snapshot prop type in GraphContainer
for f in $(fd "GraphContainer" apps/stats-web/src -t f); do
  echo "=== File: $f ==="
  cat -n "$f" | head -50
done

Repository: akash-network/console

Length of output: 5779


🏁 Script executed:

#!/bin/bash
# Check type definitions for ProviderSnapshotsUrlParam and ProviderSnapshots
rg -rn "ProviderSnapshotsUrlParam\|ProviderSnapshots" apps/stats-web/src --type ts --type tsx | grep -E "(type|interface)" | head -20

Repository: akash-network/console

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Check what library useParams comes from and its behavior
rg -n "import.*useParams" apps/stats-web/src/pages/provider-graph/[snapshot]/page.tsx

Repository: akash-network/console

Length of output: 118


🏁 Script executed:

#!/bin/bash
# Check the snapshotsUrlHelpers file to understand urlParamToProviderSnapshot and SNAPSHOT_NOT_FOUND
cat -n apps/stats-web/src/lib/snapshotsUrlHelpers.ts

Repository: akash-network/console

Length of output: 2004


🏁 Script executed:

#!/bin/bash
# Check the snapshots types file
cat -n apps/stats-web/src/types/snapshots.ts | head -100

Repository: akash-network/console

Length of output: 1930


🏁 Script executed:

#!/bin/bash
# Also check how useParams actually types the return value
rg -rn "useParams.*snapshot.*string" apps/stats-web/src --type ts -A 3

Repository: akash-network/console

Length of output: 889


🏁 Script executed:

#!/bin/bash
# Check what useProviderGraphSnapshot does with SNAPSHOT_NOT_FOUND
rg -rn "useProviderGraphSnapshot\|SNAPSHOT_NOT_FOUND" apps/stats-web/src/queries --type ts -B 2 -A 5

Repository: akash-network/console

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check how getTitle handles the snapshot parameter
rg -rn "getTitle" apps/stats-web/src --type ts -B 2 -A 10 | grep -A 10 "export.*getTitle\|function getTitle"

Repository: akash-network/console

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check the actual page.tsx file in full to see complete context
cat -n apps/stats-web/src/pages/provider-graph/[snapshot]/page.tsx

Repository: akash-network/console

Length of output: 2436


🏁 Script executed:

#!/bin/bash
# Check if getTitle is actually called with SNAPSHOT_NOT_FOUND anywhere
rg -rn "getTitle" apps/stats-web/src/pages/provider-graph --type ts -B 3 -A 3

# Also check if there are similar patterns in the other graph page
cat -n apps/stats-web/src/pages/graph/[snapshot]/page.tsx | tail -30

Repository: akash-network/console

Length of output: 2423


🏁 Script executed:

#!/bin/bash
# Check the regular graph page implementation for comparison
cat -n apps/stats-web/src/pages/graph/[snapshot]/page.tsx

Repository: akash-network/console

Length of output: 3021


🏁 Script executed:

#!/bin/bash
# Check how react-router-dom's useParams actually types the return value
rg -rn "useParams" apps/stats-web/src/pages --type ts -B 1 -A 1 | head -30

Repository: akash-network/console

Length of output: 2574


🏁 Script executed:

#!/bin/bash
# Check how urlParamToProviderSnapshot actually handles undefined or invalid input
rg -rn "useProviderGraphSnapshot" apps/stats-web/src/queries --type ts -A 10

Repository: akash-network/console

Length of output: 804


🏁 Script executed:

#!/bin/bash
# Verify if undefined snapshot parameter causes issues with useProviderGraphSnapshot
fd "index.ts" apps/stats-web/src/queries -x cat -n {} | head -100

Repository: akash-network/console

Length of output: 399


Remove unnecessary type assertions; improve handling of missing snapshot parameter.

The type assertions (as ProviderSnapshotsUrlParam and as ProviderSnapshots) mask the fact that useParams() returns string | undefined. While the code currently works because urlParamToProviderSnapshot() returns SNAPSHOT_NOT_FOUND as a fallback and GraphContainer accepts this type, the misleading assertions reduce code clarity.

Improve type safety by:

  1. Typing useParams as { snapshot?: string }
  2. Adding an explicit guard to check the snapshot before rendering
  3. Removing the unnecessary assertions
✅ Suggested fix
-export function ProviderGraphPage() {
-  const { snapshot: snapshotUrlParam } = useParams<{ snapshot: string }>();
-  const snapshot = urlParamToProviderSnapshot(snapshotUrlParam as ProviderSnapshotsUrlParam);
-  const title = getTitle(snapshot as ProviderSnapshots);
+export function ProviderGraphPage() {
+  const { snapshot: snapshotUrlParam } = useParams<{ snapshot?: string }>();
+  if (!snapshotUrlParam) {
+    return (
+      <PageContainer>
+        <div className="m-auto max-w-3xl">
+          <h1 className="text-center text-3xl font-normal tracking-tight sm:text-left">Graph not found.</h1>
+        </div>
+      </PageContainer>
+    );
+  }
+  const snapshot = urlParamToProviderSnapshot(snapshotUrlParam as ProviderSnapshotsUrlParam);
+  const title = getTitle(snapshot as ProviderSnapshots);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/pages/provider-graph/`[snapshot]/page.tsx around lines 16
- 39, useParams is being asserted to non-null which hides that it can return
undefined; change the useParams typing to useParams<{ snapshot?: string }>
(remove the "as ProviderSnapshotsUrlParam" and "as ProviderSnapshots"
assertions), call urlParamToProviderSnapshot with the possibly-undefined param
(e.g., pass snapshotUrlParam directly) and add an explicit guard using the
parsed snapshot (from urlParamToProviderSnapshot) before rendering the
page/GraphContainer (handle SNAPSHOT_NOT_FOUND or render a fallback/error
state), ensuring getTitle and GraphContainer receive the correctly-typed
snapshot without assertions.

Comment on lines +24 to +27
COPY package*.json /app
COPY /$WORKSPACE/package.json /app/$WORKSPACE/package.json
COPY --parents /packages/*/package.json /app
# reset app version to 1.0.0 to avoid cache busting after every release
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:

# First, examine the actual file content
cat -n packages/docker/Dockerfile.vite | sed -n '20,35p'

Repository: akash-network/console

Length of output: 825


🏁 Script executed:

# Run the verification script from the review comment
python - <<'PY'
import pathlib, re
path = pathlib.Path("packages/docker/Dockerfile.vite")
lines = path.read_text().splitlines()
for i, line in enumerate(lines, 1):
    s = line.strip()
    if not s.startswith("COPY"):
        continue
    parts = s.split()
    if len(parts) < 3:
        continue
    dest = parts[-1]
    sources = [p for p in parts[1:-1] if not p.startswith("--")]
    if len(sources) > 1 and not dest.endswith("/"):
        print(f"Line {i}: destination does not end with '/': {dest}")
PY

Repository: akash-network/console

Length of output: 47


🌐 Web query:

Docker COPY multiple sources destination trailing slash requirement semantics

💡 Result:

In a Dockerfile, COPY (and ADD) have special semantics when you provide multiple sources:

  • Multiple sources are allowed in both shell form and JSON form:
    COPY <src>... <dest> or COPY ["<src>", ... , "<dest>"]. [1]

  • If you specify multiple <src> (either by listing them or by using a wildcard that expands to multiple matches), then <dest> must be a directory and must end with a trailing /—otherwise the build fails:
    COPY a b /some/dir/ (valid)
    COPY a b /some/dir (invalid). [1]

  • The trailing slash on the destination is significant even for single-source copies:
    COPY test.txt /abs creates a file literally at /abs (named abs).
    COPY test.txt /abs/ creates /abs/test.txt. [1]

  • Trailing slashes on source paths are ignored (e.g., COPY something/ /something is equivalent to COPY something /something). [1]

References: Docker Dockerfile reference (COPY section). [1]


Add trailing slash to COPY destination on line 26.

When COPY uses a glob pattern that matches multiple files (e.g., /packages/*/package.json), the destination must be a directory ending with /. Without the trailing slash, the Docker build will fail.

🛠️ Suggested fix
-COPY --parents /packages/*/package.json /app
+COPY --parents /packages/*/package.json /app/
📝 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
COPY package*.json /app
COPY /$WORKSPACE/package.json /app/$WORKSPACE/package.json
COPY --parents /packages/*/package.json /app
# reset app version to 1.0.0 to avoid cache busting after every release
COPY package*.json /app
COPY /$WORKSPACE/package.json /app/$WORKSPACE/package.json
COPY --parents /packages/*/package.json /app/
# reset app version to 1.0.0 to avoid cache busting after every release
🧰 Tools
🪛 Hadolint (2.14.0)

[error] 26-26: COPY with more than 2 arguments requires the last argument to end with /

(DL3021)

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

In `@packages/docker/Dockerfile.vite` around lines 24 - 27, The COPY command using
a glob (/packages/*/package.json) needs the destination to be a directory, so
update the COPY --parents /packages/*/package.json /app to target a directory
with a trailing slash (i.e., /app/) to avoid Docker build errors; locate the
COPY --parents line in the Dockerfile.vite and add the trailing slash to the
destination path.

Comment on lines +58 to +65
FROM nginx:alpine AS production

COPY --from=builder /app/$WORKSPACE/dist /usr/share/nginx/html
COPY $WORKSPACE/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 3000

CMD ["nginx", "-g", "daemon off;"]
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:

#!/bin/bash
# Check if Dockerfile.vite exists and examine the production stage
if [ -f "packages/docker/Dockerfile.vite" ]; then
  echo "=== File found. Content ===" 
  cat -n "packages/docker/Dockerfile.vite"
else
  echo "File not found at packages/docker/Dockerfile.vite"
  # Try to locate it
  find . -name "Dockerfile.vite" 2>/dev/null
fi

Repository: akash-network/console

Length of output: 2280


🏁 Script executed:

#!/bin/bash
# Run the verification script from the comment
python - <<'PY'
from pathlib import Path
lines = Path("packages/docker/Dockerfile.vite").read_text().splitlines()
start = None
for i, l in enumerate(lines):
    if "FROM nginx:alpine AS production" in l:
        start = i
        break
if start is None:
    print("Production stage not found")
else:
    user_lines = [(idx+1, l.strip()) for idx, l in enumerate(lines[start+1:], start+1) if l.strip().startswith("USER ")]
    print("USER directives after production stage:", user_lines or "None")
    print("\n=== Full production stage ===")
    for idx, l in enumerate(lines[start:], start+1):
        print(f"{idx}: {l}")
        if idx > start and l.strip().startswith("FROM "):
            break
PY

Repository: akash-network/console

Length of output: 176


🏁 Script executed:

#!/bin/bash
# Verify the file ends at line 65 and production is the final stage
wc -l "packages/docker/Dockerfile.vite"
echo "---"
tail -20 "packages/docker/Dockerfile.vite" | cat -n

Repository: akash-network/console

Length of output: 701


🌐 Web query:

nginx:alpine default user non-root nginx security

💡 Result:

  • nginx:alpine default user: the container starts as root (so it can do root-only tasks like binding to port 80 / writing to some system paths). The image includes an nginx user/group with UID/GID 101, and worker processes drop privileges to that user. [1]
  • If you want “non-root by default” (recommended for strict security policies): use nginxinc/nginx-unprivileged. It’s explicitly built to run NGINX as an unprivileged user and changes defaults to make that work (listen on 8080, removes the user directive, moves PID and temp paths to /tmp, etc.). [2]
  • If you must stay on nginx:alpine but run non-root: you can run with an arbitrary UID/GID, but you must adjust config paths (PID and temp dirs) to writable locations like /tmp (otherwise you’ll hit permission errors). [1]

Sources: [1] Docker Official Image “nginx” docs (User/group id + running as non-root) (hub.docker.com), [2] nginxinc/nginx-unprivileged Docker Hub overview (hub.docker.com)

Citations:


Run nginx as a non‑root user in the production image.

The production stage (lines 58–65) lacks a USER directive, causing the container to run as root. The standard nginx:alpine image starts as root and includes a bundled nginx user (UID/GID 101) for security. Add a USER directive and adjust file ownership so the nginx process can read the static assets and configuration:

 FROM nginx:alpine AS production
 
 COPY --from=builder /app/$WORKSPACE/dist /usr/share/nginx/html
 COPY $WORKSPACE/nginx.conf /etc/nginx/conf.d/default.conf
+RUN chown -R nginx:nginx /usr/share/nginx/html /etc/nginx/conf.d
+USER nginx
 
 EXPOSE 3000
 
 CMD ["nginx", "-g", "daemon off;"]
📝 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
FROM nginx:alpine AS production
COPY --from=builder /app/$WORKSPACE/dist /usr/share/nginx/html
COPY $WORKSPACE/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 3000
CMD ["nginx", "-g", "daemon off;"]
FROM nginx:alpine AS production
COPY --from=builder /app/$WORKSPACE/dist /usr/share/nginx/html
COPY $WORKSPACE/nginx.conf /etc/nginx/conf.d/default.conf
RUN chown -R nginx:nginx /usr/share/nginx/html /etc/nginx/conf.d
USER nginx
EXPOSE 3000
CMD ["nginx", "-g", "daemon off;"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docker/Dockerfile.vite` around lines 58 - 65, The production stage
is running nginx as root; change it to use the non‑root nginx user and ensure
static files and config are readable by that user: after copying assets and
nginx.conf (the COPY lines referencing /app/$WORKSPACE/dist and
$WORKSPACE/nginx.conf) change ownership of /usr/share/nginx/html and
/etc/nginx/conf.d/default.conf to the nginx user (UID/GID used by the base
image) and add a USER directive (use the nginx user name or UID) before the CMD
so nginx runs as the non‑root user.

…code

- Update .env.local.sample to use VITE_ prefix
- Update deploy.yml env var comments to use VITE_ prefix
- Delete unused serverIntl.ts (Next.js server-only module, not imported)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/stats-web/package.json`:
- Around line 5-18: package.json sets "type": "module" which forces Node to
treat .js config files as ESM; update the ESLint and Prettier config files
referenced (.eslintrc.js and .prettierrc.js) by either renaming them to
.eslintrc.cjs and .prettierrc.cjs (keeping CommonJS module.exports) or
converting them to ESM syntax (export default ...) so they match the project
module type; ensure references to these filenames in any tooling scripts remain
consistent after renaming.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/stats-web/.env.local.sample`:
- Around line 5-7: Reorder the dotenv keys to satisfy the linter by placing
VITE_BASE_API_SANDBOX_URL in its correct alphabetical position relative to
VITE_BASE_API_MAINNET_URL and VITE_BASE_API_TESTNET_URL; update the sequence so
the three lines read in proper order (e.g., VITE_BASE_API_MAINNET_URL,
VITE_BASE_API_SANDBOX_URL, VITE_BASE_API_TESTNET_URL) to resolve the
dotenv-linter key ordering warning.

Comment on lines +5 to +7
VITE_BASE_API_MAINNET_URL=$VITE_API_BASE_URL
VITE_BASE_API_TESTNET_URL=$VITE_API_BASE_URL
VITE_BASE_API_SANDBOX_URL=$VITE_API_BASE_URL
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 | 🟡 Minor

Address dotenv-linter key ordering to keep the file lint-clean.

The linter flags VITE_BASE_API_SANDBOX_URL being placed after VITE_BASE_API_TESTNET_URL. Consider reordering to satisfy the .env ordering rules.

🔧 Suggested reorder
 VITE_BASE_API_MAINNET_URL=$VITE_API_BASE_URL
-VITE_BASE_API_TESTNET_URL=$VITE_API_BASE_URL
 VITE_BASE_API_SANDBOX_URL=$VITE_API_BASE_URL
+VITE_BASE_API_TESTNET_URL=$VITE_API_BASE_URL
📝 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
VITE_BASE_API_MAINNET_URL=$VITE_API_BASE_URL
VITE_BASE_API_TESTNET_URL=$VITE_API_BASE_URL
VITE_BASE_API_SANDBOX_URL=$VITE_API_BASE_URL
VITE_BASE_API_MAINNET_URL=$VITE_API_BASE_URL
VITE_BASE_API_SANDBOX_URL=$VITE_API_BASE_URL
VITE_BASE_API_TESTNET_URL=$VITE_API_BASE_URL
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 7-7: [UnorderedKey] The VITE_BASE_API_SANDBOX_URL key should go before the VITE_BASE_API_TESTNET_URL key

(UnorderedKey)

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

In `@apps/stats-web/.env.local.sample` around lines 5 - 7, Reorder the dotenv keys
to satisfy the linter by placing VITE_BASE_API_SANDBOX_URL in its correct
alphabetical position relative to VITE_BASE_API_MAINNET_URL and
VITE_BASE_API_TESTNET_URL; update the sequence so the three lines read in proper
order (e.g., VITE_BASE_API_MAINNET_URL, VITE_BASE_API_SANDBOX_URL,
VITE_BASE_API_TESTNET_URL) to resolve the dotenv-linter key ordering warning.

- Add rel="noopener noreferrer" to target="_blank" links (TransactionRow, EventRow, columns)
- Use anchor tags instead of Link for external new-tab navigation
- Use useRef instead of module-level variable in GoogleAnalytics
- Fix Discord link aria-label from "Twitter" to "Discord" in Nav
- Fix "Poposer" typo to "Proposer" in BlockInfo
- Fix vite.config define key to match import.meta.env usage
- Simplify StatsCard diffNumber condition to only check diffPercent
- Remove dead conditional in FlagProvider
- Replace lazy-loaded react-intl imports with static imports in TransactionInfo
- Remove commented-out dead code in MsgDelegate
- Remove unused sentryTraceToW3C and getTraceData from error-handler service
- Remove unused path import from .eslintrc.cjs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
apps/stats-web/src/services/error-handler/error-handler.service.ts (1)

26-41: ⚠️ Potential issue | 🟠 Major

Remove any from wrapCallback signature to comply with TypeScript guidelines.

Line 26 uses any[] and any in the generic constraint, violating the repo rule to never use any types. Replace with unknown and use Parameters<T> for proper type safety.

Proposed fix
-  wrapCallback<T extends (...args: any[]) => any>(fn: T, options?: WrapCallbackOptions<ReturnType<T>>): T {
-    return ((...args) => {
+  wrapCallback<T extends (...args: unknown[]) => unknown>(
+    fn: T,
+    options?: WrapCallbackOptions<ReturnType<T>>
+  ): T {
+    return ((...args: Parameters<T>) => {
       try {
         const result = fn(...args);
         if (result && typeof result.catch === "function") {
           return result.catch((error: unknown) => {
             this.reportError({ error, tags: options?.tags });
             if (options?.fallbackValue) return options.fallbackValue();
           });
         }
         return result;
       } catch (error) {
         this.reportError({ error, tags: options?.tags });
         if (options?.fallbackValue) return options.fallbackValue();
       }
     }) as T;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/stats-web/src/services/error-handler/error-handler.service.ts` around
lines 26 - 41, The wrapCallback signature uses `any` types; change the generic
constraint to `T extends (...args: unknown[]) => unknown`, type the inner args
as `...args: Parameters<T>` and the result as `const result = fn(...args) as
ReturnType<T>`, and keep error typed as `unknown`; this removes `any` while
preserving use of `Parameters<T>` and `ReturnType<T>` (leave existing reference
to WrapCallbackOptions<ReturnType<T>>), and ensure fallbackValue handling and
promise `.catch` logic remain the same in the function body.
apps/stats-web/src/pages/home/StatsCard.tsx (1)

8-13: ⚠️ Potential issue | 🟠 Major

Tighten IStatsCardProps carefully — the component no longer consumes diffNumber/diffNumberUnit, but multiple call sites still pass them.

StatsCard doesn't destructure or render diffNumber/diffNumberUnit (destructure on line 19 omits them, only diffPercent is used on line 34), leaving orphaned props in the interface. However, Dashboard.tsx has 20+ active call sites still passing these props (lines 83, 102, 118, 129, 152, 167, 182, 192–193, 203–204, 227, 254, 270, 281–282, 295–296, 327, 348, 369, 390). Removing them from the interface requires updating all call sites in Dashboard.tsx or refactoring the component to consume them. Clarify the intended direction before proceeding with cleanup.

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

In `@apps/stats-web/src/pages/home/StatsCard.tsx` around lines 8 - 13,
IStatsCardProps currently declares diffNumber and diffNumberUnit but StatsCard
only uses diffPercent (StatsCard component’s props destructuring omits
diffNumber/diffNumberUnit), so either restore usage or remove & update call
sites: Option A — add diffNumber and diffNumberUnit back into StatsCard’s props
destructure and render them where the diff UI is produced (inside StatsCard near
where diffPercent is used) so existing Dashboard.tsx call sites keep working;
Option B — remove diffNumber and diffNumberUnit from IStatsCardProps and then
update every Dashboard.tsx call site that passes those props (the ~20 locations
referenced) to stop passing them (or compute any needed display upstream),
ensuring types and usages are consistent. Reference symbols: IStatsCardProps,
StatsCard, Dashboard.tsx.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/stats-web/src/components/layout/Nav.tsx`:
- Around line 49-83: All anchor tags that open in a new tab (those using
target="_blank" in Nav.tsx — e.g., the anchors wrapping TwitterX, Github,
Discord icons and the akash.network and console.akash.network buttons) must
include rel="noopener noreferrer" to prevent reverse-tabnabbing; update each <a>
that currently has rel="noreferrer" to rel="noopener noreferrer" (or add rel if
missing) so every external new-tab link uses both noopener and noreferrer.

In `@apps/stats-web/src/pages/home/StatsCard.tsx`:
- Around line 34-37: The conditional currently hides legitimate zero values by
using a truthy check on diffPercent; change the render guard for the
DiffPercentageChip to explicitly check for null/undefined (e.g., diffPercent !=
null) instead of using !!diffPercent so that 0 is rendered; update the JSX
around DiffPercentageChip/DiffPercentageChip value={diffPercent} to use this
null/undefined check.

---

Outside diff comments:
In `@apps/stats-web/src/pages/home/StatsCard.tsx`:
- Around line 8-13: IStatsCardProps currently declares diffNumber and
diffNumberUnit but StatsCard only uses diffPercent (StatsCard component’s props
destructuring omits diffNumber/diffNumberUnit), so either restore usage or
remove & update call sites: Option A — add diffNumber and diffNumberUnit back
into StatsCard’s props destructure and render them where the diff UI is produced
(inside StatsCard near where diffPercent is used) so existing Dashboard.tsx call
sites keep working; Option B — remove diffNumber and diffNumberUnit from
IStatsCardProps and then update every Dashboard.tsx call site that passes those
props (the ~20 locations referenced) to stop passing them (or compute any needed
display upstream), ensuring types and usages are consistent. Reference symbols:
IStatsCardProps, StatsCard, Dashboard.tsx.

In `@apps/stats-web/src/services/error-handler/error-handler.service.ts`:
- Around line 26-41: The wrapCallback signature uses `any` types; change the
generic constraint to `T extends (...args: unknown[]) => unknown`, type the
inner args as `...args: Parameters<T>` and the result as `const result =
fn(...args) as ReturnType<T>`, and keep error typed as `unknown`; this removes
`any` while preserving use of `Parameters<T>` and `ReturnType<T>` (leave
existing reference to WrapCallbackOptions<ReturnType<T>>), and ensure
fallbackValue handling and promise `.catch` logic remain the same in the
function body.

- Rename .prettierrc.js to .prettierrc.cjs for ESM compatibility
- Remove outdated .eslintrc.json (Next.js config, using .eslintrc.cjs)
- Replace useVariant with stub in useTopBanner (no UnleashProvider in simplified FlagProvider)
- Fix exhaustive-deps warnings in useTopBanner useEffect hooks
- Remove default export from PageContainer, use named exports everywhere
- Reorder env file keys alphabetically (VITE_API_BASE_URL before VITE_NODE_ENV)
- Remove quoted booleans in .env.production and .env.staging
@baktun14 baktun14 force-pushed the claude/slack-migrate-stats-vite-242OX branch from b4c23fb to 8222f15 Compare February 20, 2026 18:25
@baktun14 baktun14 marked this pull request as draft February 23, 2026 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants