Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
REACT_APP_DEBUG=
REACT_APP_API_BASE_URL=$API_BASE_URL
REACT_APP_LINK_SSV_WEBAPP=$LINK_SSV_WEBAPP
REACT_APP_GOOGLE_TAG_SECRET=$GOOGLE_TAG_SECRET
REACT_APP_GOOGLE_TAG_URL=https://www.googletagmanager.com/gtm.js?id=
REACT_APP_ANNOUNCEMENT=$ANNOUNCEMENT
REACT_APP_MIXPANEL_TOKEN=$MIXPANEL_TOKEN
33 changes: 5 additions & 28 deletions .github/workflows/lint_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,16 @@ jobs:

- name: Run lint
run: pnpm lint
env:
SKIP_ENV_VALIDATION: true

# <explorer.stage.ssv.network>
- name: Run stage build
if: github.ref == 'refs/heads/stage'
env:
SSV_NETWORKS: "${{ env.STAGE_SSV_NETWORKS }}"
NEXT_PUBLIC_SSV_NETWORKS: "${{ env.STAGE_SSV_NETWORKS }}"
SITE_URL: "https://explorer.stage.ssv.network"

run: pnpm build
# </explorer.stage.ssv.network>
Expand All @@ -78,6 +81,7 @@ jobs:
LINK_SSV_WEBAPP: ${{ secrets.PROD_LINK_SSV_WEBAPP_V4 }}
GOOGLE_TAG_SECRET: ${{ secrets.PROD_GOOGLE_TAG_SECRET_V4 }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN_PROD }}
SITE_URL: "https://explorer.ssv.network"
run: pnpm build

- name: Deploy prod
Expand All @@ -93,31 +97,4 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: "us-west-2"
SOURCE_DIR: "build/"
# </explorer.ssv.network>

# <hoodi.explorer.ssv.network>
- name: Run prod testnet build
if: github.ref == 'refs/heads/main'
env:
NODE_OPTIONS: "--openssl-legacy-provider"
API_BASE_URL: ${{ secrets.PROD_API_BASE_URL_V4_HOODI }}
ANNOUNCEMENT: ${{ secrets.PROD_ANNOUNCEMENT_V4_HOODI }}
LINK_SSV_WEBAPP: ${{ secrets.PROD_LINK_SSV_WEBAPP_V4 }}
GOOGLE_TAG_SECRET: ${{ secrets.PROD_GOOGLE_TAG_SECRET_V4_TESTNET }}
MIXPANEL_TOKEN: ${{ secrets.MIXPANEL_TOKEN_PROD }}
run: pnpm build

