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
98 changes: 98 additions & 0 deletions services/explorer-ui/src/components/option-buttons/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { CustomTooltip } from "~/components/custom-tooltip";
import {
Button,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Separator,
} from "~/components/ui";

export type OptionItem = {
id: string;
label: string;
};

export type OptionItems = OptionItem[];

type ExtractOptionId<T extends OptionItems> = T[number]["id"];

type OptionButtonProps<T extends OptionItems> = {
options: T;
availableOptions: Record<string, boolean>; // TODO: this should work: Record<ExtractOptionId<T>, boolean>
onOptionSelect: (option: ExtractOptionId<T>) => void;
selectedItem: ExtractOptionId<T>;
};

const withAvailableTooltip = (
isAvailable: boolean,
key: number,
children: React.ReactNode
) => {
// TODO: refactor to use CustomTooltip component instead?
if (!isAvailable) {
return (
<CustomTooltip key={key} content="Not available">
{children}
</CustomTooltip>
);
}

return <div key={key}>{children}</div>;
};

export const OptionButtons = <T extends OptionItems>({
options,
availableOptions,
onOptionSelect,
selectedItem,
}: OptionButtonProps<T>) => {
return (
<>
<div className="hidden lg:flex flex-row gap-4 mb-4 justify-center">
{options.map((option, key) => {
const isAvailable = availableOptions[option.id];
return withAvailableTooltip(
isAvailable,
key,
<div className="flex flex-col justify-center items-center gap-1">
<Button
key={key}
onClick={() => onOptionSelect(option.id)}
disabled={!isAvailable}
variant="default"
className="hover:bg-slate-500 border border-input"
>
{option.label}
</Button>
{selectedItem === option.id && (
<Separator className="h-0.5 bg-gray-700 w-1/2" />
)}
</div>
);
})}
</div>
<div className="mb-1 mt-4 lg:hidden">
<Select onValueChange={onOptionSelect} value={selectedItem}>
<SelectTrigger className="h-8 w-3/5 bg-primary text-white">
<SelectValue placeholder={selectedItem} />
</SelectTrigger>
<SelectContent>
{
options.map((option, key) => (
<SelectItem
key={key}
disabled={!availableOptions[option.id]}
value={option.id}
>
{option.label}
</SelectItem>
))
}
</SelectContent>
</Select>
</div>
</>
);
};
19 changes: 19 additions & 0 deletions services/explorer-ui/src/pages/block-details/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { z } from "zod";

export type tabId = "txEffects" | "contracts";

export const tabIds = ["txEffects", "contracts"] as const;

export const tabIdSchema = z.enum(tabIds);
export type TabId = z.infer<typeof tabIdSchema>;

export const tabSchema = z.object({
id: tabIdSchema,
label: z.string(),
});
export type Tab = z.infer<typeof tabSchema>;

export const blockDetailsTabs: Tab[] = [
{ id: "txEffects", label: "Transaction effects" },
{ id: "contracts", label: "Contracts" },
];
35 changes: 21 additions & 14 deletions services/explorer-ui/src/pages/block-details/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { useParams } from "@tanstack/react-router";
import { type FC } from "react";
import { useState, type FC } from "react";
import { KeyValueDisplay } from "~/components/info-display/key-value-display";
import { OptionButtons } from "~/components/option-buttons";
import { TxEffectsTable } from "~/components/tx-effects/tx-effects-table";
import { Button } from "~/components/ui";
import {
useGetBlockByIdentifier,
useGetTxEffectsByBlockHeight,
useSubTitle,
} from "~/hooks";
import { blockDetailsTabs, type TabId } from "./constants";
import { getBlockDetails, getTxEffects } from "./util";

