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..fee463f0 --- /dev/null +++ b/src/app/(sidebar)/smart-contracts/contract-explorer/components/ContractInfo.tsx @@ -0,0 +1,177 @@ +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 { ContractInfoApiResponse, NetworkType } from "@/types/types"; + +export const ContractInfo = ({ + infoData, + networkId, +}: { + infoData: ContractInfoApiResponse; + 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..2ea5a6d2 --- /dev/null +++ b/src/app/(sidebar)/smart-contracts/contract-explorer/page.tsx @@ -0,0 +1,196 @@ +"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"; + +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 contractInfoData ? ( + + ) : null; + }; + + 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..1766d1e9 --- /dev/null +++ b/src/app/(sidebar)/smart-contracts/styles.scss @@ -0,0 +1,39 @@ +@use "../../../styles/utils.scss" as *; + +.ContractExplorer { + &__form { + .Button { + width: fit-content; + } + } +} + +.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/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..bb4b944a 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", @@ -44,7 +43,7 @@ const primaryNavLinks: NavLink[] = [ }, ]; -export const MainNav = () => { +export const MainNav = ({ excludeDocs }: { excludeDocs?: boolean }) => { const pathname = usePathname(); const isActiveRoute = (link: string) => { @@ -66,10 +65,14 @@ export const MainNav = () => { ); + const links = excludeDocs + ? primaryNavLinks.filter((l) => l.label !== "View Docs") + : primaryNavLinks; + return (