- name: Deploy prod testnet
if: github.ref == 'refs/heads/main'
uses: jakejarvis/s3-sync-action@v0.5.0
with:
args: --acl public-read --follow-symlinks --delete
env:
NODE_OPTIONS: "--openssl-legacy-provider"
AWS_S3_BUCKET: ${{ secrets.PROD_AWS_S3_BUCKET_V4_HOODI }}
ANNOUNCEMENT: ${{ secrets.PROD_ANNOUNCEMENT_V4_HOODI }}
AWS_ACCESS_KEY_ID: ${{ secrets.PROD_AWS_SECRET_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: "us-west-2"
SOURCE_DIR: "build/"
# </hoodi.explorer.ssv.network>
# </explorer.ssv.network>
2 changes: 1 addition & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
Binary file modified public/favicon.ico
Binary file not shown.
Binary file added public/og.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions public/site.webmanifest
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Shadcn Table",
"short_name": "Shadcn Table",
"name": "SSV Network Explorer",
"short_name": "SSV Network Explorer",
"icons": [
{
"src": "/icon.png",
Expand Down
6 changes: 3 additions & 3 deletions src/api/clusters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ export const searchClusters = async <
filtered as unknown as Record<string, string>
)

return await api.get<PaginatedClustersResponse<T>>(
endpoint(params.network, "clusters", `?${searchParams}`)
)
const e = endpoint(params.network, "clusters", `?${searchParams}`)
console.log("e:", e)
return await api.get<PaginatedClustersResponse<T>>(e)
},
[JSON.stringify(stringifyBigints(params))],
{
Expand Down
25 changes: 17 additions & 8 deletions src/app/_components/clusters/clusters-table-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import { formatSSV } from "@/lib/utils/number"
import { remove0x, shortenAddress } from "@/lib/utils/strings"
import { CopyBtn } from "@/components/ui/copy-btn"
import { Text } from "@/components/ui/text"
import { Tooltip } from "@/components/ui/tooltip"
import { ClusterStatusBadge } from "@/components/clusters/cluster-status-badge"
import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header"
import { OperatorAvatar } from "@/components/operators/operator-avatar"
import { OperatorInfo } from "@/components/tooltip/operator-info"

export const clustersTableColumns = [
{
Expand Down Expand Up @@ -59,13 +62,19 @@ export const clustersTableColumns = [
<div className="flex gap-1">
{row.original.operators.map((operator) => {
return (
<Link href={`/operator/${operator.id}`} key={operator.id}>
<OperatorAvatar
size="base"
src={operator.logo}
isPrivate={operator.is_private}
/>
</Link>
<Tooltip
asChild
key={operator.id}
className="w-[240px] p-4"
content={<OperatorInfo operator={operator} />}
>
<Link href={`/operator/${operator.id}`} key={operator.id}>
<OperatorAvatar
src={operator.logo}
isPrivate={operator.is_private}
/>
</Link>
</Tooltip>
)
})}
</div>
Expand All @@ -91,7 +100,7 @@ export const clustersTableColumns = [
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Active" />
),
cell: ({ row }) => <div>{row.original.active ? "Yes" : "No"}</div>,
cell: ({ row }) => <ClusterStatusBadge active={row.original.active} />,
},
] satisfies ColumnDef<Cluster>[]

Expand Down
47 changes: 27 additions & 20 deletions src/app/_components/clusters/filters/cluster-id-filter.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
"use client"

import { useState } from "react"
import { FC, useState } from "react"
import { searchClusters } from "@/api/clusters"
import { useQuery } from "@tanstack/react-query"
import { CommandLoading } from "cmdk"
import { xor } from "lodash-es"
import { Loader2, X } from "lucide-react"
import { useQueryState } from "nuqs"

import { clustersSearchFilters } from "@/lib/search-parsers/clusters-search-parsers"
import { cn } from "@/lib/utils"
import { shortenAddress } from "@/lib/utils/strings"
import { useClustersSearchParams } from "@/hooks/search/use-clusters-search-params"
import { useNetworkQuery } from "@/hooks/search/use-network-query"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
Expand All @@ -22,10 +25,22 @@ import {
import { Text } from "@/components/ui/text"
import { FilterButton } from "@/components/filter/filter-button"

export function ClusterIdFilter() {
type ClusterIdFilterProps = {
searchQueryKey?: string
}

export const ClusterIdFilter: FC<ClusterIdFilterProps> = ({
searchQueryKey = "cluster",
}) => {
const [open, setOpen] = useState(false)
const [search, setSearch] = useState<string>("")
const { network, filters, setFilters } = useClustersSearchParams()

const network = useNetworkQuery().query.value
const [clusterIds, setClusterIds] = useQueryState(
searchQueryKey,
clustersSearchFilters.clusterId
)

const query = useQuery({
queryKey: ["clusters", "ids", search, network],
queryFn: async () => {
Expand All @@ -46,8 +61,8 @@ export function ClusterIdFilter() {
return (
<FilterButton
name="Cluster ID"
activeFiltersCount={filters.clusterId?.length ?? 0}
onClear={() => setFilters((prev) => ({ ...prev, clusterId: [] }))}
activeFiltersCount={clusterIds?.length ?? 0}
onClear={() => setClusterIds(null)}
popover={{
root: {
open,
Expand All @@ -66,20 +81,15 @@ export function ClusterIdFilter() {
onValueChange={(value) => setSearch(value)}
/>
</div>
{Boolean(filters.clusterId?.length) && (
{Boolean(clusterIds?.length) && (
<div className="flex flex-wrap gap-1 border-y border-gray-200 p-2">
{filters.clusterId?.map((id) => (
{clusterIds?.map((id) => (
<Button
size="sm"
key={id}
className="h-6 gap-0.5 rounded-full pb-px pl-2 pr-1"
variant="secondary"
onClick={() =>
setFilters((prev) => ({
...prev,
clusterId: xor(prev.clusterId, [id]),
}))
}
onClick={() => setClusterIds(xor(clusterIds, [id]))}
>
<Text variant="caption-medium">{shortenAddress(id)}</Text>{" "}
<div className="flex size-4 items-center justify-center">
Expand All @@ -102,16 +112,13 @@ export function ClusterIdFilter() {
key={cluster.clusterId}
value={cluster.clusterId}
className="flex h-10 items-center space-x-2 px-2"
onSelect={() => {
setFilters((prev) => ({
...prev,
clusterId: xor(prev.clusterId, [cluster.clusterId]),
}))
}}
onSelect={() =>
setClusterIds(xor(clusterIds, [cluster.clusterId]))
}
>
<Checkbox
id={cluster.clusterId}
checked={filters.clusterId?.includes(cluster.clusterId)}
checked={clusterIds?.includes(cluster.clusterId)}
className="mr-2"
/>
<span
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const ClusterTableFilters = () => {
)}
aria-hidden={!isFiltersOpen}
>
<ClusterIdFilter />
<ClusterIdFilter />
<OwnerAddressFilter />
<StatusFilter />
<IsLiquidatedFilter />
Expand Down
44 changes: 25 additions & 19 deletions src/app/_components/clusters/filters/operators-filter.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"use client"

import { useState } from "react"
import { FC, useState } from "react"
import { searchOperators } from "@/api/operator"
import { useQuery } from "@tanstack/react-query"
import { CommandLoading } from "cmdk"
import { xor } from "lodash-es"
import { Loader2, X } from "lucide-react"
import { useQueryState } from "nuqs"

import { useClustersSearchParams } from "@/hooks/search/use-clusters-search-params"
import { clustersSearchFilters } from "@/lib/search-parsers/clusters-search-parsers"
import { useNetworkQuery } from "@/hooks/search/use-network-query"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
Expand All @@ -21,10 +23,22 @@ import { Text } from "@/components/ui/text"
import { FilterButton } from "@/components/filter/filter-button"
import { OperatorInfo } from "@/components/operators/operator-info"

export function OperatorsFilter() {
type OperatorsFilterProps = {
searchQueryKey?: string
}

export const OperatorsFilter: FC<OperatorsFilterProps> = ({
searchQueryKey = "operators",
}) => {
const [open, setOpen] = useState(false)
const [search, setSearch] = useState<string>("")
const { network, filters, setFilters } = useClustersSearchParams()

const network = useNetworkQuery().query.value
const [operators, setOperators] = useQueryState(
searchQueryKey,
clustersSearchFilters.operators
)

const query = useQuery({
queryKey: ["operators", "search", search, network],
queryFn: async () => {
Expand All @@ -41,8 +55,8 @@ export function OperatorsFilter() {
return (
<FilterButton
name="Operators"
activeFiltersCount={filters.operators?.length ?? 0}
onClear={() => setFilters((prev) => ({ ...prev, operators: [] }))}
activeFiltersCount={operators?.length ?? 0}
onClear={() => setOperators(null)}
popover={{
root: {
open,
Expand All @@ -61,20 +75,15 @@ export function OperatorsFilter() {
onValueChange={(value) => setSearch(value)}
/>
</div>
{Boolean(filters.operators?.length) && (
{Boolean(operators?.length) && (
<div className="flex flex-wrap gap-1 border-y border-gray-200 p-2">
{filters.operators?.map((id) => (
{operators?.map((id) => (
<Button
size="sm"
key={id}
className="h-6 gap-0.5 rounded-full pb-px pl-2 pr-1"
variant="secondary"
onClick={() =>
setFilters((prev) => ({
...prev,
operators: xor(prev.operators, [id]),
}))
}
onClick={() => setOperators(xor(operators, [id]))}
>
<Text variant="caption-medium">{id}</Text>{" "}
<div className="flex size-4 items-center justify-center">
Expand All @@ -98,15 +107,12 @@ export function OperatorsFilter() {
value={operator.id.toString()}
className="flex h-10 items-center space-x-2 px-2 py-1"
onSelect={() => {
setFilters((prev) => ({
...prev,
operators: xor(prev.operators, [operator.id]),
}))
setOperators(xor(operators, [operator.id]))
}}
>
<Checkbox
id={operator.id.toString()}
checked={filters.operators?.includes(operator.id)}
checked={operators?.includes(operator.id)}
className="mr-2"
/>
<OperatorInfo
Expand Down
Loading