export const BlockDetails: FC = () => {
const { blockNumber } = useParams({
from: "/blocks/$blockNumber",
});
useSubTitle(`Block ${blockNumber}`);
const [selectedTab, setSelectedTab] = useState<TabId>("txEffects");
const onOptionSelect = (value: string) => {
setSelectedTab(value as TabId);
};
const {
data: latestBlock,
isLoading,
Expand All @@ -28,7 +33,6 @@ export const BlockDetails: FC = () => {
error: txEffectsError,
} = useGetTxEffectsByBlockHeight(height);

//TODO: Check for better solution
if (!latestBlock) return <div> No block hash</div>;

return (
Expand All @@ -37,19 +41,22 @@ export const BlockDetails: FC = () => {
<div>
<h2>Block Details </h2>
</div>
<div className="flex flex-col gap-4 mt-8">
<div className="flex flex-col gap-4 mt-8 pb-4">
<div className="bg-white rounded-lg shadow-md p-4">
<KeyValueDisplay data={getBlockDetails(latestBlock)} />
</div>
<div className="flex flex-row gap-4 w-10 mb-4">
<Button
variant={"default"}
className={"shadow-[0px_0px_1px_2px_rgba(0,0,0,1)]"}
>
<p>View TxEffects</p>
</Button>
</div>
<div className="rounded-lg shadow-lg">
</div>
<OptionButtons
options={blockDetailsTabs}
availableOptions={{
txEffects: !!blockTxEffects,
contracts: false, // TODO
}}
onOptionSelect={onOptionSelect}
selectedItem={selectedTab}
/>
<div className="bg-white rounded-lg shadow-md p-4">
{selectedTab === "txEffects" && (
<TxEffectsTable
txEffects={getTxEffects(blockTxEffects, latestBlock)}
isLoading={
Expand All @@ -59,7 +66,7 @@ export const BlockDetails: FC = () => {
}
error={error ?? txEffectsError}
/>
</div>
)}
</div>
</div>
</div>
Expand Down
14 changes: 8 additions & 6 deletions services/explorer-ui/src/pages/contract-class-details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useState, type FC } from "react";
import { ContractClassesTable } from "~/components/contracts/classes/table";
import { ContractInstancesTable } from "~/components/contracts/instances/table";
import { KeyValueDisplay } from "~/components/info-display/key-value-display";
import { OptionButtons } from "~/components/option-buttons";
import {
useContractClassPrivateFunctions,
useContractClassUnconstrainedFunctions,
Expand All @@ -12,15 +13,14 @@ import {
} from "~/hooks";
import { mapContractClasses, mapContractInstances } from "../contract/util";
import { contractClassTabs, type TabId } from "./constants";
import { OptionButtons } from "./tabs";
import { getContractClassKeyValueData } from "./util";

export const ContractClassDetails: FC = () => {
const [selectedTab, setSelectedTab] = useState<TabId>("contractVersions");
const { id, version } = useParams({
from: "/contracts/classes/$id/versions/$version",
});
useSubTitle(`Ctrct cls ${id}`);
const [selectedTab, setSelectedTab] = useState<TabId>("contractVersions");
const onOptionSelect = (value: string) => {
setSelectedTab(value as TabId);
};
Expand Down Expand Up @@ -48,11 +48,13 @@ export const ContractClassDetails: FC = () => {
privateFunctions:
!contractClassPrivateFunctionsHookRes.isLoading &&
!contractClassPrivateFunctionsHookRes.error &&
!!contractClassPrivateFunctionsHookRes.data,
!!contractClassPrivateFunctionsHookRes.data &&
!!contractClassPrivateFunctionsHookRes.data.length,
unconstrainedFunctions:
!contractClassUnconstrainedFunctionsHookRes.isLoading &&
!contractClassUnconstrainedFunctionsHookRes.error &&
!!contractClassUnconstrainedFunctionsHookRes.data,
!!contractClassUnconstrainedFunctionsHookRes.data &&
!!contractClassUnconstrainedFunctionsHookRes.data.length,
};

if (!id) return <div>No classId</div>;
Expand All @@ -77,8 +79,8 @@ export const ContractClassDetails: FC = () => {
</div>
</div>
<OptionButtons
availableData={isOptionAvailable}
requiredOptions={contractClassTabs}
options={contractClassTabs}
availableOptions={isOptionAvailable}
onOptionSelect={onOptionSelect}
selectedItem={selectedTab}
/>
Expand Down
94 changes: 0 additions & 94 deletions services/explorer-ui/src/pages/contract-class-details/tabs.tsx

This file was deleted.

Loading