Migrate stats-web from Next.js to Vite with React Router#2801
Migrate stats-web from Next.js to Vite with React Router#2801
Conversation
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
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReplaces Next.js app-router with a Vite-powered React SPA: adds Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
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.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 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
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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: RemovesentryTraceToW3Cto eliminate dead code in stats-web.The function is exported but never used within this app. Since
getTraceData()always returnsundefinedfortraceId, this helper function has no purpose here. Remove it to keep the codebase clean. (Note: This function is actively used inapps/deploy-weband 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 loadingreact-intlcomponents may not provide meaningful benefit.
FormattedRelativeTimeandFormattedDateare lightweight components fromreact-intl. Lazy loading them adds:
- Potential visual flicker ("Loading..." fallback)
- Extra network requests for small chunks
- Complexity in the codebase
If
react-intlis 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
Suspensewrapper 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} /> ( <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:diffNumberis checked but not rendered.The condition
!!diffNumber || !!diffPercentstill checksdiffNumber, but onlydiffPercentis used for renderingDiffPercentageChip. IfdiffNumberdisplay 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/reactshould 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 fortarget="_blank"links.While React Router's
Linkwithtarget="_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 fortarget="_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
DeploymentDetailtype is imported but never used in this file. Thedeploymentvariable is already typed by theuseDeploymenthook.🧹 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.
| "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" | ||
| }, |
There was a problem hiding this comment.
🧩 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.
There was a problem hiding this comment.
@claude why remove the build-env-schemas scripts?
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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.
| 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.
apps/stats-web/src/services/error-handler/error-handler.service.ts
Outdated
Show resolved
Hide resolved
| "include": ["src"], | ||
| "references": [{ "path": "./tsconfig.node.json" }] |
There was a problem hiding this comment.
🧩 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/apiRepository: 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.jsonRepository: 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.
| "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
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (6)
apps/stats-web/src/pages/transactions/[hash]/page.tsx (1)
14-16: Consider defensive handling forhashparameter.
useParams<{ hash: string }>()typeshashasstring, but React Router'suseParamsactually returnsstring | undefinedfor each param. The current code handles this withhash || "", which works becauseuseTransactionhasenabled: !!hashthat 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 afterlazy,-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: GuardnewValuetoAddressTabto 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.
| VITE_API_BASE_URL= | ||
| VITE_BASE_API_MAINNET_URL= | ||
| VITE_BASE_API_TESTNET_URL= | ||
| VITE_BASE_API_SANDBOX_URL= |
There was a problem hiding this comment.
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.
| 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.
apps/stats-web/src/pages/addresses/[address]/deployments/[dseq]/EventRow.tsx
Outdated
Show resolved
Hide resolved
| 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} /> |
There was a problem hiding this comment.
🧩 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
doneRepository: 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 -nRepository: 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 5Repository: 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 -50Repository: 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.
| 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} /> |
There was a problem hiding this comment.
🧩 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 5Repository: 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
doneRepository: 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 -20Repository: 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.tsxRepository: 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.tsRepository: 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 -100Repository: 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 3Repository: 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 5Repository: 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.tsxRepository: 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 -30Repository: 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.tsxRepository: 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 -30Repository: 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 10Repository: 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 -100Repository: 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:
- Typing
useParamsas{ snapshot?: string } - Adding an explicit guard to check the snapshot before rendering
- 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.
| 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 |
There was a problem hiding this comment.
🧩 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}")
PYRepository: 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>orCOPY ["<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 /abscreates a file literally at/abs(namedabs).
COPY test.txt /abs/creates/abs/test.txt. [1] -
Trailing slashes on source paths are ignored (e.g.,
COPY something/ /somethingis equivalent toCOPY 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.
| 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.
| 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;"] |
There was a problem hiding this comment.
🧩 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
fiRepository: 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
PYRepository: 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 -nRepository: akash-network/console
Length of output: 701
🌐 Web query:
nginx:alpine default user non-root nginx security
💡 Result:
nginx:alpinedefault 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 annginxuser/group with UID/GID101, 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 theuserdirective, moves PID and temp paths to/tmp, etc.). [2] - If you must stay on
nginx:alpinebut 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:
- 1: https://hub.docker.com/_/nginx?utm_source=openai
- 2: https://hub.docker.com/r/nginxinc/nginx-unprivileged/?utm_source=openai
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.
| 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)
There was a problem hiding this comment.
🤖 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.
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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
There was a problem hiding this comment.
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 | 🟠 MajorRemove
anyfromwrapCallbacksignature to comply with TypeScript guidelines.Line 26 uses
any[]andanyin the generic constraint, violating the repo rule to never useanytypes. Replace withunknownand useParameters<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 | 🟠 MajorTighten
IStatsCardPropscarefully — the component no longer consumesdiffNumber/diffNumberUnit, but multiple call sites still pass them.
StatsCarddoesn't destructure or renderdiffNumber/diffNumberUnit(destructure on line 19 omits them, onlydiffPercentis 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
b4c23fb to
8222f15
Compare
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
Key Changes
src/app/directory structure tosrc/pages/with React Router configuration insrc/main.tsxsrc/main.tsxas the application entry point with Vite configurationuseDetailQueries.ts(useBlock, useTransaction, useAddress, useValidator, useDeployment)react-helmet-asyncfor dynamic head managementnext/linkimports toreact-router-domLink componentnextjs-google-analyticstoreact-ga4Build Configuration
vite.config.tswith React plugin and path alias configurationtsconfig.jsonfor Vite/ESM compatibilitytsconfig.node.jsonfor Vite config type checkingindex.htmlas Vite entry pointpackage.jsonscripts:buildnow runsvite buildinstead ofnext buildnext.config.js,middleware.ts,instrumentation.tsComponent Updates
lazyandSuspensefrom ReactHomePage,BlockDetailPage)Removed Dependencies
https://claude.ai/code/session_01LghCUqfxhqgCNahb3zN9DW
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Chores