From 4308bf8e92a70ef361777dd42ecd7d6da5cc3a71 Mon Sep 17 00:00:00 2001 From: Iveta Date: Wed, 18 Dec 2024 08:46:04 -0500 Subject: [PATCH 1/8] Contract Explorer: Contract info --- .../account/fund/components/SwitchNetwork.tsx | 41 +--- src/app/(sidebar)/account/saved/page.tsx | 24 +-- .../components/ContractInfo.tsx | 173 +++++++++++++++ .../contract-explorer/page.tsx | 197 ++++++++++++++++++ src/app/(sidebar)/smart-contracts/layout.tsx | 12 +- src/app/(sidebar)/smart-contracts/page.tsx | 5 - src/app/(sidebar)/smart-contracts/styles.scss | 18 ++ .../transaction/sign/components/Overview.tsx | 21 +- src/components/MainNav.tsx | 9 +- src/components/MessageField.tsx | 22 ++ src/components/SwitchNetworkButtons.tsx | 41 ++++ src/components/TabView/index.tsx | 22 +- src/constants/navItems.tsx | 12 ++ src/constants/routes.ts | 1 + src/constants/settings.ts | 2 + src/helpers/formatNumber.ts | 2 + src/helpers/getBlockExplorerLink.ts | 15 +- src/helpers/stellarExpertAccountLink.ts | 16 ++ src/query/external/useSEContractInfo.ts | 36 ++++ src/store/createStore.ts | 33 +++ src/styles/globals.scss | 12 ++ src/types/types.ts | 32 +++ tests/submitTransactionPage.test.ts | 3 +- 23 files changed, 636 insertions(+), 113 deletions(-) create mode 100644 src/app/(sidebar)/smart-contracts/contract-explorer/components/ContractInfo.tsx create mode 100644 src/app/(sidebar)/smart-contracts/contract-explorer/page.tsx delete mode 100644 src/app/(sidebar)/smart-contracts/page.tsx create mode 100644 src/app/(sidebar)/smart-contracts/styles.scss create mode 100644 src/components/MessageField.tsx create mode 100644 src/components/SwitchNetworkButtons.tsx create mode 100644 src/helpers/formatNumber.ts create mode 100644 src/helpers/stellarExpertAccountLink.ts create mode 100644 src/query/external/useSEContractInfo.ts diff --git a/src/app/(sidebar)/account/fund/components/SwitchNetwork.tsx b/src/app/(sidebar)/account/fund/components/SwitchNetwork.tsx index 0df1c3be..fa51eac9 100644 --- a/src/app/(sidebar)/account/fund/components/SwitchNetwork.tsx +++ b/src/app/(sidebar)/account/fund/components/SwitchNetwork.tsx @@ -1,24 +1,11 @@ "use client"; -import { Card, Text, Button } from "@stellar/design-system"; - -import { NetworkOptions } from "@/constants/settings"; -import { useStore } from "@/store/useStore"; - -import { NetworkType } from "@/types/types"; +import { Card, Text } from "@stellar/design-system"; +import { SwitchNetworkButtons } from "@/components/SwitchNetworkButtons"; import "../../styles.scss"; export const SwitchNetwork = () => { - const { selectNetwork, updateIsDynamicNetworkSelect } = useStore(); - const onSwitchNetwork = (network: NetworkType) => { - const selectedNetwork = NetworkOptions.find((n) => n.id === network); - if (selectedNetwork) { - updateIsDynamicNetworkSelect(true); - selectNetwork(selectedNetwork); - } - }; - return (
@@ -33,26 +20,12 @@ export const SwitchNetwork = () => { fund an account with 10,000 lumens on the futurenet network.
-
- - +
+
diff --git a/src/app/(sidebar)/account/saved/page.tsx b/src/app/(sidebar)/account/saved/page.tsx index 6dd8f30f..f27cb83f 100644 --- a/src/app/(sidebar)/account/saved/page.tsx +++ b/src/app/(sidebar)/account/saved/page.tsx @@ -16,6 +16,7 @@ import { InputSideElement } from "@/components/InputSideElement"; import { SavedItemTimestampAndDelete } from "@/components/SavedItemTimestampAndDelete"; import { PageCard } from "@/components/layout/PageCard"; import { SaveToLocalStorageModal } from "@/components/SaveToLocalStorageModal"; +import { SwitchNetworkButtons } from "@/components/SwitchNetworkButtons"; import { localStorageSavedKeypairs } from "@/helpers/localStorageSavedKeypairs"; import { arrayItem } from "@/helpers/arrayItem"; @@ -131,25 +132,10 @@ export default function SavedKeypairs() { - - - + ); diff --git a/src/app/(sidebar)/smart-contracts/contract-explorer/components/ContractInfo.tsx b/src/app/(sidebar)/smart-contracts/contract-explorer/components/ContractInfo.tsx new file mode 100644 index 00000000..82a7d7fb --- /dev/null +++ b/src/app/(sidebar)/smart-contracts/contract-explorer/components/ContractInfo.tsx @@ -0,0 +1,173 @@ +import { Avatar, Card, Icon, Logo } from "@stellar/design-system"; +import { Box } from "@/components/layout/Box"; +import { SdsLink } from "@/components/SdsLink"; +import { formatEpochToDate } from "@/helpers/formatEpochToDate"; +import { formatNumber } from "@/helpers/formatNumber"; +import { stellarExpertAccountLink } from "@/helpers/stellarExpertAccountLink"; +import { NetworkType } from "@/types/types"; + +export const ContractInfo = ({ + infoData, + networkId, +}: { + infoData: any; + networkId: NetworkType; +}) => { + type ContractExplorerInfoField = { + id: string; + label: string; + }; + + const INFO_FIELDS: ContractExplorerInfoField[] = [ + { + id: "repository", + label: "Source Code", + }, + { + id: "created", + label: "Created", + }, + { + id: "wasm", + label: "WASM Hash", + }, + { + id: "versions", + label: "Versions", + }, + { + id: "creator", + label: "Creator", + }, + { + id: "storage_entries", + label: "Data Storage", + }, + ]; + + const InfoFieldItem = ({ + label, + value, + }: { + label: string; + value: React.ReactNode; + }) => { + return ( + +
{label}
+
{value ?? "-"}
+
+ ); + }; + + const formatEntriesText = (entriesCount: number) => { + if (!entriesCount) { + return ""; + } + + if (entriesCount === 1) { + return `1 entry`; + } + + return `${formatNumber(entriesCount)} entries`; + }; + + const renderInfoField = (field: ContractExplorerInfoField) => { + switch (field.id) { + case "repository": + return ( + + + {infoData.validation.repository.replace( + "https://github.com/", + "", + )} + + + ) : null + } + /> + ); + case "created": + return ( + + ); + case "wasm": + return ( + + ); + case "versions": + return ( + + ); + case "creator": + return ( + + + {infoData.creator} + + + ) : null + } + /> + ); + case "storage_entries": + return ( + + ); + default: + return null; + } + }; + + return ( + + + <>{INFO_FIELDS.map((f) => renderInfoField(f))} + + + ); +}; diff --git a/src/app/(sidebar)/smart-contracts/contract-explorer/page.tsx b/src/app/(sidebar)/smart-contracts/contract-explorer/page.tsx new file mode 100644 index 00000000..f4b3a184 --- /dev/null +++ b/src/app/(sidebar)/smart-contracts/contract-explorer/page.tsx @@ -0,0 +1,197 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Button, Card, Icon, Input, Link, Text } from "@stellar/design-system"; +import { useQueryClient } from "@tanstack/react-query"; + +import { useStore } from "@/store/useStore"; +import { useSEContractInfo } from "@/query/external/useSEContractInfo"; +import { validate } from "@/validate"; + +import { Box } from "@/components/layout/Box"; +import { PageCard } from "@/components/layout/PageCard"; +import { MessageField } from "@/components/MessageField"; +import { TabView } from "@/components/TabView"; +import { SwitchNetworkButtons } from "@/components/SwitchNetworkButtons"; + +import { ContractInfo } from "./components/ContractInfo"; + +// TODO: mobile UI +// TODO: update nav break points +// TODO: tests +export default function ContractExplorer() { + const { network, smartContracts } = useStore(); + const [contractActiveTab, setContractActiveTab] = useState("contract-info"); + const [contractIdInput, setContractIdInput] = useState(""); + const [contractIdInputError, setContractIdInputError] = useState(""); + + const { + data: contractInfoData, + error: contractInfoError, + isLoading: isContractInfoLoading, + isFetching: isContractInfoFetching, + refetch: fetchContractInfo, + } = useSEContractInfo({ + networkId: network.id, + contractId: contractIdInput, + }); + + const queryClient = useQueryClient(); + + useEffect(() => { + if (smartContracts.explorer.contractId) { + setContractIdInput(smartContracts.explorer.contractId); + } + // On page load only + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const resetFetchContractInfo = () => { + if (contractInfoData) { + queryClient.resetQueries({ + queryKey: ["useSEContractInfo"], + }); + smartContracts.resetExplorerContractId(); + } + + if (contractIdInputError) { + setContractIdInputError(""); + } + }; + + const isCurrentNetworkSupported = ["mainnet", "testnet"].includes(network.id); + + const renderContractInfoContent = () => { + return ; + }; + + const renderContractInvokeContent = () => { + return Coming soon; + }; + + const renderButtons = () => { + if (isCurrentNetworkSupported) { + return ( + + + + <> + {contractIdInput ? ( + + ) : null} + + + ); + } + + return ( + + + + ); + }; + + return ( + + +
{ + e.preventDefault(); + fetchContractInfo(); + smartContracts.updateExplorerContractId(contractIdInput); + }} + className="ContractExplorer__form" + > + + } + placeholder="Ex: CCBWOUL7XW5XSWD3UKL76VWLLFCSZP4D4GUSCFBHUQCEAW23QVKJZ7ON" + error={contractIdInputError} + value={contractIdInput} + onChange={(e) => { + resetFetchContractInfo(); + setContractIdInput(e.target.value); + + const error = validate.getContractIdError(e.target.value); + setContractIdInputError(error || ""); + }} + note={ + isCurrentNetworkSupported + ? "" + : "You must switch your network to Mainnet or Testnet in order to see contract info." + } + /> + + <>{renderButtons()} + + <> + {contractInfoError ? ( + + ) : null} + + +
+
+ + <> + {contractInfoData ? ( + <> + { + setContractActiveTab(tabId); + }} + /> + + + Powered by{" "} + Stellar.Expert + + + ) : null} + +
+ ); +} diff --git a/src/app/(sidebar)/smart-contracts/layout.tsx b/src/app/(sidebar)/smart-contracts/layout.tsx index 4ef90538..dd560588 100644 --- a/src/app/(sidebar)/smart-contracts/layout.tsx +++ b/src/app/(sidebar)/smart-contracts/layout.tsx @@ -1,20 +1,18 @@ "use client"; import React from "react"; - import { LayoutSidebarContent } from "@/components/layout/LayoutSidebarContent"; +import { SMART_CONTRACTS_NAV_ITEMS } from "@/constants/navItems"; + +import "./styles.scss"; -export default function TransactionTemplate({ +export default function SmartContractsTemplate({ children, }: { children: React.ReactNode; }) { return ( - + {children} ); diff --git a/src/app/(sidebar)/smart-contracts/page.tsx b/src/app/(sidebar)/smart-contracts/page.tsx deleted file mode 100644 index 74d178fe..00000000 --- a/src/app/(sidebar)/smart-contracts/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -"use client"; - -export default function SmartContracts() { - return
Coming soon
; -} diff --git a/src/app/(sidebar)/smart-contracts/styles.scss b/src/app/(sidebar)/smart-contracts/styles.scss new file mode 100644 index 00000000..179c3e3f --- /dev/null +++ b/src/app/(sidebar)/smart-contracts/styles.scss @@ -0,0 +1,18 @@ +@use "../../../styles/utils.scss" as *; + +.ContractExplorer { + &__form { + .Button { + width: fit-content; + } + } +} + +.InfoFieldItem { + font-size: pxToRem(12px); + line-height: pxToRem(18px); + + &__label { + width: pxToRem(128px); + } +} diff --git a/src/app/(sidebar)/transaction/sign/components/Overview.tsx b/src/app/(sidebar)/transaction/sign/components/Overview.tsx index 5d68c055..5f8049cb 100644 --- a/src/app/(sidebar)/transaction/sign/components/Overview.tsx +++ b/src/app/(sidebar)/transaction/sign/components/Overview.tsx @@ -35,6 +35,7 @@ import { ViewInXdrButton } from "@/components/ViewInXdrButton"; import { PubKeyPicker } from "@/components/FormElements/PubKeyPicker"; import { LabelHeading } from "@/components/LabelHeading"; import { PageCard } from "@/components/layout/PageCard"; +import { MessageField } from "@/components/MessageField"; const MIN_LENGTH_FOR_FULL_WIDTH_FIELD = 30; @@ -426,26 +427,6 @@ export const Overview = () => { mergedFields = [...REQUIRED_FIELDS, ...TX_FIELDS(sign.importTx)]; } - const MessageField = ({ - message, - isError, - }: { - message: string; - isError?: boolean; - }) => { - return ( - - {message} - {isError ? : } - - ); - }; - const SignTxButton = ({ label = "Sign transaction", onSign, diff --git a/src/components/MainNav.tsx b/src/components/MainNav.tsx index 851f08b4..e8c0fa14 100644 --- a/src/components/MainNav.tsx +++ b/src/components/MainNav.tsx @@ -32,11 +32,10 @@ const primaryNavLinks: NavLink[] = [ href: Routes.ENDPOINTS, label: "API Explorer", }, - // TODO: hide until ready - // { - // href: Routes.SMART_CONTRACTS, - // label: "Smart Contracts", - // }, + { + href: Routes.SMART_CONTRACTS_CONTRACT_EXPLORER, + label: "Smart Contracts", + }, { href: "https://developers.stellar.org/", label: "View Docs", diff --git a/src/components/MessageField.tsx b/src/components/MessageField.tsx new file mode 100644 index 00000000..2f1a2b8d --- /dev/null +++ b/src/components/MessageField.tsx @@ -0,0 +1,22 @@ +import { Icon } from "@stellar/design-system"; +import { Box } from "@/components/layout/Box"; + +export const MessageField = ({ + message, + isError, +}: { + message: string; + isError?: boolean; +}) => { + return ( + + {message} + {isError ? : } + + ); +}; diff --git a/src/components/SwitchNetworkButtons.tsx b/src/components/SwitchNetworkButtons.tsx new file mode 100644 index 00000000..1e4b30e1 --- /dev/null +++ b/src/components/SwitchNetworkButtons.tsx @@ -0,0 +1,41 @@ +import { Button } from "@stellar/design-system"; +import { getNetworkById } from "@/helpers/getNetworkById"; +import { useStore } from "@/store/useStore"; +import { NetworkType } from "@/types/types"; +import { capitalizeString } from "@/helpers/capitalizeString"; + +export const SwitchNetworkButtons = ({ + includedNetworks, + buttonSize, +}: { + includedNetworks: NetworkType[]; + buttonSize: "sm" | "md" | "lg"; +}) => { + const { selectNetwork, updateIsDynamicNetworkSelect } = useStore(); + + const getAndSetNetwork = (networkId: NetworkType) => { + const newNetwork = getNetworkById(networkId); + + if (newNetwork) { + updateIsDynamicNetworkSelect(true); + selectNetwork(newNetwork); + } + }; + + return ( + <> + {includedNetworks.map((n) => ( + + ))} + + ); +}; diff --git a/src/components/TabView/index.tsx b/src/components/TabView/index.tsx index adfb8415..5ed5686f 100644 --- a/src/components/TabView/index.tsx +++ b/src/components/TabView/index.tsx @@ -14,7 +14,7 @@ type Tab = { }; type TabViewProps = { - heading: TabViewHeadingProps; + heading?: TabViewHeadingProps; tab1: Tab; tab2: Tab; tab3?: Tab; @@ -42,9 +42,9 @@ export const TabView = ({ })); return ( - +
- + {heading ? : null}
- -
- {tabContent.map((tc) => ( -
- {tc.content} -
- ))} -
-
+
+ {tabContent.map((tc) => ( +
+ {tc.content} +
+ ))} +
); }; diff --git a/src/constants/navItems.tsx b/src/constants/navItems.tsx index 81794293..4d467da0 100644 --- a/src/constants/navItems.tsx +++ b/src/constants/navItems.tsx @@ -117,3 +117,15 @@ export const XDR_NAV_ITEMS = [ ], }, ]; + +export const SMART_CONTRACTS_NAV_ITEMS = [ + { + instruction: "Smart Contract Tools", + navItems: [ + { + route: Routes.SMART_CONTRACTS_CONTRACT_EXPLORER, + label: "Contract Explorer", + }, + ], + }, +]; diff --git a/src/constants/routes.ts b/src/constants/routes.ts index 19a854cd..dae40ce9 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -83,4 +83,5 @@ export enum Routes { TO_XDR = "/xdr/to", // Smart Contracts SMART_CONTRACTS = "/smart-contracts", + SMART_CONTRACTS_CONTRACT_EXPLORER = "/smart-contracts/contract-explorer", } diff --git a/src/constants/settings.ts b/src/constants/settings.ts index b46aea29..92d871aa 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -120,3 +120,5 @@ export const OPERATION_TRUSTLINE_CLEAR_FLAGS = [ ]; export const GITHUB_URL = "https://github.com/stellar/laboratory"; +export const STELLAR_EXPERT = "https://stellar.expert/explorer"; +export const STELLAR_EXPERT_API = "https://api.stellar.expert/explorer"; diff --git a/src/helpers/formatNumber.ts b/src/helpers/formatNumber.ts new file mode 100644 index 00000000..702a3516 --- /dev/null +++ b/src/helpers/formatNumber.ts @@ -0,0 +1,2 @@ +export const formatNumber = (num: number) => + new Intl.NumberFormat("en-US").format(num); diff --git a/src/helpers/getBlockExplorerLink.ts b/src/helpers/getBlockExplorerLink.ts index 10a63882..5344f66d 100644 --- a/src/helpers/getBlockExplorerLink.ts +++ b/src/helpers/getBlockExplorerLink.ts @@ -1,15 +1,9 @@ +import { STELLAR_EXPERT } from "@/constants/settings"; + export type BlockExplorer = "stellar.expert" | "stellarchain.io"; export const getBlockExplorerLink = (explorer: BlockExplorer) => { switch (explorer) { - case "stellar.expert": { - return { - mainnet: "https://stellar.expert/explorer/public", - testnet: "https://stellar.expert/explorer/testnet", - futurenet: "", - custom: "", - }; - } case "stellarchain.io": return { mainnet: "https://stellarchain.io", @@ -18,10 +12,11 @@ export const getBlockExplorerLink = (explorer: BlockExplorer) => { custom: "", }; // defaults to stellar.expert + case "stellar.expert": default: return { - mainnet: "https://stellar.expert/explorer/public", - testnet: "https://stellar.expert/explorer/testnet", + mainnet: `${STELLAR_EXPERT}/public`, + testnet: `${STELLAR_EXPERT}/testnet`, futurenet: "", custom: "", }; diff --git a/src/helpers/stellarExpertAccountLink.ts b/src/helpers/stellarExpertAccountLink.ts new file mode 100644 index 00000000..d10fe803 --- /dev/null +++ b/src/helpers/stellarExpertAccountLink.ts @@ -0,0 +1,16 @@ +import { STELLAR_EXPERT } from "@/constants/settings"; +import { NetworkType } from "@/types/types"; + +export const stellarExpertAccountLink = ( + accountAddress: string, + networkId: NetworkType, +) => { + // Not supported networks + if (["futurenet", "custom"].includes(networkId)) { + return ""; + } + + const network = networkId === "mainnet" ? "public" : "testnet"; + + return `${STELLAR_EXPERT}/${network}/account/${accountAddress}`; +}; diff --git a/src/query/external/useSEContractInfo.ts b/src/query/external/useSEContractInfo.ts new file mode 100644 index 00000000..abd846de --- /dev/null +++ b/src/query/external/useSEContractInfo.ts @@ -0,0 +1,36 @@ +import { useQuery } from "@tanstack/react-query"; +import { STELLAR_EXPERT_API } from "@/constants/settings"; +import { ContractInfoApiResponse, NetworkType } from "@/types/types"; + +export const useSEContractInfo = ({ + networkId, + contractId, +}: { + networkId: NetworkType; + contractId: string; +}) => { + const query = useQuery({ + queryKey: ["useSEContractInfo", networkId, contractId], + queryFn: async () => { + // Not supported networks + if (["futurenet", "custom"].includes(networkId)) { + return null; + } + + const network = networkId === "mainnet" ? "public" : "testnet"; + + try { + const response = await fetch( + `${STELLAR_EXPERT_API}/${network}/contract/${contractId}`, + ); + + return await response.json(); + } catch (e: any) { + throw `Something went wrong. ${e}`; + } + }, + enabled: false, + }); + + return query; +}; diff --git a/src/store/createStore.ts b/src/store/createStore.ts index 86033b90..790a5812 100644 --- a/src/store/createStore.ts +++ b/src/store/createStore.ts @@ -178,6 +178,15 @@ export interface Store { resetXdr: () => void; resetJsonString: () => void; }; + + // Smart Contracts + smartContracts: { + explorer: { + contractId: string; + }; + updateExplorerContractId: (contractId: string) => void; + resetExplorerContractId: () => void; + }; } interface CreateStoreOptions { @@ -255,6 +264,12 @@ const initXdrState = { type: XDR_TYPE_TRANSACTION_ENVELOPE, }; +const initSmartContractsState = { + explorer: { + contractId: "", + }, +}; + // Store export const createStore = (options: CreateStoreOptions) => create()( @@ -480,6 +495,7 @@ export const createStore = (options: CreateStoreOptions) => state.transaction.feeBump = initTransactionState.feeBump; }), }, + // XDR xdr: { ...initXdrState, updateXdrBlob: (blob: string) => @@ -505,6 +521,18 @@ export const createStore = (options: CreateStoreOptions) => state.xdr.type = initXdrState.type; }), }, + // Smart Contracts + smartContracts: { + ...initSmartContractsState, + updateExplorerContractId: (contractId) => + set((state) => { + state.smartContracts.explorer.contractId = contractId; + }), + resetExplorerContractId: () => + set((state) => { + state.smartContracts.explorer.contractId = ""; + }), + }, })), { url: options.url, @@ -549,6 +577,11 @@ export const createStore = (options: CreateStoreOptions) => jsonString: true, type: true, }, + smartContracts: { + explorer: { + contractId: true, + }, + }, }; }, }, diff --git a/src/styles/globals.scss b/src/styles/globals.scss index 9aa31339..62323f79 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -69,6 +69,18 @@ } } + .Link--external { + gap: pxToRem(4px); + align-items: center; + + svg { + // GitHub icon + g[clip-path="url(#github_svg__a)"] path { + fill: currentColor !important; + } + } + } + // =========================================================================== // Layout // =========================================================================== diff --git a/src/types/types.ts b/src/types/types.ts index fc0098a1..d0458d50 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -351,3 +351,35 @@ export interface SavedRpcMethod extends LocalStorageSavedItem { shareableUrl: string | undefined; payload: AnyObject; } + +// ============================================================================= +// Smart Contract Explorer +// ============================================================================= +export type ContractInfoApiResponse = { + contract: string; + created: number; + creator: string; + account?: string; + payments?: number; + trades?: number; + wasm?: string; + storage_entries?: number; + validation?: { + status?: "verified" | "unverified"; + repository?: string; + commit?: string; + package?: string; + make?: string; + ts?: number; + }; + versions?: number; + salt?: string; + asset?: string; + code?: string; + issuer?: string; + functions?: { + invocations: number; + subinvocations: number; + function: string; + }[]; +}; diff --git a/tests/submitTransactionPage.test.ts b/tests/submitTransactionPage.test.ts index 8f5bf416..afcb2c65 100644 --- a/tests/submitTransactionPage.test.ts +++ b/tests/submitTransactionPage.test.ts @@ -1,3 +1,4 @@ +import { STELLAR_EXPERT } from "@/constants/settings"; import { test, expect, Page } from "@playwright/test"; test.describe("Submit Transaction Page", () => { @@ -493,7 +494,7 @@ const testBlockExplorerLink = async ({ // Assert the StellarExpert URL expect(newTabUrl).toBe( - `https://stellar.expert/explorer/${network?.toLowerCase()}/tx/${hash}`, + `${STELLAR_EXPERT}/${network?.toLowerCase()}/tx/${hash}`, ); const [stellarChainPage] = await Promise.all([ From d758edce4226538051addaf1739db4d9da7bd492 Mon Sep 17 00:00:00 2001 From: Iveta Date: Wed, 18 Dec 2024 09:04:22 -0500 Subject: [PATCH 2/8] Tweak fee bump test --- tests/feeBumpPage.test.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/feeBumpPage.test.ts b/tests/feeBumpPage.test.ts index 6a858737..13a96fd9 100644 --- a/tests/feeBumpPage.test.ts +++ b/tests/feeBumpPage.test.ts @@ -55,7 +55,10 @@ test.describe("Fee Bump Page", () => { await page.getByLabel("Base Fee").fill(BASE_FEE); await page.getByText("Sign in Transaction Signer").click(); - await expect(page.locator("h1")).toHaveText("Transaction Overview"); + // Adding extra delay because sometimes it takes longer to load the next page + await expect(page.locator("h1")).toHaveText("Transaction Overview", { + timeout: 5000, + }); await expect(page.getByLabel("Transaction Envelope XDR")).toHaveText( MOCK_XDR, ); @@ -65,7 +68,10 @@ test.describe("Fee Bump Page", () => { await page.getByLabel("Base Fee").fill(BASE_FEE); await page.getByText("View in XDR Viewer").click(); - await expect(page.locator("h1")).toHaveText("View XDR"); + // Adding extra delay because sometimes it takes longer to load the next page + await expect(page.locator("h1")).toHaveText("View XDR", { + timeout: 5000, + }); await expect(page.getByLabel("Base-64 encoded XDR")).toHaveText(MOCK_XDR); }); }); From 794bb73740ea4d567db834000b42cd49474a1301 Mon Sep 17 00:00:00 2001 From: Iveta Date: Wed, 18 Dec 2024 10:49:12 -0500 Subject: [PATCH 3/8] Responsive contract info + top nav --- .../components/ContractInfo.tsx | 4 +- .../contract-explorer/page.tsx | 3 - src/app/(sidebar)/smart-contracts/styles.scss | 21 +++ src/components/MainNav.tsx | 8 +- .../layout/LayoutContextProvider.tsx | 14 +- src/components/layout/LayoutHeader.tsx | 149 +++++++++++------- 6 files changed, 130 insertions(+), 69 deletions(-) diff --git a/src/app/(sidebar)/smart-contracts/contract-explorer/components/ContractInfo.tsx b/src/app/(sidebar)/smart-contracts/contract-explorer/components/ContractInfo.tsx index 82a7d7fb..4c9135d2 100644 --- a/src/app/(sidebar)/smart-contracts/contract-explorer/components/ContractInfo.tsx +++ b/src/app/(sidebar)/smart-contracts/contract-explorer/components/ContractInfo.tsx @@ -54,7 +54,7 @@ export const ContractInfo = ({ }) => { return ( - + <>{INFO_FIELDS.map((f) => renderInfoField(f))} diff --git a/src/app/(sidebar)/smart-contracts/contract-explorer/page.tsx b/src/app/(sidebar)/smart-contracts/contract-explorer/page.tsx index f4b3a184..e9ed7c7f 100644 --- a/src/app/(sidebar)/smart-contracts/contract-explorer/page.tsx +++ b/src/app/(sidebar)/smart-contracts/contract-explorer/page.tsx @@ -16,9 +16,6 @@ import { SwitchNetworkButtons } from "@/components/SwitchNetworkButtons"; import { ContractInfo } from "./components/ContractInfo"; -// TODO: mobile UI -// TODO: update nav break points -// TODO: tests export default function ContractExplorer() { const { network, smartContracts } = useStore(); const [contractActiveTab, setContractActiveTab] = useState("contract-info"); diff --git a/src/app/(sidebar)/smart-contracts/styles.scss b/src/app/(sidebar)/smart-contracts/styles.scss index 179c3e3f..1766d1e9 100644 --- a/src/app/(sidebar)/smart-contracts/styles.scss +++ b/src/app/(sidebar)/smart-contracts/styles.scss @@ -8,11 +8,32 @@ } } +.ContractInfo { + @media screen and (max-width: 500px) { + &.Box--xs { + gap: pxToRem(12px); + } + } +} + .InfoFieldItem { font-size: pxToRem(12px); line-height: pxToRem(18px); &__label { + flex-shrink: 0; width: pxToRem(128px); } + + &__value { + word-break: break-all; + } + + @media screen and (max-width: 500px) { + flex-wrap: wrap; + + &__label { + width: 100%; + } + } } diff --git a/src/components/MainNav.tsx b/src/components/MainNav.tsx index e8c0fa14..bb4b944a 100644 --- a/src/components/MainNav.tsx +++ b/src/components/MainNav.tsx @@ -43,7 +43,7 @@ const primaryNavLinks: NavLink[] = [ }, ]; -export const MainNav = () => { +export const MainNav = ({ excludeDocs }: { excludeDocs?: boolean }) => { const pathname = usePathname(); const isActiveRoute = (link: string) => { @@ -65,10 +65,14 @@ export const MainNav = () => { ); + const links = excludeDocs + ? primaryNavLinks.filter((l) => l.label !== "View Docs") + : primaryNavLinks; + return (