diff --git a/apps/stats-web/env/.env.production b/apps/stats-web/env/.env.production index dbd0c6496..dd649e84e 100644 --- a/apps/stats-web/env/.env.production +++ b/apps/stats-web/env/.env.production @@ -1 +1,9 @@ -NEXT_PUBLIC_API_BASE_URL=https://console-api.akash.network \ No newline at end of file +NEXT_PUBLIC_NODE_ENV=$NODE_ENV +NEXT_PUBLIC_BASE_API_MAINNET_URL=https://console-api.akash.network +NEXT_PUBLIC_BASE_API_SANDBOX_URL=https://console-api-sandbox.akash.network +NEXT_PUBLIC_BASE_API_TESTNET_URL=https://console-api-testnet.akash.network +NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL + +BASE_API_MAINNET_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL +BASE_API_TESTNET_URL=$NEXT_PUBLIC_BASE_API_TESTNET_URL +BASE_API_SANDBOX_URL=$NEXT_PUBLIC_BASE_API_SANDBOX_URL \ No newline at end of file diff --git a/apps/stats-web/env/.env.staging b/apps/stats-web/env/.env.staging index 968b942c0..3778aa132 100644 --- a/apps/stats-web/env/.env.staging +++ b/apps/stats-web/env/.env.staging @@ -1 +1,9 @@ -NEXT_PUBLIC_API_BASE_URL=https://console-api-mainnet-staging.akash.network \ No newline at end of file +NEXT_PUBLIC_NODE_ENV=$NODE_ENV +NEXT_PUBLIC_BASE_API_MAINNET_URL=https://console-api-mainnet-staging.akash.network +NEXT_PUBLIC_BASE_API_SANDBOX_URL=https://console-api-sandbox-staging.akash.network +NEXT_PUBLIC_BASE_API_TESTNET_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL +NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL + +BASE_API_MAINNET_URL=$NEXT_PUBLIC_BASE_API_MAINNET_URL +BASE_API_TESTNET_URL=$NEXT_PUBLIC_BASE_API_TESTNET_URL +BASE_API_SANDBOX_URL=$NEXT_PUBLIC_BASE_API_SANDBOX_URL \ No newline at end of file diff --git a/apps/stats-web/package.json b/apps/stats-web/package.json index 7134d2993..c9a54267f 100644 --- a/apps/stats-web/package.json +++ b/apps/stats-web/package.json @@ -11,8 +11,8 @@ "start": "next start" }, "dependencies": { - "@akashnetwork/ui": "*", "@akashnetwork/network-store": "*", + "@akashnetwork/ui": "*", "@cosmjs/encoding": "^0.32.4", "@json2csv/plainjs": "^7.0.4", "@nivo/line": "^0.87.0", diff --git a/apps/stats-web/src/app/addresses/[address]/deployments/[dseq]/page.tsx b/apps/stats-web/src/app/addresses/[address]/deployments/[dseq]/page.tsx index 71f6cf4bf..0dac31a87 100644 --- a/apps/stats-web/src/app/addresses/[address]/deployments/[dseq]/page.tsx +++ b/apps/stats-web/src/app/addresses/[address]/deployments/[dseq]/page.tsx @@ -1,19 +1,28 @@ +import type { Network } from "@akashnetwork/network-store"; import { Metadata } from "next"; +import { z } from "zod"; import { DeploymentInfo } from "./DeploymentInfo"; import PageContainer from "@/components/PageContainer"; import { Title } from "@/components/Title"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { networkId } from "@/config/env-config.schema"; import { UrlService } from "@/lib/urlUtils"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { DeploymentDetail } from "@/types"; -interface IProps { - params: { address: string; dseq: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const DeploymentDetailPageSchema = z.object({ + params: z.object({ + address: z.string(), + dseq: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type DeploymentDetailPageProps = z.infer; -export async function generateMetadata({ params: { address, dseq } }: IProps): Promise { +export async function generateMetadata({ params: { address, dseq } }: DeploymentDetailPageProps): Promise { const url = `https://stats.akash.network${UrlService.deployment(address, dseq)}`; return { @@ -27,8 +36,8 @@ export async function generateMetadata({ params: { address, dseq } }: IProps): P }; } -async function fetchDeploymentData(address: string, dseq: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchDeploymentData(address: string, dseq: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/deployment/${address}/${dseq}`); if (!response.ok) { @@ -39,8 +48,12 @@ async function fetchDeploymentData(address: string, dseq: string, network: strin return response.json(); } -export default async function DeploymentDetailPage({ params: { address, dseq }, searchParams: { network } }: IProps) { - const deployment = await fetchDeploymentData(address, dseq, network as string); +export default async function DeploymentDetailPage(props: DeploymentDetailPageProps) { + const { + params: { address, dseq }, + searchParams: { network } + } = DeploymentDetailPageSchema.parse(props); + const deployment = await fetchDeploymentData(address, dseq, network); return ( diff --git a/apps/stats-web/src/app/addresses/[address]/page.tsx b/apps/stats-web/src/app/addresses/[address]/page.tsx index ba0464822..2f26909cb 100644 --- a/apps/stats-web/src/app/addresses/[address]/page.tsx +++ b/apps/stats-web/src/app/addresses/[address]/page.tsx @@ -1,4 +1,6 @@ -import { Metadata } from "next"; +import type { Network } from "@akashnetwork/network-store"; +import type { Metadata } from "next"; +import { z } from "zod"; import { AddressInfo } from "./AddressInfo"; import AddressLayout from "./AddressLayout"; @@ -7,16 +9,23 @@ import { AssetList } from "./AssetList"; import { LatestTransactions } from "./LatestTransactions"; import { Title } from "@/components/Title"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { networkId } from "@/config/env-config.schema"; import { UrlService } from "@/lib/urlUtils"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { AddressDetail } from "@/types"; -interface IProps { - params: { address: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const AddressDetailPageSchema = z.object({ + params: z.object({ + address: z.string(), + dseq: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type AddressDetailPageProps = z.infer; -export async function generateMetadata({ params: { address } }: IProps): Promise { +export async function generateMetadata({ params: { address } }: AddressDetailPageProps): Promise { const url = `https://stats.akash.network${UrlService.address(address)}`; return { @@ -30,8 +39,8 @@ export async function generateMetadata({ params: { address } }: IProps): Promise }; } -async function fetchAddressData(address: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchAddressData(address: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/addresses/${address}`); if (!response.ok) { @@ -42,8 +51,12 @@ async function fetchAddressData(address: string, network: string): Promise diff --git a/apps/stats-web/src/app/blocks/[height]/page.tsx b/apps/stats-web/src/app/blocks/[height]/page.tsx index 7f36b23d9..16955584d 100644 --- a/apps/stats-web/src/app/blocks/[height]/page.tsx +++ b/apps/stats-web/src/app/blocks/[height]/page.tsx @@ -1,28 +1,36 @@ +import type { Network } from "@akashnetwork/network-store"; import { Card, CardContent, Table, TableBody, TableHead, TableHeader, TableRow } from "@akashnetwork/ui/components"; import { SearchX } from "lucide-react"; -import { Metadata } from "next"; +import type { Metadata } from "next"; +import { z } from "zod"; import { BlockInfo } from "./BlockInfo"; import { TransactionRow } from "@/components/blockchain/TransactionRow"; import PageContainer from "@/components/PageContainer"; import { Title } from "@/components/Title"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { networkId } from "@/config/env-config.schema"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { BlockDetail } from "@/types"; -interface IProps { - params: { height: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const BlockDetailPageSchema = z.object({ + params: z.object({ + height: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type BlockDetailPageProps = z.infer; -export async function generateMetadata({ params: { height } }: IProps): Promise { +export async function generateMetadata({ params: { height } }: BlockDetailPageProps): Promise { return { title: `Block #${height}` }; } -async function fetchBlockData(height: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchBlockData(height: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/blocks/${height}`); if (!response.ok) { @@ -33,8 +41,12 @@ async function fetchBlockData(height: string, network: string): Promise diff --git a/apps/stats-web/src/app/transactions/[hash]/page.tsx b/apps/stats-web/src/app/transactions/[hash]/page.tsx index f63fd61ec..76bc605b6 100644 --- a/apps/stats-web/src/app/transactions/[hash]/page.tsx +++ b/apps/stats-web/src/app/transactions/[hash]/page.tsx @@ -1,35 +1,44 @@ import React from "react"; +import type { Network } from "@akashnetwork/network-store"; import { Alert, Card, CardContent } from "@akashnetwork/ui/components"; -import { Metadata } from "next"; +import type { Metadata } from "next"; +import { z } from "zod"; import { TransactionInfo } from "./TransactionInfo"; import PageContainer from "@/components/PageContainer"; import { Title } from "@/components/Title"; import { TxMessageRow } from "@/components/transactions/TxMessageRow"; +import { networkId } from "@/config/env-config.schema"; import { getSplitText } from "@/hooks/useShortText"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { TransactionDetail } from "@/types"; -interface IProps { - params: { hash: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const TransactionDetailPageSchema = z.object({ + params: z.object({ + hash: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type TransactionDetailPageProps = z.infer; -export async function generateMetadata({ params: { hash } }: IProps): Promise { - const splittedTxHash = getSplitText(hash, 6, 6); +export async function generateMetadata({ params: { hash } }: TransactionDetailPageProps): Promise { + const splitTxHash = getSplitText(hash, 6, 6); return { - title: `Tx ${splittedTxHash}` + title: `Tx ${splitTxHash}` }; } -async function fetchTransactionData(hash: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchTransactionData(hash: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); + console.log("DEBUG apiUrl", apiUrl); const response = await fetch(`${apiUrl}/v1/transactions/${hash}`); if (!response.ok && response.status !== 404) { // This will activate the closest `error.js` Error Boundary - throw new Error("Error fetching transction data"); + throw new Error("Error fetching transaction data"); } else if (response.status === 404) { return null; } @@ -37,8 +46,12 @@ async function fetchTransactionData(hash: string, network: string): Promise diff --git a/apps/stats-web/src/app/validators/[address]/page.tsx b/apps/stats-web/src/app/validators/[address]/page.tsx index 079793141..4a0f68b38 100644 --- a/apps/stats-web/src/app/validators/[address]/page.tsx +++ b/apps/stats-web/src/app/validators/[address]/page.tsx @@ -1,21 +1,29 @@ -import { Metadata } from "next"; +import type { Network } from "@akashnetwork/network-store"; +import type { Metadata } from "next"; +import { z } from "zod"; import { ValidatorsInfo } from "./ValidatorInfo"; import PageContainer from "@/components/PageContainer"; import { Title } from "@/components/Title"; -import { getNetworkBaseApiUrl } from "@/lib/constants"; +import { networkId } from "@/config/env-config.schema"; import { UrlService } from "@/lib/urlUtils"; +import { serverApiUrlService } from "@/services/api-url/server-api-url.service"; import { ValidatorDetail } from "@/types"; -interface IProps { - params: { address: string }; - searchParams: { [key: string]: string | string[] | undefined }; -} +const ValidatorDetailPageSchema = z.object({ + params: z.object({ + address: z.string() + }), + searchParams: z.object({ + network: networkId + }) +}); +type ValidatorDetailPageProps = z.infer; -export async function generateMetadata({ params: { address }, searchParams: { network } }: IProps): Promise { +export async function generateMetadata({ params: { address }, searchParams: { network } }: ValidatorDetailPageProps): Promise { const url = `https://stats.akash.network${UrlService.validator(address)}`; - const apiUrl = getNetworkBaseApiUrl(network as string); + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/validators/${address}`); const data = (await response.json()) as ValidatorDetail; @@ -30,8 +38,8 @@ export async function generateMetadata({ params: { address }, searchParams: { ne }; } -async function fetchValidatorData(address: string, network: string): Promise { - const apiUrl = getNetworkBaseApiUrl(network); +async function fetchValidatorData(address: string, network: Network["id"]): Promise { + const apiUrl = serverApiUrlService.getBaseApiUrlFor(network); const response = await fetch(`${apiUrl}/v1/validators/${address}`); if (!response.ok) { @@ -42,8 +50,12 @@ async function fetchValidatorData(address: string, network: string): Promise diff --git a/apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx b/apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx index 18299f77f..e9c65409c 100644 --- a/apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx +++ b/apps/stats-web/src/components/layout/CustomGoogleAnalytics.tsx @@ -3,7 +3,7 @@ import { useReportWebVitals } from "next/web-vitals"; import { event, GoogleAnalytics as GAnalytics } from "nextjs-google-analytics"; -import { isProd } from "@/lib/constants"; +import { browserEnvConfig } from "@/config/browser-env.config"; export default function GoogleAnalytics() { useReportWebVitals(({ id, name, label, value }) => { @@ -15,5 +15,5 @@ export default function GoogleAnalytics() { }); }); - return <>{isProd && }; + return <>{browserEnvConfig.NEXT_PUBLIC_NODE_ENV === "production" && }; } diff --git a/apps/stats-web/src/config/browser-env.config.ts b/apps/stats-web/src/config/browser-env.config.ts index 785dfe72f..903fec82c 100644 --- a/apps/stats-web/src/config/browser-env.config.ts +++ b/apps/stats-web/src/config/browser-env.config.ts @@ -2,5 +2,9 @@ import { validateStaticEnvVars } from "./env-config.schema"; export const browserEnvConfig = validateStaticEnvVars({ NEXT_PUBLIC_DEFAULT_NETWORK_ID: process.env.NEXT_PUBLIC_DEFAULT_NETWORK_ID, - NEXT_PUBLIC_API_BASE_URL: process.env.NEXT_PUBLIC_API_BASE_URL + NEXT_PUBLIC_API_BASE_URL: process.env.NEXT_PUBLIC_API_BASE_URL, + NEXT_PUBLIC_NODE_ENV: process.env.NEXT_PUBLIC_NODE_ENV, + NEXT_PUBLIC_BASE_API_TESTNET_URL: process.env.NEXT_PUBLIC_BASE_API_TESTNET_URL, + NEXT_PUBLIC_BASE_API_SANDBOX_URL: process.env.NEXT_PUBLIC_BASE_API_SANDBOX_URL, + NEXT_PUBLIC_BASE_API_MAINNET_URL: process.env.NEXT_PUBLIC_BASE_API_MAINNET_URL }); diff --git a/apps/stats-web/src/config/env-config.schema.ts b/apps/stats-web/src/config/env-config.schema.ts index 9575f793e..1cb77e54f 100644 --- a/apps/stats-web/src/config/env-config.schema.ts +++ b/apps/stats-web/src/config/env-config.schema.ts @@ -1,11 +1,15 @@ import { z } from "zod"; -const networkId = z.enum(["mainnet", "sandbox", "testnet"]); +export const networkId = z.enum(["mainnet", "sandbox", "testnet"]); const coercedBoolean = () => z.enum(["true", "false"]).transform(val => val === "true"); export const browserEnvSchema = z.object({ NEXT_PUBLIC_DEFAULT_NETWORK_ID: networkId.optional().default("mainnet"), - NEXT_PUBLIC_API_BASE_URL: z.string().url() + NEXT_PUBLIC_API_BASE_URL: z.string().url(), + NEXT_PUBLIC_NODE_ENV: z.enum(["development", "production", "test"]).optional().default("development"), + NEXT_PUBLIC_BASE_API_TESTNET_URL: z.string().url(), + NEXT_PUBLIC_BASE_API_SANDBOX_URL: z.string().url(), + NEXT_PUBLIC_BASE_API_MAINNET_URL: z.string().url() }); export const serverEnvSchema = browserEnvSchema.extend({ diff --git a/apps/stats-web/src/lib/apiUtils.ts b/apps/stats-web/src/lib/apiUtils.ts index 87c6a5739..484ee4585 100644 --- a/apps/stats-web/src/lib/apiUtils.ts +++ b/apps/stats-web/src/lib/apiUtils.ts @@ -1,87 +1,57 @@ -import axios from "axios"; - -import { BASE_API_URL } from "./constants"; import { appendSearchParams } from "./urlUtils"; +import { browserApiUrlService } from "@/services/api-url/browser-api-url.service"; +import { networkStore } from "@/store/network.store"; + export class ApiUrlService { static dashboardData() { - return `${BASE_API_URL}/v1/dashboard-data`; + return `${this.baseApiUrl}/v1/dashboard-data`; } static marketData() { - return `${BASE_API_URL}/v1/market-data`; + return `${this.baseApiUrl}/v1/market-data`; } static proposals() { - return `${BASE_API_URL}/v1/proposals`; + return `${this.baseApiUrl}/v1/proposals`; } static validators() { - return `${BASE_API_URL}/v1/validators`; + return `${this.baseApiUrl}/v1/validators`; } static transactions(limit: number) { - return `${BASE_API_URL}/v1/transactions${appendSearchParams({ limit })}`; + return `${this.baseApiUrl}/v1/transactions${appendSearchParams({ limit })}`; } static addressTransactions(address: string, skip: number, limit: number) { - return `${BASE_API_URL}/v1/addresses/${address}/transactions/${skip}/${limit}`; + return `${this.baseApiUrl}/v1/addresses/${address}/transactions/${skip}/${limit}`; } static addressDeployments(address: string, skip: number, limit: number, reverseSorting: boolean, filters: { [key: string]: string }) { - return `${BASE_API_URL}/v1/addresses/${address}/deployments/${skip}/${limit}${appendSearchParams({ reverseSorting, ...filters })}`; + return `${this.baseApiUrl}/v1/addresses/${address}/deployments/${skip}/${limit}${appendSearchParams({ reverseSorting, ...filters })}`; } static graphData(snapshot: string) { - return `${BASE_API_URL}/v1/graph-data/${snapshot}`; + return `${this.baseApiUrl}/v1/graph-data/${snapshot}`; } static providerGraphData(snapshot: string) { - return `${BASE_API_URL}/v1/provider-graph-data/${snapshot}`; + return `${this.baseApiUrl}/v1/provider-graph-data/${snapshot}`; } static blocks(limit: number) { - return `${BASE_API_URL}/v1/blocks${appendSearchParams({ limit })}`; + return `${this.baseApiUrl}/v1/blocks${appendSearchParams({ limit })}`; } static providerAttributesSchema() { - return `${BASE_API_URL}/v1/provider-attributes-schema`; + return `${this.baseApiUrl}/v1/provider-attributes-schema`; } static networkCapacity() { - return `${BASE_API_URL}/v1/network-capacity`; + return `${this.baseApiUrl}/v1/network-capacity`; } static mainnetVersion() { - return `${BASE_API_URL}/v1/version/mainnet`; + return `${this.baseApiUrl}/v1/version/mainnet`; } static testnetVersion() { - return `${BASE_API_URL}/v1/version/testnet`; + return `${this.baseApiUrl}/v1/version/testnet`; } static sandboxVersion() { - return `${BASE_API_URL}/v1/version/sandbox`; + return `${this.baseApiUrl}/v1/version/sandbox`; } -} - -export async function loadWithPagination(baseUrl: string, dataKey: string, limit: number) { - let items: any[] = []; - let nextKey = null; - // let callCount = 1; - // let totalCount = null; - - do { - const _hasQueryParam = hasQueryParam(baseUrl); - let queryUrl = `${baseUrl}${_hasQueryParam ? "&" : "?"}pagination.limit=${limit}&pagination.count_total=true`; - if (nextKey) { - queryUrl += "&pagination.key=" + encodeURIComponent(nextKey); - } - // console.log(`Querying ${dataKey} [${callCount}] from : ${queryUrl}`); - const response = await axios.get(queryUrl); - const data = response.data; - - // if (!nextKey) { - // totalCount = data.pagination.total; - // } - items = items.concat(data[dataKey]); - nextKey = data.pagination.next_key; - // callCount++; - - // console.log(`Got ${items.length} of ${totalCount}`); - } while (nextKey); - - return items.filter(item => item); -} - -function hasQueryParam(url: string) { - return /[?&]/gm.test(url); + static get baseApiUrl() { + return browserApiUrlService.getBaseApiUrlFor(networkStore.selectedNetworkId); + } } diff --git a/apps/stats-web/src/lib/constants.ts b/apps/stats-web/src/lib/constants.ts deleted file mode 100644 index dab4540e2..000000000 --- a/apps/stats-web/src/lib/constants.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { SANDBOX_ID, TESTNET_ID } from "@akashnetwork/network-store"; - -import { networkStore } from "@/store/network.store"; - -const productionMainnetApiUrl = "https://console-api.akash.network"; -const productionTestnetApiUrl = "https://console-api-testnet.akash.network"; -const productionSandboxApiUrl = "https://console-api-sandbox.akash.network"; -const productionHostnames = ["stats.akash.network"]; - -export const isProd = process.env.NODE_ENV === "production"; -export const isMaintenanceMode = process.env.MAINTENANCE_MODE === "true"; -export const BASE_API_MAINNET_URL = getApiMainnetUrl(); -export const BASE_API_TESTNET_URL = getApiTestnetUrl(); -export const BASE_API_SANDBOX_URL = getApiSandboxUrl(); - -export const BASE_API_URL = getApiUrl(); - -export function getNetworkBaseApiUrl(network: string | null) { - switch (network) { - case TESTNET_ID: - return BASE_API_TESTNET_URL; - case SANDBOX_ID: - return BASE_API_SANDBOX_URL; - default: - return BASE_API_MAINNET_URL; - } -} - -function getApiMainnetUrl() { - if (process.env.API_MAINNET_BASE_URL) return process.env.API_MAINNET_BASE_URL; - if (typeof window === "undefined") return "http://localhost:3080"; - if (productionHostnames.includes(window.location?.hostname)) return productionMainnetApiUrl; - return "http://localhost:3080"; -} - -function getApiTestnetUrl() { - if (process.env.API_TESTNET_BASE_URL) return process.env.API_TESTNET_BASE_URL; - if (typeof window === "undefined") return "http://localhost:3080"; - if (productionHostnames.includes(window.location?.hostname)) return productionTestnetApiUrl; - return "http://localhost:3080"; -} - -function getApiSandboxUrl() { - if (process.env.API_SANDBOX_BASE_URL) return process.env.API_SANDBOX_BASE_URL; - if (typeof window === "undefined") return "http://localhost:3080"; - if (productionHostnames.includes(window.location?.hostname)) return productionSandboxApiUrl; - return "http://localhost:3080"; -} - -function getApiUrl() { - if (process.env.API_BASE_URL) return process.env.API_BASE_URL; - if (typeof window === "undefined") return "http://localhost:3080"; - if (productionHostnames.includes(window.location?.hostname)) { - try { - return getNetworkBaseApiUrl(networkStore.selectedNetworkId); - } catch (e) { - console.error(e); - return productionMainnetApiUrl; - } - } - return "http://localhost:3080"; -} diff --git a/apps/stats-web/src/services/api-url/api-url.service.ts b/apps/stats-web/src/services/api-url/api-url.service.ts new file mode 100644 index 000000000..5c13fb9cd --- /dev/null +++ b/apps/stats-web/src/services/api-url/api-url.service.ts @@ -0,0 +1,36 @@ +import type { NetworkId } from "@akashnetwork/akashjs/build/types/network"; +import { SANDBOX_ID, TESTNET_ID } from "@akashnetwork/network-store"; + +import type { BrowserEnvConfig, ServerEnvConfig } from "@/config/env-config.schema"; + +export class ApiUrlService { + constructor( + private readonly config: + | Pick + | Pick + ) {} + + getBaseApiUrlFor(network: NetworkId) { + if ("BASE_API_MAINNET_URL" in this.config) { + switch (network) { + case TESTNET_ID: + return this.config.BASE_API_TESTNET_URL; + case SANDBOX_ID: + return this.config.BASE_API_SANDBOX_URL; + default: + return this.config.BASE_API_MAINNET_URL; + } + } + + if ("NEXT_PUBLIC_BASE_API_MAINNET_URL" in this.config) { + switch (network) { + case TESTNET_ID: + return this.config.NEXT_PUBLIC_BASE_API_TESTNET_URL; + case SANDBOX_ID: + return this.config.NEXT_PUBLIC_BASE_API_SANDBOX_URL; + default: + return this.config.NEXT_PUBLIC_BASE_API_MAINNET_URL; + } + } + } +} diff --git a/apps/stats-web/src/services/api-url/browser-api-url.service.ts b/apps/stats-web/src/services/api-url/browser-api-url.service.ts new file mode 100644 index 000000000..ca1a02faf --- /dev/null +++ b/apps/stats-web/src/services/api-url/browser-api-url.service.ts @@ -0,0 +1,4 @@ +import { browserEnvConfig } from "@/config/browser-env.config"; +import { ApiUrlService } from "@/services/api-url/api-url.service"; + +export const browserApiUrlService = new ApiUrlService(browserEnvConfig); diff --git a/apps/stats-web/src/services/api-url/server-api-url.service.ts b/apps/stats-web/src/services/api-url/server-api-url.service.ts new file mode 100644 index 000000000..5b17432a7 --- /dev/null +++ b/apps/stats-web/src/services/api-url/server-api-url.service.ts @@ -0,0 +1,4 @@ +import { serverEnvConfig } from "@/config/server-env.config"; +import { ApiUrlService } from "@/services/api-url/api-url.service"; + +export const serverApiUrlService = new ApiUrlService(serverEnvConfig); diff --git a/packages/network-store/package.json b/packages/network-store/package.json index 4959ea4b8..b7ea9e61b 100644 --- a/packages/network-store/package.json +++ b/packages/network-store/package.json @@ -11,7 +11,8 @@ "lint": "eslint ." }, "dependencies": { - "axios": "^1.7.2" + "axios": "^1.7.2", + "lodash": "^4.17.21" }, "devDependencies": { "@akashnetwork/akashjs": "^0.10.0" diff --git a/packages/network-store/src/network.store.ts b/packages/network-store/src/network.store.ts index 87e8f91e4..f919c4a90 100644 --- a/packages/network-store/src/network.store.ts +++ b/packages/network-store/src/network.store.ts @@ -2,6 +2,7 @@ import axios from "axios"; import { atom } from "jotai"; import { getDefaultStore, useAtom } from "jotai"; import { atomWithStorage } from "jotai/utils"; +import cloneDeep from "lodash/cloneDeep"; import { INITIAL_NETWORKS_CONFIG } from "./network.config"; import type { Network } from "./network.type"; @@ -31,9 +32,11 @@ export class NetworkStore { return new NetworkStore(options); } - readonly networksStore = atom({ isLoading: true, error: undefined, data: INITIAL_NETWORKS_CONFIG }); + private readonly STORAGE_KEY = "selectedNetworkId"; - private readonly selectedNetworkIdStore = atomWithStorage("selectedNetworkId", this.getInitialNetworkId()); + readonly networksStore = atom({ isLoading: true, error: undefined, data: cloneDeep(INITIAL_NETWORKS_CONFIG) }); + + private readonly selectedNetworkIdStore = atomWithStorage(this.STORAGE_KEY, this.options.defaultNetworkId); private readonly selectedNetworkStore = atom( get => { @@ -71,13 +74,14 @@ export class NetworkStore { constructor(private readonly options: NetworkStoreOptions) { this.store = options.store || getDefaultStore(); + this.initiateNetworkFromUrlQuery(); this.initiateNetworks(); } private async initiateNetworks() { const errors: { network: Network; error: Error }[] = []; const networks = await Promise.all( - INITIAL_NETWORKS_CONFIG.map(async network => { + cloneDeep(INITIAL_NETWORKS_CONFIG).map(async network => { try { network.versionUrl = this.options.apiBaseUrl + network.versionUrl; network.nodesUrl = this.options.apiBaseUrl + network.nodesUrl; @@ -101,24 +105,22 @@ export class NetworkStore { } } - private getInitialNetworkId(): Network["id"] { + private initiateNetworkFromUrlQuery(): Network["id"] { if (typeof window === "undefined") { - return this.options.defaultNetworkId; + return; } const url = new URL(window.location.href); if (!url.searchParams.has("network")) { - return this.options.defaultNetworkId; + return; } const raw = url.searchParams.get("network"); - if (this.networks.some(({ id }) => id === raw)) { - return raw as Network["id"]; + if (INITIAL_NETWORKS_CONFIG.some(({ id }) => id === raw)) { + window.localStorage.setItem(this.STORAGE_KEY, JSON.stringify(raw)); } - - return this.options.defaultNetworkId; } useNetworksStore() {