From 51968818975ada74f0135ca514d26c8017b2620c Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Mon, 21 Aug 2023 13:59:35 +0700 Subject: [PATCH 001/368] feat(components): add initia theme --- src/config/theme/index.ts | 1 + src/config/theme/initia.ts | 142 +++++++++++++++++++++++++++++++++++++ src/env.ts | 9 ++- 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 src/config/theme/initia.ts diff --git a/src/config/theme/index.ts b/src/config/theme/index.ts index 5c652a933..01a6befcc 100644 --- a/src/config/theme/index.ts +++ b/src/config/theme/index.ts @@ -1,3 +1,4 @@ export * from "./default"; export * from "./osmosis"; export * from "./sei"; +export * from "./initia"; diff --git a/src/config/theme/initia.ts b/src/config/theme/initia.ts new file mode 100644 index 000000000..e3e408e9f --- /dev/null +++ b/src/config/theme/initia.ts @@ -0,0 +1,142 @@ +import type { ThemeConfig } from "./types"; + +export const INITIA_THEME: ThemeConfig = { + branding: { + logo: "https://assets.alleslabs.dev/integrations/initia/logo.png", + favicon: "https://assets.alleslabs.dev/integrations/initia/favicon.svg", + seo: { + appName: "Initia", + title: "Initia Explorer | Powered by Celatone", + description: + "Explore Initia's layered ecosystem effortlessly, bridging Web2 to Web3, while delving into sovereign applications.", + image: "https://assets.alleslabs.dev/integrations/initia/cover.jpg", + twitter: { + handle: "@initiafnd", + cardType: "summary_large_image", + }, + }, + }, + fonts: { + heading: { + url: "https://xfonts.pro/fonts/pilat-wide_regular", + name: "Pilat Wide, serif", + }, + body: { + url: "https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500;600;700&display=swap", + name: "Manrope, sans-serif", + }, + }, + colors: { + gradient: { + main: "linear(to-tr, #15AFF8, #5FC1EE)", + }, + error: { + main: "#FF666E", + light: "#FF8086", + dark: "#B43E44", + background: "#4C1A1D", + }, + warning: { + main: "#FFBB33", + light: "#FFCC66", + dark: "#CC8800", + background: "#523600", + }, + success: { + main: "#42BEA6", + light: "#67CBB7", + dark: "#207966", + background: "#102E28", + }, + background: { + main: "#070708", + overlay: "rgba(17, 17, 23, 0.7)", + }, + text: { + main: "#F5F5F5", + dark: "#B7B7B7", + disabled: "#A1A6AA", + }, + primary: { + main: "#2AB9FC", + light: "#5FC1EE", + lighter: "#79D5FF", + dark: "#15AFF8", + darker: "#008FD7", + background: "#005881", + }, + secondary: { + main: "#2AB9FC", + light: "#5FC1EE", + dark: "#15AFF8", + darker: "#008FD7", + background: "#005881", + }, + accent: { + main: "#D8BEFC", + light: "#D2A3F9", + lighter: "#79D5FF", + dark: "#A28FBD", + darker: "#6C5F7E", + background: "#36303F", + }, + gray: { + 100: "#F5F5F5", + 400: "#B7B7B7", + 500: "#A1A6AA", + 600: "#7C7F83", + 700: "#3B3E41", + 800: "#26282B", + 900: "#151617", + }, + }, + tag: { + signer: { + bg: "primary.darker", + color: "text.main", + }, + related: { + bg: "accent.darker", + color: "inherit", + }, + }, + // button: { + // primary: { + // background: "#F5F5F5", + // color: "#151617", + // disabledBackground: "#A1A6AA", + // disabledColor: "#151617", + // }, + // }, + borderRadius: { + default: "4px", + iconButton: "36px", + viewButton: "0 0 8px 8px", + uploadButton: "50%", + tag: "full", + badge: "16px", + radio: "12px", + indicator: "2px", + stepper: "full", + }, + jsonTheme: "monokai", + illustration: { + error: + "https://assets.alleslabs.dev/integrations/initia/illustration/404.svg", + searchNotFound: + "https://assets.alleslabs.dev/integrations/initia/illustration/search-not-found.svg", + searchEmpty: + "https://assets.alleslabs.dev/integrations/initia/illustration/search-empty.svg", + disconnected: + "https://assets.alleslabs.dev/integrations/initia/illustration/disconnected.svg", + overview: { + // Fill image url + main: "", + }, + }, + socialMedia: { + website: "https://initia.tech/", + github: "https://github.com/initia-labs", + twitter: "https://twitter.com/initiaFND", + }, +}; diff --git a/src/env.ts b/src/env.ts index 6ad0eb6d9..8a779fea1 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,4 +1,9 @@ -import { DEFAULT_THEME, OSMOSIS_THEME, SEI_THEME } from "config/theme"; +import { + DEFAULT_THEME, + INITIA_THEME, + OSMOSIS_THEME, + SEI_THEME, +} from "config/theme"; export const SUPPORTED_CHAIN_IDS: string[] = (() => { const chainIds = process.env.NEXT_PUBLIC_SUPPORTED_CHAIN_IDS?.split(","); @@ -36,6 +41,8 @@ export const CURR_THEME = (() => { return OSMOSIS_THEME; case "sei": return SEI_THEME; + case "initia": + return INITIA_THEME; default: return DEFAULT_THEME; } From 7da534600f7679f1a48eaf748ab549030d604176 Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Mon, 21 Aug 2023 15:02:56 +0700 Subject: [PATCH 002/368] feat(components): add initia config --- src/config/chain/default.ts | 3 +++ src/config/chain/index.ts | 2 ++ src/config/chain/initia.ts | 50 ++++++++++++++++++++++++++++++++++++ src/config/chain/neutron.ts | 6 +++++ src/config/chain/osmosis.ts | 9 +++++++ src/config/chain/sei.ts | 6 +++++ src/config/chain/stargaze.ts | 6 +++++ src/config/chain/terra.ts | 6 +++++ src/config/chain/types.ts | 3 +++ 9 files changed, 91 insertions(+) create mode 100644 src/config/chain/initia.ts diff --git a/src/config/chain/default.ts b/src/config/chain/default.ts index f2e899988..adf7976a7 100644 --- a/src/config/chain/default.ts +++ b/src/config/chain/default.ts @@ -16,6 +16,9 @@ export const DEFAULT_CHAIN_CONFIG: ChainConfig = { wasm: { enabled: false, }, + move: { + enabled: false, + }, pool: { enabled: false, }, diff --git a/src/config/chain/index.ts b/src/config/chain/index.ts index 6299e2761..5e89f99db 100644 --- a/src/config/chain/index.ts +++ b/src/config/chain/index.ts @@ -1,3 +1,4 @@ +import { INITIA_CHAIN_CONFIGS } from "./initia"; import { NEUTRON_CHAIN_CONFIGS } from "./neutron"; import { OSMOSIS_CHAIN_CONFIGS } from "./osmosis"; import { SEI_CHAIN_CONFIGS } from "./sei"; @@ -14,4 +15,5 @@ export const CHAIN_CONFIGS: ChainConfigs = { ...NEUTRON_CHAIN_CONFIGS, ...STARGAZE_CHAIN_CONFIGS, ...TERRA_CHAIN_CONFIGS, + ...INITIA_CHAIN_CONFIGS, }; diff --git a/src/config/chain/initia.ts b/src/config/chain/initia.ts new file mode 100644 index 000000000..f4a440ce4 --- /dev/null +++ b/src/config/chain/initia.ts @@ -0,0 +1,50 @@ +import { wallets as keplrWallets } from "@cosmos-kit/keplr"; + +import type { ChainConfigs } from "./types"; + +export const INITIA_CHAIN_CONFIGS: ChainConfigs = { + "stone-9": { + chain: "initia", + registryChainName: "initia", + prettyName: "Initia Testnet", + // TODO change to initia + lcd: "https://lcd.osmosis.zone", + rpc: "https://rpc.osmosis.zone:443", + indexer: "https://osmosis-mainnet-graphql.alleslabs.dev/v1/graphql", + api: "https://celatone-api.alleslabs.dev", + wallets: [...keplrWallets], + features: { + faucet: { + enabled: false, + }, + wasm: { + enabled: false, + }, + move: { + enabled: true, + }, + pool: { + enabled: false, + }, + publicProject: { + enabled: false, + }, + gov: { + enabled: true, + }, + }, + gas: { + gasPrice: { + tokenPerGas: 0.025, + denom: "init", + }, + gasAdjustment: 1.5, + maxGasLimit: 25_000_000, + }, + explorerLink: { + validator: "https://app.initia.tech/validator", + proposal: "https://app.initia.tech/proposals", + }, + extra: {}, + }, +}; diff --git a/src/config/chain/neutron.ts b/src/config/chain/neutron.ts index 455cd693c..88937e6d5 100644 --- a/src/config/chain/neutron.ts +++ b/src/config/chain/neutron.ts @@ -21,6 +21,9 @@ export const NEUTRON_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: false, }, @@ -63,6 +66,9 @@ export const NEUTRON_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: false, }, diff --git a/src/config/chain/osmosis.ts b/src/config/chain/osmosis.ts index 457c258c2..76fb3a7ae 100644 --- a/src/config/chain/osmosis.ts +++ b/src/config/chain/osmosis.ts @@ -21,6 +21,9 @@ export const OSMOSIS_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: true, url: "https://app.osmosis.zone/pool", @@ -65,6 +68,9 @@ export const OSMOSIS_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: true, url: "https://testnet.osmosis.zone/pool", @@ -109,6 +115,9 @@ export const OSMOSIS_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: false, }, diff --git a/src/config/chain/sei.ts b/src/config/chain/sei.ts index 800985ca7..bce746362 100644 --- a/src/config/chain/sei.ts +++ b/src/config/chain/sei.ts @@ -27,6 +27,9 @@ export const SEI_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: false, }, @@ -71,6 +74,9 @@ export const SEI_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: false, }, diff --git a/src/config/chain/stargaze.ts b/src/config/chain/stargaze.ts index 189f55da7..fbfca617a 100644 --- a/src/config/chain/stargaze.ts +++ b/src/config/chain/stargaze.ts @@ -21,6 +21,9 @@ export const STARGAZE_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: false, }, @@ -63,6 +66,9 @@ export const STARGAZE_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: false, }, diff --git a/src/config/chain/terra.ts b/src/config/chain/terra.ts index 35d39106b..0a849d1dd 100644 --- a/src/config/chain/terra.ts +++ b/src/config/chain/terra.ts @@ -21,6 +21,9 @@ export const TERRA_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: false, }, @@ -64,6 +67,9 @@ export const TERRA_CHAIN_CONFIGS: ChainConfigs = { storeCodeMaxFileSize: 800_000, clearAdminGas: 50_000, }, + move: { + enabled: false, + }, pool: { enabled: false, }, diff --git a/src/config/chain/types.ts b/src/config/chain/types.ts index a2df8bade..2ae2e1009 100644 --- a/src/config/chain/types.ts +++ b/src/config/chain/types.ts @@ -17,6 +17,8 @@ type WasmConfig = enabled: false; }; +type MoveConfig = { enabled: boolean }; + type PoolConfig = | { enabled: true; @@ -53,6 +55,7 @@ export interface ChainConfig { features: { faucet: FaucetConfig; wasm: WasmConfig; + move: MoveConfig; pool: PoolConfig; publicProject: PublicProjectConfig; gov: GovConfig; From 4f39794c131f6a130ab7e5fbec8ad960189511d5 Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Mon, 21 Aug 2023 17:34:33 +0700 Subject: [PATCH 003/368] feat(components): adjust high level nav and sidebar --- src/config/chain/initia.ts | 4 +- src/lib/app-provider/hooks/useConfig.ts | 14 +++++ src/lib/layout/SubHeader.tsx | 60 ++++++++++++++------ src/lib/layout/navbar/index.tsx | 37 ++++++++++++ src/lib/pages/deploy-script/index.tsx | 14 +++++ src/lib/pages/interaction/index.tsx | 19 +++++++ src/lib/pages/modules/index.tsx | 14 +++++ src/lib/pages/publish-module/index.tsx | 14 +++++ src/pages/[network]/deploy-script/index.tsx | 3 + src/pages/[network]/interaction/[type].tsx | 3 + src/pages/[network]/modules/index.tsx | 3 + src/pages/[network]/publish-module/index.tsx | 3 + src/pages/deploy-script/index.tsx | 3 + src/pages/interaction/[type].tsx | 3 + src/pages/modules/index.tsx | 3 + src/pages/publish-module/index.tsx | 3 + 16 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 src/lib/pages/deploy-script/index.tsx create mode 100644 src/lib/pages/interaction/index.tsx create mode 100644 src/lib/pages/modules/index.tsx create mode 100644 src/lib/pages/publish-module/index.tsx create mode 100644 src/pages/[network]/deploy-script/index.tsx create mode 100644 src/pages/[network]/interaction/[type].tsx create mode 100644 src/pages/[network]/modules/index.tsx create mode 100644 src/pages/[network]/publish-module/index.tsx create mode 100644 src/pages/deploy-script/index.tsx create mode 100644 src/pages/interaction/[type].tsx create mode 100644 src/pages/modules/index.tsx create mode 100644 src/pages/publish-module/index.tsx diff --git a/src/config/chain/initia.ts b/src/config/chain/initia.ts index f4a440ce4..3a885c3c0 100644 --- a/src/config/chain/initia.ts +++ b/src/config/chain/initia.ts @@ -5,7 +5,7 @@ import type { ChainConfigs } from "./types"; export const INITIA_CHAIN_CONFIGS: ChainConfigs = { "stone-9": { chain: "initia", - registryChainName: "initia", + registryChainName: "osmosis", prettyName: "Initia Testnet", // TODO change to initia lcd: "https://lcd.osmosis.zone", @@ -27,7 +27,7 @@ export const INITIA_CHAIN_CONFIGS: ChainConfigs = { enabled: false, }, publicProject: { - enabled: false, + enabled: true, }, gov: { enabled: true, diff --git a/src/lib/app-provider/hooks/useConfig.ts b/src/lib/app-provider/hooks/useConfig.ts index 8fe3e31ed..5d73f2339 100644 --- a/src/lib/app-provider/hooks/useConfig.ts +++ b/src/lib/app-provider/hooks/useConfig.ts @@ -38,6 +38,20 @@ export const useWasmConfig = ({ return useBaseConfig({ feature: wasm, shouldRedirect }); }; +export const useMoveConfig = ({ + shouldRedirect, +}: { + shouldRedirect: boolean; +}) => { + const { + chainConfig: { + features: { move }, + }, + } = useCelatoneApp(); + + return useBaseConfig({ feature: move, shouldRedirect }); +}; + export const useFaucetConfig = ({ shouldRedirect, }: { diff --git a/src/lib/layout/SubHeader.tsx b/src/lib/layout/SubHeader.tsx index 13f919202..078dcc6f4 100644 --- a/src/lib/layout/SubHeader.tsx +++ b/src/lib/layout/SubHeader.tsx @@ -9,7 +9,12 @@ import { import type { CSSProperties, Dispatch, SetStateAction } from "react"; import { useEffect, useRef } from "react"; -import { usePoolConfig, useGovConfig, useWasmConfig } from "lib/app-provider"; +import { + usePoolConfig, + useGovConfig, + useWasmConfig, + useMoveConfig, +} from "lib/app-provider"; import { AppLink } from "lib/components/AppLink"; import type { IconKeys } from "lib/components/icon"; import { CustomIcon } from "lib/components/icon"; @@ -70,6 +75,7 @@ interface SubHeaderProps { setIsDevMode: Dispatch>>; setIsExpand: Dispatch>; } + const SubHeader = ({ isExpand, isDevMode, @@ -77,28 +83,46 @@ const SubHeader = ({ setIsExpand, }: SubHeaderProps) => { const wasmConfig = useWasmConfig({ shouldRedirect: false }); + const moveConfig = useMoveConfig({ shouldRedirect: false }); const poolConfig = usePoolConfig({ shouldRedirect: false }); const govConfig = useGovConfig({ shouldRedirect: false }); const prevIsDevModeRef = useRef(Boolean(isDevMode)); - const subHeaderMenu: SubHeaderMenuInfo[] = [ - { name: "Overview", slug: "/", icon: "home" }, - { name: "Transactions", slug: "/txs", icon: "file" }, - { name: "Blocks", slug: "/blocks", icon: "block" }, - ...(wasmConfig.enabled - ? ([ - { name: "Codes", slug: "/codes", icon: "code" }, - { name: "Contracts", slug: "/contracts", icon: "contract-address" }, - ] as const) - : []), - ...(govConfig.enabled - ? ([{ name: "Proposals", slug: "/proposals", icon: "proposal" }] as const) - : []), - ...(poolConfig.enabled - ? ([{ name: "Osmosis Pools", slug: "/pools", icon: "pool" }] as const) - : []), - ]; + let subHeaderMenu: SubHeaderMenuInfo[] = []; + if (moveConfig) + subHeaderMenu = [ + { name: "Overview", slug: "/", icon: "home" }, + { name: "Transactions", slug: "/txs", icon: "file" }, + { name: "Blocks", slug: "/blocks", icon: "block" }, + { name: "Modules", slug: "/modules", icon: "block" }, + ...(govConfig.enabled + ? ([ + { name: "Proposals", slug: "/proposals", icon: "proposal" }, + ] as const) + : []), + ]; + else + subHeaderMenu = [ + { name: "Overview", slug: "/", icon: "home" }, + { name: "Transactions", slug: "/txs", icon: "file" }, + { name: "Blocks", slug: "/blocks", icon: "block" }, + ...(wasmConfig.enabled + ? ([ + { name: "Codes", slug: "/codes", icon: "code" }, + { name: "Contracts", slug: "/contracts", icon: "contract-address" }, + ] as const) + : []), + ...(govConfig.enabled + ? ([ + { name: "Proposals", slug: "/proposals", icon: "proposal" }, + ] as const) + : []), + ...(poolConfig.enabled + ? ([{ name: "Osmosis Pools", slug: "/pools", icon: "pool" }] as const) + : []), + ]; + const isCurrentPage = useIsCurrentPage(); const activeColor = "primary.light"; diff --git a/src/lib/layout/navbar/index.tsx b/src/lib/layout/navbar/index.tsx index 61138553e..a7c4bcb68 100644 --- a/src/lib/layout/navbar/index.tsx +++ b/src/lib/layout/navbar/index.tsx @@ -5,6 +5,7 @@ import { usePublicProjectConfig, useCurrentChain, useWasmConfig, + useMoveConfig, } from "lib/app-provider"; import type { IconKeys } from "lib/components/icon"; import { INSTANTIATED_LIST_NAME, SAVED_LIST_NAME } from "lib/data"; @@ -28,6 +29,7 @@ const Navbar = ({ isExpand, isDevMode, setIsExpand }: NavbarProps) => { const publicProject = usePublicProjectConfig({ shouldRedirect: false }); const isCurrentPage = useIsCurrentPage(); const wasm = useWasmConfig({ shouldRedirect: false }); + const move = useMoveConfig({ shouldRedirect: false }); const { address } = useCurrentChain(); @@ -100,6 +102,41 @@ const Navbar = ({ isExpand, isDevMode, setIsExpand }: NavbarProps) => { }, ] : []), + ...(isDevMode && move.enabled + ? [ + { + category: "Quick Actions", + submenu: [ + // TODO change path to /account/0x1 + { + name: "0x1 Page", + slug: "/deploy", + icon: "home" as IconKeys, + }, + { + name: "Publish Module", + slug: "/publish-module", + icon: "add-new" as IconKeys, + }, + { + name: "View", + slug: "/interaction/view", + icon: "query" as IconKeys, + }, + { + name: "Execute", + slug: "/interaction/execute", + icon: "execute" as IconKeys, + }, + { + name: "Deploy Script", + slug: "/deploy-script", + icon: "code" as IconKeys, + }, + ], + }, + ] + : []), ...(wasm.enabled ? [ { diff --git a/src/lib/pages/deploy-script/index.tsx b/src/lib/pages/deploy-script/index.tsx new file mode 100644 index 000000000..09fb47f3a --- /dev/null +++ b/src/lib/pages/deploy-script/index.tsx @@ -0,0 +1,14 @@ +import { Flex, Text } from "@chakra-ui/react"; + +export const DeployScript = () => { + return ( + + + DeployScript: Lorem ipsum dolor sit amet consectetur adipisicing elit. + Aliquam voluptas hic aperiam ad sint unde sapiente cupiditate! Quo et + cupiditate iure, sequi deserunt tempora corrupti eum. Error facere + placeat repellendus? + + + ); +}; diff --git a/src/lib/pages/interaction/index.tsx b/src/lib/pages/interaction/index.tsx new file mode 100644 index 000000000..8d9702313 --- /dev/null +++ b/src/lib/pages/interaction/index.tsx @@ -0,0 +1,19 @@ +import { Flex, Text } from "@chakra-ui/react"; +import { useRouter } from "next/router"; + +export const Interaction = () => { + const { query } = useRouter(); + + return ( + +

{query.type}

+
+ + Interaction: Lorem ipsum dolor sit amet consectetur adipisicing elit. + Aliquam voluptas hic aperiam ad sint unde sapiente cupiditate! Quo et + cupiditate iure, sequi deserunt tempora corrupti eum. Error facere + placeat repellendus? + +
+ ); +}; diff --git a/src/lib/pages/modules/index.tsx b/src/lib/pages/modules/index.tsx new file mode 100644 index 000000000..9edcb9d5a --- /dev/null +++ b/src/lib/pages/modules/index.tsx @@ -0,0 +1,14 @@ +import { Flex, Text } from "@chakra-ui/react"; + +export const Modules = () => { + return ( + + + Modules: Lorem ipsum dolor sit amet consectetur adipisicing elit. + Aliquam voluptas hic aperiam ad sint unde sapiente cupiditate! Quo et + cupiditate iure, sequi deserunt tempora corrupti eum. Error facere + placeat repellendus? + + + ); +}; diff --git a/src/lib/pages/publish-module/index.tsx b/src/lib/pages/publish-module/index.tsx new file mode 100644 index 000000000..859e48c29 --- /dev/null +++ b/src/lib/pages/publish-module/index.tsx @@ -0,0 +1,14 @@ +import { Flex, Text } from "@chakra-ui/react"; + +export const PublishModule = () => { + return ( + + + PublishModule: Lorem ipsum dolor sit amet consectetur adipisicing elit. + Aliquam voluptas hic aperiam ad sint unde sapiente cupiditate! Quo et + cupiditate iure, sequi deserunt tempora corrupti eum. Error facere + placeat repellendus? + + + ); +}; diff --git a/src/pages/[network]/deploy-script/index.tsx b/src/pages/[network]/deploy-script/index.tsx new file mode 100644 index 000000000..b2b13842d --- /dev/null +++ b/src/pages/[network]/deploy-script/index.tsx @@ -0,0 +1,3 @@ +import DeployScript from "pages/deploy-script"; + +export default DeployScript; diff --git a/src/pages/[network]/interaction/[type].tsx b/src/pages/[network]/interaction/[type].tsx new file mode 100644 index 000000000..6812f4848 --- /dev/null +++ b/src/pages/[network]/interaction/[type].tsx @@ -0,0 +1,3 @@ +import { Interaction } from "lib/pages/interaction"; + +export default Interaction; diff --git a/src/pages/[network]/modules/index.tsx b/src/pages/[network]/modules/index.tsx new file mode 100644 index 000000000..9d66bb8ef --- /dev/null +++ b/src/pages/[network]/modules/index.tsx @@ -0,0 +1,3 @@ +import Modules from "pages/modules"; + +export default Modules; diff --git a/src/pages/[network]/publish-module/index.tsx b/src/pages/[network]/publish-module/index.tsx new file mode 100644 index 000000000..3e1ca0dbf --- /dev/null +++ b/src/pages/[network]/publish-module/index.tsx @@ -0,0 +1,3 @@ +import { PublishModule } from "lib/pages/publish-module"; + +export default PublishModule; diff --git a/src/pages/deploy-script/index.tsx b/src/pages/deploy-script/index.tsx new file mode 100644 index 000000000..0df4bda56 --- /dev/null +++ b/src/pages/deploy-script/index.tsx @@ -0,0 +1,3 @@ +import { DeployScript } from "lib/pages/deploy-script"; + +export default DeployScript; diff --git a/src/pages/interaction/[type].tsx b/src/pages/interaction/[type].tsx new file mode 100644 index 000000000..6812f4848 --- /dev/null +++ b/src/pages/interaction/[type].tsx @@ -0,0 +1,3 @@ +import { Interaction } from "lib/pages/interaction"; + +export default Interaction; diff --git a/src/pages/modules/index.tsx b/src/pages/modules/index.tsx new file mode 100644 index 000000000..49cccb88d --- /dev/null +++ b/src/pages/modules/index.tsx @@ -0,0 +1,3 @@ +import { Modules } from "lib/pages/modules"; + +export default Modules; diff --git a/src/pages/publish-module/index.tsx b/src/pages/publish-module/index.tsx new file mode 100644 index 000000000..3e1ca0dbf --- /dev/null +++ b/src/pages/publish-module/index.tsx @@ -0,0 +1,3 @@ +import { PublishModule } from "lib/pages/publish-module"; + +export default PublishModule; From 843339af622e9c30207ea5dcfd69cb8399c23179 Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Mon, 21 Aug 2023 17:35:53 +0700 Subject: [PATCH 004/368] feat(components): add change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f87592d9e..ebdbd8628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#488](https://github.com/alleslabs/celatone-frontend/pull/488) Add initia navigation and sidebar - [#481](https://github.com/alleslabs/celatone-frontend/pull/481) Support Stargaze testnet - [#471](https://github.com/alleslabs/celatone-frontend/pull/471) Add proposal config, use Hasura admin secret - [#467](https://github.com/alleslabs/celatone-frontend/pull/467) Dynamic wallet by network, add disableAnyofAddress config From 7e790c0d155dc8bc7fe8b3993c05f959011329fe Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Mon, 21 Aug 2023 17:37:50 +0700 Subject: [PATCH 005/368] fix(components): change module icon --- src/lib/layout/SubHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/layout/SubHeader.tsx b/src/lib/layout/SubHeader.tsx index 078dcc6f4..a9e2b3cac 100644 --- a/src/lib/layout/SubHeader.tsx +++ b/src/lib/layout/SubHeader.tsx @@ -95,7 +95,7 @@ const SubHeader = ({ { name: "Overview", slug: "/", icon: "home" }, { name: "Transactions", slug: "/txs", icon: "file" }, { name: "Blocks", slug: "/blocks", icon: "block" }, - { name: "Modules", slug: "/modules", icon: "block" }, + { name: "Modules", slug: "/modules", icon: "contract-address" }, ...(govConfig.enabled ? ([ { name: "Proposals", slug: "/proposals", icon: "proposal" }, From f4b63acfbf07a3282bce2b53ad8400f1142c4dba Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Tue, 22 Aug 2023 10:18:19 +0700 Subject: [PATCH 006/368] fix(components): revise sub header structure --- src/lib/layout/SubHeader.tsx | 61 +++++++++++++++++------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/src/lib/layout/SubHeader.tsx b/src/lib/layout/SubHeader.tsx index a9e2b3cac..d70be01c2 100644 --- a/src/lib/layout/SubHeader.tsx +++ b/src/lib/layout/SubHeader.tsx @@ -89,39 +89,34 @@ const SubHeader = ({ const prevIsDevModeRef = useRef(Boolean(isDevMode)); - let subHeaderMenu: SubHeaderMenuInfo[] = []; - if (moveConfig) - subHeaderMenu = [ - { name: "Overview", slug: "/", icon: "home" }, - { name: "Transactions", slug: "/txs", icon: "file" }, - { name: "Blocks", slug: "/blocks", icon: "block" }, - { name: "Modules", slug: "/modules", icon: "contract-address" }, - ...(govConfig.enabled - ? ([ - { name: "Proposals", slug: "/proposals", icon: "proposal" }, - ] as const) - : []), - ]; - else - subHeaderMenu = [ - { name: "Overview", slug: "/", icon: "home" }, - { name: "Transactions", slug: "/txs", icon: "file" }, - { name: "Blocks", slug: "/blocks", icon: "block" }, - ...(wasmConfig.enabled - ? ([ - { name: "Codes", slug: "/codes", icon: "code" }, - { name: "Contracts", slug: "/contracts", icon: "contract-address" }, - ] as const) - : []), - ...(govConfig.enabled - ? ([ - { name: "Proposals", slug: "/proposals", icon: "proposal" }, - ] as const) - : []), - ...(poolConfig.enabled - ? ([{ name: "Osmosis Pools", slug: "/pools", icon: "pool" }] as const) - : []), - ]; + const subHeaderMenu: SubHeaderMenuInfo[] = [ + { name: "Overview", slug: "/", icon: "home" }, + { name: "Transactions", slug: "/txs", icon: "file" }, + { name: "Blocks", slug: "/blocks", icon: "block" }, + ]; + + if (moveConfig.enabled) + subHeaderMenu.push({ + name: "Modules", + slug: "/modules", + icon: "contract-address", + }); + + if (wasmConfig.enabled) + subHeaderMenu.push( + { name: "Codes", slug: "/codes", icon: "code" }, + { name: "Contracts", slug: "/contracts", icon: "contract-address" } + ); + + if (govConfig.enabled) + subHeaderMenu.push({ + name: "Proposals", + slug: "/proposals", + icon: "proposal", + }); + + if (poolConfig.enabled) + subHeaderMenu.push({ name: "Osmosis Pools", slug: "/pools", icon: "pool" }); const isCurrentPage = useIsCurrentPage(); From 6c37c55dd218d1928ebeb43d89cf7462431af598 Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Tue, 22 Aug 2023 12:10:44 +0700 Subject: [PATCH 007/368] feat(components): add module interaction page empty state ui --- CHANGELOG.md | 1 + src/config/theme/initia.ts | 18 ++-- src/config/theme/sei.ts | 2 + src/config/theme/types.ts | 2 + src/lib/components/MotionBox.tsx | 7 ++ src/lib/components/state/StateImage.tsx | 8 +- .../component/InteractionTypeSwitch.tsx | 82 ++++++++++++++++ src/lib/pages/interaction/index.tsx | 98 ++++++++++++++++--- src/lib/styles/theme/components/button.ts | 4 +- src/pages/[network]/interaction/index.tsx | 3 + src/pages/interaction/index.tsx | 3 + 11 files changed, 204 insertions(+), 24 deletions(-) create mode 100644 src/lib/components/MotionBox.tsx create mode 100644 src/lib/pages/interaction/component/InteractionTypeSwitch.tsx create mode 100644 src/pages/[network]/interaction/index.tsx create mode 100644 src/pages/interaction/index.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index ebdbd8628..b76f9e96e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#490](https://github.com/alleslabs/celatone-frontend/pull/490) Add initia module interaction page - [#488](https://github.com/alleslabs/celatone-frontend/pull/488) Add initia navigation and sidebar - [#481](https://github.com/alleslabs/celatone-frontend/pull/481) Support Stargaze testnet - [#471](https://github.com/alleslabs/celatone-frontend/pull/471) Add proposal config, use Hasura admin secret diff --git a/src/config/theme/initia.ts b/src/config/theme/initia.ts index e3e408e9f..01fa690c5 100644 --- a/src/config/theme/initia.ts +++ b/src/config/theme/initia.ts @@ -100,14 +100,16 @@ export const INITIA_THEME: ThemeConfig = { color: "inherit", }, }, - // button: { - // primary: { - // background: "#F5F5F5", - // color: "#151617", - // disabledBackground: "#A1A6AA", - // disabledColor: "#151617", - // }, - // }, + button: { + primary: { + background: "#F5F5F5", + color: "#151617", + disabledBackground: "#A1A6AA", + disabledColor: "#151617", + hoverBackground: "#B7B7B7", + activeBackground: "#B7B7B7", + }, + }, borderRadius: { default: "4px", iconButton: "36px", diff --git a/src/config/theme/sei.ts b/src/config/theme/sei.ts index b299570ae..2af9f694e 100644 --- a/src/config/theme/sei.ts +++ b/src/config/theme/sei.ts @@ -106,6 +106,8 @@ export const SEI_THEME: ThemeConfig = { color: "#0C1C23", disabledBackground: "#787971", disabledColor: "#0C1C23", + hoverBackground: "FAF6EF", + activeBackground: "FAF6EF", }, outlinePrimary: { borderColor: "#787971", diff --git a/src/config/theme/types.ts b/src/config/theme/types.ts index c5c4682cc..e5780adba 100644 --- a/src/config/theme/types.ts +++ b/src/config/theme/types.ts @@ -103,6 +103,8 @@ export type ThemeConfig = { color: string; disabledBackground: string; disabledColor: string; + hoverBackground: string; + activeBackground: string; }; outlinePrimary?: { borderColor: string; diff --git a/src/lib/components/MotionBox.tsx b/src/lib/components/MotionBox.tsx new file mode 100644 index 000000000..1477da9c8 --- /dev/null +++ b/src/lib/components/MotionBox.tsx @@ -0,0 +1,7 @@ +import { chakra, shouldForwardProp } from "@chakra-ui/react"; +import { motion, isValidMotionProp } from "framer-motion"; + +export const MotionBox = chakra(motion.div, { + shouldForwardProp: (prop) => + isValidMotionProp(prop) || shouldForwardProp(prop), +}); diff --git a/src/lib/components/state/StateImage.tsx b/src/lib/components/state/StateImage.tsx index 182f79d78..6095edcec 100644 --- a/src/lib/components/state/StateImage.tsx +++ b/src/lib/components/state/StateImage.tsx @@ -12,12 +12,16 @@ const imageSourceMap: Record = { interface StateImageProps { imageVariant: ImageVariant; + width?: string; } -export const StateImage = ({ imageVariant }: StateImageProps) => ( +export const StateImage = ({ + imageVariant, + width = "200px", +}: StateImageProps) => ( result not found ); diff --git a/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx b/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx new file mode 100644 index 000000000..06301cc57 --- /dev/null +++ b/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx @@ -0,0 +1,82 @@ +import { Flex, Heading } from "@chakra-ui/react"; +import type { Dispatch, SetStateAction } from "react"; +import { useRef } from "react"; + +import { MotionBox } from "lib/components/MotionBox"; + +export enum MessageTabs { + VIEW_MODULE = "View", + EXECUTE_MODULE = "Execute", +} + +export const viewModuleFormKey = MessageTabs.VIEW_MODULE as "View"; +export const executeModuleFormKey = MessageTabs.EXECUTE_MODULE as "Execute"; + +interface InteractionTypeSwitchProps { + currentTab: MessageTabs; + disabled?: boolean; + onTabChange: Dispatch>; +} + +const tabs = Object.values(MessageTabs); + +export const InteractionTypeSwitch = ({ + currentTab, + disabled = false, + onTabChange, +}: InteractionTypeSwitchProps) => { + const tabRefs = useRef<(HTMLDivElement | null)[]>([]); + const activeIndex = tabs.indexOf(currentTab); + + return ( + + {tabs.map((tab, idx) => ( + { + tabRefs.current[idx] = el; + }} + cursor="pointer" + p="2px 10px" + variants={{ + active: { color: "var(--chakra-colors-text-main)" }, + inactive: { + color: "var(--chakra-colors-primary-light)", + }, + }} + initial="inactive" + animate={currentTab === tab ? "active" : "inactive"} + onClick={() => onTabChange(tab)} + zIndex={1} + textAlign="center" + > + + {tab} + + + ))} + + + ); +}; diff --git a/src/lib/pages/interaction/index.tsx b/src/lib/pages/interaction/index.tsx index 8d9702313..f90bc4994 100644 --- a/src/lib/pages/interaction/index.tsx +++ b/src/lib/pages/interaction/index.tsx @@ -1,19 +1,93 @@ -import { Flex, Text } from "@chakra-ui/react"; +import { Badge, Button, Flex, Heading, Text, Box } from "@chakra-ui/react"; import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +import PageContainer from "lib/components/PageContainer"; +import { StateImage } from "lib/components/state"; + +import { + InteractionTypeSwitch, + MessageTabs, +} from "./component/InteractionTypeSwitch"; export const Interaction = () => { - const { query } = useRouter(); + const { query, isReady } = useRouter(); + const [tab, setTab] = useState(MessageTabs.VIEW_MODULE); + + useEffect(() => { + if (isReady) + setTab( + query.type === "execute" + ? MessageTabs.EXECUTE_MODULE + : MessageTabs.VIEW_MODULE + ); + }, [isReady, query.type]); return ( - -

{query.type}

-
- - Interaction: Lorem ipsum dolor sit amet consectetur adipisicing elit. - Aliquam voluptas hic aperiam ad sint unde sapiente cupiditate! Quo et - cupiditate iure, sequi deserunt tempora corrupti eum. Error facere - placeat repellendus? - -
+ + + Module Interactions + + + Select module to interact with ... + + + + + + + Available functions + + + 0 + + + + + + + + + Available functions for selected modules will display here + + + + + + + Initiate your Module interactions by choosing a module and its + associated function. +
This section will showcase the input or response type + required for the functions. +
+ +
+
+
); }; diff --git a/src/lib/styles/theme/components/button.ts b/src/lib/styles/theme/components/button.ts index 818a55058..fb73c9328 100644 --- a/src/lib/styles/theme/components/button.ts +++ b/src/lib/styles/theme/components/button.ts @@ -88,8 +88,8 @@ export const Button: ComponentStyleConfig = { color: CURR_THEME.button.primary.disabledColor, }, }, - hoverBg: primaryDark, - activeBg: primaryLight, + hoverBg: CURR_THEME.button.primary.hoverBackground, + activeBg: CURR_THEME.button.primary.activeBackground, }) : generateStyle({ basic: { diff --git a/src/pages/[network]/interaction/index.tsx b/src/pages/[network]/interaction/index.tsx new file mode 100644 index 000000000..6812f4848 --- /dev/null +++ b/src/pages/[network]/interaction/index.tsx @@ -0,0 +1,3 @@ +import { Interaction } from "lib/pages/interaction"; + +export default Interaction; diff --git a/src/pages/interaction/index.tsx b/src/pages/interaction/index.tsx new file mode 100644 index 000000000..6812f4848 --- /dev/null +++ b/src/pages/interaction/index.tsx @@ -0,0 +1,3 @@ +import { Interaction } from "lib/pages/interaction"; + +export default Interaction; From 7b593ead2dc2e690f01d215d80ea5c7d7e19945f Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Tue, 22 Aug 2023 12:30:55 +0700 Subject: [PATCH 008/368] fix(components): add s tag to badge --- src/lib/pages/interaction/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/pages/interaction/index.tsx b/src/lib/pages/interaction/index.tsx index f90bc4994..aaea42f92 100644 --- a/src/lib/pages/interaction/index.tsx +++ b/src/lib/pages/interaction/index.tsx @@ -46,7 +46,7 @@ export const Interaction = () => { Available functions - 0 + 0 From 42b3412790972c597dd86e742fd14b1bf757b90c Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Tue, 22 Aug 2023 14:29:28 +0700 Subject: [PATCH 009/368] feat(components): select module drawer --- .../component/ModuleSelectDrawerButton.tsx | 129 ++++++++++++++++++ src/lib/pages/interaction/index.tsx | 3 +- src/lib/styles/theme/components/button.ts | 22 +++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx diff --git a/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx b/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx new file mode 100644 index 000000000..668f9faee --- /dev/null +++ b/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx @@ -0,0 +1,129 @@ +import { + Button, + Text, + Heading, + useDisclosure, + Drawer, + DrawerOverlay, + DrawerContent, + DrawerHeader, + DrawerCloseButton, + DrawerBody, + Flex, +} from "@chakra-ui/react"; + +import { ExplorerLink } from "lib/components/ExplorerLink"; +import { CustomIcon } from "lib/components/icon"; +import { LabelText } from "lib/components/LabelText"; +import { StateImage } from "lib/components/state"; + +interface ModuleSelectDrawerButtonProps { + buttonText?: string; +} + +export const ModuleSelectDrawerButton = ({ + buttonText = "Select Module", +}: ModuleSelectDrawerButtonProps) => { + const { isOpen, onClose, onOpen } = useDisclosure(); + return ( + <> + + + + + + + + Select Module + + + + + + {/* Input */} + {/* + TODO Input here + + + + + */} + {/* Selected Address */} + + + + + + + + + + + + {/* Empty State */} + + + + Available functions for selected modules will display here + + + + + + + + ); +}; diff --git a/src/lib/pages/interaction/index.tsx b/src/lib/pages/interaction/index.tsx index aaea42f92..b960ee234 100644 --- a/src/lib/pages/interaction/index.tsx +++ b/src/lib/pages/interaction/index.tsx @@ -9,6 +9,7 @@ import { InteractionTypeSwitch, MessageTabs, } from "./component/InteractionTypeSwitch"; +import { ModuleSelectDrawerButton } from "./component/ModuleSelectDrawerButton"; export const Interaction = () => { const { query, isReady } = useRouter(); @@ -37,7 +38,7 @@ export const Interaction = () => { my={8} > Select module to interact with ... - + diff --git a/src/lib/styles/theme/components/button.ts b/src/lib/styles/theme/components/button.ts index fb73c9328..596c22a6b 100644 --- a/src/lib/styles/theme/components/button.ts +++ b/src/lib/styles/theme/components/button.ts @@ -3,6 +3,8 @@ import { defineStyle } from "@chakra-ui/react"; import { CURR_THEME } from "env"; +const gray100 = "gray.100"; +const gray400 = "gray.400"; const gray500 = "gray.500"; const gray600 = "gray.600"; const gray700 = "gray.700"; @@ -197,6 +199,26 @@ export const Button: ComponentStyleConfig = { hoverBg: gray700, activeBg: "transparent", }), + "outline-white": generateStyle({ + basic: { + border: borderDefualt, + borderColor: gray100, + color: "text.main", + "> svg": { + color: "text.main", + }, + }, + disabled: { + border: borderDefualt, + borderColor: gray500, + color: gray500, + "> svg": { + color: gray500, + }, + }, + hoverBg: gray400, + activeBg: "transparent", + }), "outline-accent": generateStyle({ basic: { border: borderDefualt, From a68715cd504fbe0dfbfee918a4925d0f7b387713 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Tue, 22 Aug 2023 14:57:05 +0700 Subject: [PATCH 010/368] fix: correctly display interaction switch highlight on first land --- .../pages/interaction/component/InteractionTypeSwitch.tsx | 7 ++++--- src/lib/pages/interaction/index.tsx | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx b/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx index 06301cc57..0c13a02e7 100644 --- a/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx +++ b/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx @@ -3,6 +3,7 @@ import type { Dispatch, SetStateAction } from "react"; import { useRef } from "react"; import { MotionBox } from "lib/components/MotionBox"; +import type { Option } from "lib/types"; export enum MessageTabs { VIEW_MODULE = "View", @@ -13,9 +14,9 @@ export const viewModuleFormKey = MessageTabs.VIEW_MODULE as "View"; export const executeModuleFormKey = MessageTabs.EXECUTE_MODULE as "Execute"; interface InteractionTypeSwitchProps { - currentTab: MessageTabs; + currentTab: Option; disabled?: boolean; - onTabChange: Dispatch>; + onTabChange: Dispatch>>; } const tabs = Object.values(MessageTabs); @@ -26,7 +27,7 @@ export const InteractionTypeSwitch = ({ onTabChange, }: InteractionTypeSwitchProps) => { const tabRefs = useRef<(HTMLDivElement | null)[]>([]); - const activeIndex = tabs.indexOf(currentTab); + const activeIndex = currentTab ? tabs.indexOf(currentTab) : -1; return ( { const { query, isReady } = useRouter(); - const [tab, setTab] = useState(MessageTabs.VIEW_MODULE); + const [tab, setTab] = useState(); useEffect(() => { - if (isReady) + if (isReady) { setTab( query.type === "execute" ? MessageTabs.EXECUTE_MODULE : MessageTabs.VIEW_MODULE ); + } }, [isReady, query.type]); return ( From 08310e6e6d724c0da596a679a3e15c58cad0cae8 Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Tue, 22 Aug 2023 18:25:25 +0700 Subject: [PATCH 011/368] feat(components): add module select drawer --- CHANGELOG.md | 1 + public/PilatWide-Bold.woff2 | Bin 0 -> 23940 bytes src/config/theme/initia.ts | 2 +- src/lib/components/icon/CustomIcon.tsx | 29 +++++ src/lib/components/module/FunctionCard.tsx | 78 +++++++++++ src/lib/components/module/ModuleCard.tsx | 43 +++++++ .../component/ModuleSelectDrawerButton.tsx | 87 ++----------- .../select-module/ModuleEmptyState.tsx | 31 +++++ .../select-module/ModuleSelectBody.tsx | 121 ++++++++++++++++++ .../component/select-module/ModuleSeletor.tsx | 64 +++++++++ src/lib/pages/interaction/index.tsx | 2 +- 11 files changed, 382 insertions(+), 76 deletions(-) create mode 100644 public/PilatWide-Bold.woff2 create mode 100644 src/lib/components/module/FunctionCard.tsx create mode 100644 src/lib/components/module/ModuleCard.tsx create mode 100644 src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx create mode 100644 src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx create mode 100644 src/lib/pages/interaction/component/select-module/ModuleSeletor.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index b76f9e96e..758dc34ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#494](https://github.com/alleslabs/celatone-frontend/pull/494) Initia select module drawer UI - [#490](https://github.com/alleslabs/celatone-frontend/pull/490) Add initia module interaction page - [#488](https://github.com/alleslabs/celatone-frontend/pull/488) Add initia navigation and sidebar - [#481](https://github.com/alleslabs/celatone-frontend/pull/481) Support Stargaze testnet diff --git a/public/PilatWide-Bold.woff2 b/public/PilatWide-Bold.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..d91e8f17ed077e5bd0208b11ae5ee12d56756c97 GIT binary patch literal 23940 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 + + + + + + ), + viewBox: "2 2 20 20", + }, github: { svg: ( { + switch (visibility) { + case "friends": + return ; + case "script": + return ; + case "public": + default: + return ; + } +}; +export const FunctionCard = ({ + isView = true, + disabled = false, + visibility = "public", +}: FunctionCardProps) => { + return ( + + + + + + {isView ? "View" : "Execute"} + + + + + + + + + + + {getIcon(visibility)} + + {visibility} + + + + + Function Name + + ); +}; diff --git a/src/lib/components/module/ModuleCard.tsx b/src/lib/components/module/ModuleCard.tsx new file mode 100644 index 000000000..2d4dbb5c1 --- /dev/null +++ b/src/lib/components/module/ModuleCard.tsx @@ -0,0 +1,43 @@ +import { Flex, Badge } from "@chakra-ui/react"; + +import { CustomIcon } from "../icon"; + +export const ModuleCard = () => { + return ( + + + + Module name + + + + + 0 + + + 0 + + + + ); +}; diff --git a/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx b/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx index 668f9faee..b5c5eab7b 100644 --- a/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx +++ b/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx @@ -1,6 +1,5 @@ import { Button, - Text, Heading, useDisclosure, Drawer, @@ -9,13 +8,16 @@ import { DrawerHeader, DrawerCloseButton, DrawerBody, - Flex, } from "@chakra-ui/react"; -import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; -import { LabelText } from "lib/components/LabelText"; -import { StateImage } from "lib/components/state"; + +import { ModuleSelectBody } from "./select-module/ModuleSelectBody"; +import { + ModuleSelectorDisplay, + ModuleSelectorInput, +} from "./select-module/ModuleSeletor"; +// import { ModuleEmptyState } from "./select-module/ModuleEmptyState"; interface ModuleSelectDrawerButtonProps { buttonText?: string; @@ -52,75 +54,12 @@ export const ModuleSelectDrawerButton = ({ display="flex" flexDirection="column" > - - {/* Input */} - {/* - TODO Input here - - - - - */} - {/* Selected Address */} - - - - - - - - - - - - {/* Empty State */} - - - - Available functions for selected modules will display here - - - + {/* Input */} + + {/* Selected Address */} + + + {/* */} diff --git a/src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx b/src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx new file mode 100644 index 000000000..99356b485 --- /dev/null +++ b/src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx @@ -0,0 +1,31 @@ +import { Text, Flex } from "@chakra-ui/react"; + +import { StateImage } from "lib/components/state"; + +interface ModuleEmptyStateProps { + description?: string; + imageWidth?: string; +} +export const ModuleEmptyState = ({ + description = "Available functions for selected modules will display here", + imageWidth = "160px", +}: ModuleEmptyStateProps) => { + return ( + + + + + {description} + + + + ); +}; diff --git a/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx b/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx new file mode 100644 index 000000000..462b341a6 --- /dev/null +++ b/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx @@ -0,0 +1,121 @@ +import { + Text, + Flex, + Badge, + Skeleton, + Box, + Button, + Heading, +} from "@chakra-ui/react"; + +import { FunctionCard } from "lib/components/module/FunctionCard"; +import { ModuleCard } from "lib/components/module/ModuleCard"; +// import { ModuleEmptyState } from "./ModuleEmptyState"; + +export const ModuleSelectBody = () => { + return ( + + + {/* Left */} + + input here + + + + Modules + + + 0 + + + + + + + {/* Right */} + {/* + + */} + + + Module name goes here + + input here + + + + + View Functions + + + 0 + + + + + + + + + + Execute Functions + + + 0 + + + + + + + + + + + + + + ); +}; diff --git a/src/lib/pages/interaction/component/select-module/ModuleSeletor.tsx b/src/lib/pages/interaction/component/select-module/ModuleSeletor.tsx new file mode 100644 index 000000000..9d9d84c19 --- /dev/null +++ b/src/lib/pages/interaction/component/select-module/ModuleSeletor.tsx @@ -0,0 +1,64 @@ +import { Button, Flex } from "@chakra-ui/react"; + +import { CopyLink } from "lib/components/CopyLink"; +import { CustomIcon } from "lib/components/icon"; +import { LabelText } from "lib/components/LabelText"; + +export const ModuleSelectorInput = () => { + return ( + + + input here + + + + + + + ); +}; + +export const ModuleSelectorDisplay = () => { + return ( + + + + + + + + + + ); +}; diff --git a/src/lib/pages/interaction/index.tsx b/src/lib/pages/interaction/index.tsx index b960ee234..4f7864151 100644 --- a/src/lib/pages/interaction/index.tsx +++ b/src/lib/pages/interaction/index.tsx @@ -46,7 +46,7 @@ export const Interaction = () => { Available functions - + 0 From 16ebb54e21f771b1319d275965122cd26eccd7cf Mon Sep 17 00:00:00 2001 From: evilpeach Date: Mon, 28 Aug 2023 10:37:44 +0700 Subject: [PATCH 012/368] fix: as comments --- .../pages/interaction/component/InteractionTypeSwitch.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx b/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx index 0c13a02e7..c87293b6e 100644 --- a/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx +++ b/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx @@ -10,9 +10,6 @@ export enum MessageTabs { EXECUTE_MODULE = "Execute", } -export const viewModuleFormKey = MessageTabs.VIEW_MODULE as "View"; -export const executeModuleFormKey = MessageTabs.EXECUTE_MODULE as "Execute"; - interface InteractionTypeSwitchProps { currentTab: Option; disabled?: boolean; @@ -27,7 +24,7 @@ export const InteractionTypeSwitch = ({ onTabChange, }: InteractionTypeSwitchProps) => { const tabRefs = useRef<(HTMLDivElement | null)[]>([]); - const activeIndex = currentTab ? tabs.indexOf(currentTab) : -1; + const activeIndex = currentTab ? tabs.indexOf(currentTab) : 0; return ( Date: Tue, 29 Aug 2023 15:19:31 +0700 Subject: [PATCH 013/368] refactor: delete unnecessary flex and refactor components --- src/lib/components/DotSeparator.tsx | 9 +- src/lib/components/module/CountBadge.tsx | 19 ++++ src/lib/components/module/FunctionCard.tsx | 27 ++++-- src/lib/components/module/ModuleCard.tsx | 22 ++--- src/lib/components/state/EmptyState.tsx | 16 +++- src/lib/components/state/StateImage.tsx | 7 +- .../component/InteractionTypeSwitch.tsx | 19 ++-- .../component/ModuleSelectDrawerButton.tsx | 17 +--- .../select-module/ModuleEmptyState.tsx | 2 +- .../select-module/ModuleSelectBody.tsx | 88 ++++++------------- .../{ModuleSeletor.tsx => ModuleSelector.tsx} | 11 ++- src/lib/pages/interaction/index.tsx | 82 ++++++++--------- 12 files changed, 154 insertions(+), 165 deletions(-) create mode 100644 src/lib/components/module/CountBadge.tsx rename src/lib/pages/interaction/component/select-module/{ModuleSeletor.tsx => ModuleSelector.tsx} (85%) diff --git a/src/lib/components/DotSeparator.tsx b/src/lib/components/DotSeparator.tsx index 0ffeb9d48..9c04acdf1 100644 --- a/src/lib/components/DotSeparator.tsx +++ b/src/lib/components/DotSeparator.tsx @@ -1,5 +1,8 @@ +import type { BoxProps } from "@chakra-ui/react"; import { Box } from "@chakra-ui/react"; -export const DotSeparator = () => ( - -); +export const DotSeparator = ({ + bg = "secondary.darker", +}: { + bg?: BoxProps["bg"]; +}) => ; diff --git a/src/lib/components/module/CountBadge.tsx b/src/lib/components/module/CountBadge.tsx new file mode 100644 index 000000000..2df5935d8 --- /dev/null +++ b/src/lib/components/module/CountBadge.tsx @@ -0,0 +1,19 @@ +import { Badge } from "@chakra-ui/react"; + +interface CountBadgeProps { + count: number; + variant: "view" | "execute" | "common"; +} +export const CountBadge = ({ count, variant }: CountBadgeProps) => ( + + {count} + +); diff --git a/src/lib/components/module/FunctionCard.tsx b/src/lib/components/module/FunctionCard.tsx index 3843710c9..d670203d6 100644 --- a/src/lib/components/module/FunctionCard.tsx +++ b/src/lib/components/module/FunctionCard.tsx @@ -1,5 +1,7 @@ +import type { FlexProps } from "@chakra-ui/react"; import { Flex, Text } from "@chakra-ui/react"; +import { DotSeparator } from "../DotSeparator"; import { CustomIcon } from "../icon"; import { Tooltip } from "lib/components/Tooltip"; @@ -20,6 +22,22 @@ const getIcon = (visibility: FunctionCardProps["visibility"]) => { return ; } }; + +const disabledStyle: { [key in `${boolean}`]: FlexProps } = { + true: { + bgColor: "gray.900", + _hover: { bg: "gray.900" }, + cursor: "not-allowed", + borderColor: "gray.700", + }, + false: { + bgColor: "gray.800", + _hover: { bg: "gray.700" }, + cursor: "pointer", + borderColor: "transparent", + }, +}; + export const FunctionCard = ({ isView = true, disabled = false, @@ -28,16 +46,13 @@ export const FunctionCard = ({ return ( @@ -55,11 +70,11 @@ export const FunctionCard = ({ bg="primary.dark" label="Only functions with “is_entry: true” and “visibility: public” are able to interacted through Celatone’s module interactions." > - + - + {getIcon(visibility)} { return ( { /> - - 0 - - - 0 - + + ); diff --git a/src/lib/components/state/EmptyState.tsx b/src/lib/components/state/EmptyState.tsx index 854c53730..a0d923eaf 100644 --- a/src/lib/components/state/EmptyState.tsx +++ b/src/lib/components/state/EmptyState.tsx @@ -1,3 +1,4 @@ +import type { ImageProps, TextProps } from "@chakra-ui/react"; import { Flex, Heading, Text } from "@chakra-ui/react"; import type { ReactElement } from "react"; @@ -6,18 +7,22 @@ import { StateImage } from "./StateImage"; export interface EmptyStateProps { imageVariant?: ImageVariant; + imageWidth?: ImageProps["width"]; message: string | ReactElement; heading?: string; withBorder?: boolean; my?: number; + textVariant?: TextProps["variant"]; } export const EmptyState = ({ message, imageVariant, + imageWidth, heading, withBorder = false, my = 12, + textVariant = "body1", }: EmptyStateProps) => ( - {imageVariant && } + {imageVariant && ( + + )} {heading && ( {heading} )} - + {message} diff --git a/src/lib/components/state/StateImage.tsx b/src/lib/components/state/StateImage.tsx index 6095edcec..29970d299 100644 --- a/src/lib/components/state/StateImage.tsx +++ b/src/lib/components/state/StateImage.tsx @@ -1,3 +1,4 @@ +import type { ImageProps } from "@chakra-ui/react"; import { Image } from "@chakra-ui/react"; import { CURR_THEME } from "env"; @@ -12,16 +13,16 @@ const imageSourceMap: Record = { interface StateImageProps { imageVariant: ImageVariant; - width?: string; + imageWidth?: ImageProps["width"]; } export const StateImage = ({ imageVariant, - width = "200px", + imageWidth = "200px", }: StateImageProps) => ( result not found ); diff --git a/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx b/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx index 06301cc57..ad7161c58 100644 --- a/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx +++ b/src/lib/pages/interaction/component/InteractionTypeSwitch.tsx @@ -1,32 +1,32 @@ +import type { FlexProps } from "@chakra-ui/react"; import { Flex, Heading } from "@chakra-ui/react"; import type { Dispatch, SetStateAction } from "react"; import { useRef } from "react"; import { MotionBox } from "lib/components/MotionBox"; +import type { Option } from "lib/types"; -export enum MessageTabs { +export enum InteractionTabs { VIEW_MODULE = "View", EXECUTE_MODULE = "Execute", } -export const viewModuleFormKey = MessageTabs.VIEW_MODULE as "View"; -export const executeModuleFormKey = MessageTabs.EXECUTE_MODULE as "Execute"; - -interface InteractionTypeSwitchProps { - currentTab: MessageTabs; +interface InteractionTypeSwitchProps extends FlexProps { + currentTab: Option; disabled?: boolean; - onTabChange: Dispatch>; + onTabChange: Dispatch>>; } -const tabs = Object.values(MessageTabs); +const tabs = Object.values(InteractionTabs); export const InteractionTypeSwitch = ({ currentTab, disabled = false, onTabChange, + ...flexProps }: InteractionTypeSwitchProps) => { const tabRefs = useRef<(HTMLDivElement | null)[]>([]); - const activeIndex = tabs.indexOf(currentTab); + const activeIndex = currentTab ? tabs.indexOf(currentTab) : 0; return ( {tabs.map((tab, idx) => ( - @@ -48,12 +42,7 @@ export const ModuleSelectDrawerButton = ({ - + {/* Input */} {/* Selected Address */} diff --git a/src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx b/src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx index 99356b485..a408d333b 100644 --- a/src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx +++ b/src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx @@ -21,7 +21,7 @@ export const ModuleEmptyState = ({ borderColor="gray.700" > - + {description} diff --git a/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx b/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx index 462b341a6..5d305e46c 100644 --- a/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx +++ b/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx @@ -1,25 +1,29 @@ -import { - Text, - Flex, - Badge, - Skeleton, - Box, - Button, - Heading, -} from "@chakra-ui/react"; +import type { FlexProps } from "@chakra-ui/react"; +import { Text, Flex, Badge, Skeleton, Button, Heading } from "@chakra-ui/react"; +import { CountBadge } from "lib/components/module/CountBadge"; import { FunctionCard } from "lib/components/module/FunctionCard"; import { ModuleCard } from "lib/components/module/ModuleCard"; // import { ModuleEmptyState } from "./ModuleEmptyState"; +const functionSelectBaseStyle: FlexProps = { + border: "1px solid", + borderRadius: 8, + borderColor: "gray.700", + p: 4, + direction: "column", +}; + export const ModuleSelectBody = () => { return ( - - + <> + {/* Left */} - - input here - + + + input here + + Modules @@ -44,67 +48,29 @@ export const ModuleSelectBody = () => { imageWidth="80px" /> */} - + Module name goes here input here - + View Functions - - 0 - + - + Execute Functions - - 0 - + @@ -113,9 +79,9 @@ export const ModuleSelectBody = () => { - - - - + + ); }; diff --git a/src/lib/pages/interaction/component/select-module/ModuleSeletor.tsx b/src/lib/pages/interaction/component/select-module/ModuleSelector.tsx similarity index 85% rename from src/lib/pages/interaction/component/select-module/ModuleSeletor.tsx rename to src/lib/pages/interaction/component/select-module/ModuleSelector.tsx index 9d9d84c19..66d9110b2 100644 --- a/src/lib/pages/interaction/component/select-module/ModuleSeletor.tsx +++ b/src/lib/pages/interaction/component/select-module/ModuleSelector.tsx @@ -43,20 +43,23 @@ export const ModuleSelectorDisplay = () => { borderRadius={8} alignItems="center" > - + - + - diff --git a/src/lib/pages/interaction/index.tsx b/src/lib/pages/interaction/index.tsx index 4f7864151..1d61f5813 100644 --- a/src/lib/pages/interaction/index.tsx +++ b/src/lib/pages/interaction/index.tsx @@ -1,26 +1,37 @@ -import { Badge, Button, Flex, Heading, Text, Box } from "@chakra-ui/react"; +import type { FlexProps } from "@chakra-ui/react"; +import { Button, Flex, Heading, Text } from "@chakra-ui/react"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; +import { CountBadge } from "lib/components/module/CountBadge"; import PageContainer from "lib/components/PageContainer"; -import { StateImage } from "lib/components/state"; +import { EmptyState } from "lib/components/state"; import { InteractionTypeSwitch, - MessageTabs, + InteractionTabs, } from "./component/InteractionTypeSwitch"; import { ModuleSelectDrawerButton } from "./component/ModuleSelectDrawerButton"; +const containerBaseStyle: FlexProps = { + direction: "column", + bgColor: "gray.900", + alignItems: "center", + justifyContent: "center", + borderRadius: 8, + gap: 4, +}; + export const Interaction = () => { const { query, isReady } = useRouter(); - const [tab, setTab] = useState(MessageTabs.VIEW_MODULE); + const [tab, setTab] = useState(); useEffect(() => { if (isReady) setTab( query.type === "execute" - ? MessageTabs.EXECUTE_MODULE - : MessageTabs.VIEW_MODULE + ? InteractionTabs.EXECUTE_MODULE + : InteractionTabs.VIEW_MODULE ); }, [isReady, query.type]); @@ -37,55 +48,36 @@ export const Interaction = () => { borderRadius={4} my={8} > - Select module to interact with ... +

Select module to interact with ...

- + {/* Left side */} + Available functions - - 0 - + - - - - - - - Available functions for selected modules will display here - + + + - - - - Initiate your Module interactions by choosing a module and its - associated function. -
This section will showcase the input or response type - required for the functions. -
+ {/* Right side */} + +
From af4a1e5e9cba0e25fc687d3bca2bb51a5f923788 Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Sun, 3 Sep 2023 22:37:35 +0700 Subject: [PATCH 014/368] feat(components): responsive --- public/font/PilatWide-Bold.woff | Bin 0 -> 34524 bytes public/font/PilatWide-Bold.woff2 | Bin 0 -> 23940 bytes public/font/pilatwide.css | 7 + src/config/theme/initia.ts | 2 +- src/lib/components/module/CountBadge.tsx | 19 +++ src/lib/components/module/ModuleCard.tsx | 31 ++++ src/lib/components/resource/ResourceCard.tsx | 34 ++++ .../resource/ResourceDetailCard.tsx | 49 ++++++ .../components/AccountHeader.tsx | 148 +++++++++++------- .../components/TotalAccountValue.tsx | 20 ++- .../components/modules/ModuleLists.tsx | 69 ++++++++ .../components/resources/ResourceLists.tsx | 68 ++++++++ .../components/resources/ResourceSection.tsx | 82 ++++++++++ src/lib/pages/account-details/index.tsx | 53 ++++++- 14 files changed, 512 insertions(+), 70 deletions(-) create mode 100644 public/font/PilatWide-Bold.woff create mode 100644 public/font/PilatWide-Bold.woff2 create mode 100644 public/font/pilatwide.css create mode 100644 src/lib/components/module/CountBadge.tsx create mode 100644 src/lib/components/module/ModuleCard.tsx create mode 100644 src/lib/components/resource/ResourceCard.tsx create mode 100644 src/lib/components/resource/ResourceDetailCard.tsx create mode 100644 src/lib/pages/account-details/components/modules/ModuleLists.tsx create mode 100644 src/lib/pages/account-details/components/resources/ResourceLists.tsx create mode 100644 src/lib/pages/account-details/components/resources/ResourceSection.tsx diff --git a/public/font/PilatWide-Bold.woff b/public/font/PilatWide-Bold.woff new file mode 100644 index 0000000000000000000000000000000000000000..4c87096400e89fa4796b09402e422b773e6b9f74 GIT binary patch literal 34524 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
w%IR_ak@LMzlrNTK)E% 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 ( + + {count} + +); diff --git a/src/lib/components/module/ModuleCard.tsx b/src/lib/components/module/ModuleCard.tsx new file mode 100644 index 000000000..ea005f3bc --- /dev/null +++ b/src/lib/components/module/ModuleCard.tsx @@ -0,0 +1,31 @@ +import { Flex } from "@chakra-ui/react"; + +import { CustomIcon } from "../icon"; + +import { CountBadge } from "./CountBadge"; + +export const ModuleCard = () => { + return ( + + + + Module name + + + + + + + + ); +}; diff --git a/src/lib/components/resource/ResourceCard.tsx b/src/lib/components/resource/ResourceCard.tsx new file mode 100644 index 000000000..16442ad17 --- /dev/null +++ b/src/lib/components/resource/ResourceCard.tsx @@ -0,0 +1,34 @@ +import { Badge, Flex } from "@chakra-ui/react"; + +interface ResourceCardProps { + name: string; + amount: number; + hasBorder?: boolean; + isSelected?: boolean; +} +export const ResourceCard = ({ + name, + amount, + hasBorder = false, + isSelected = false, +}: ResourceCardProps) => { + return ( + + {name} + + {amount} + + + ); +}; diff --git a/src/lib/components/resource/ResourceDetailCard.tsx b/src/lib/components/resource/ResourceDetailCard.tsx new file mode 100644 index 000000000..39d5a66a8 --- /dev/null +++ b/src/lib/components/resource/ResourceDetailCard.tsx @@ -0,0 +1,49 @@ +import { + Accordion, + AccordionButton, + AccordionItem, + AccordionPanel, + Button, + Flex, + Text, +} from "@chakra-ui/react"; + +import { CustomIcon } from "../icon"; + +interface ResourceDetailCardProps { + name: string; +} +export const ResourceDetailCard = ({ name }: ResourceDetailCardProps) => { + return ( + + + + + + {name} + + + + + + + + + + content goes here content goes here content goes here content goes + here Lorem ipsum, dolor sit amet consectetur adipisicing elit. Est + quia, impedit sed assumenda ex quibusdam earum quae, ipsum + recusandae, saepe quaerat obcaecati labore? Aspernatur, vitae + adipisci ex consequatur officiis at. + + + + + ); +}; diff --git a/src/lib/pages/account-details/components/AccountHeader.tsx b/src/lib/pages/account-details/components/AccountHeader.tsx index 8cbdd4bbc..56238ad40 100644 --- a/src/lib/pages/account-details/components/AccountHeader.tsx +++ b/src/lib/pages/account-details/components/AccountHeader.tsx @@ -6,6 +6,8 @@ import { PrimaryNameMark } from "lib/components/PrimaryNameMark"; import type { ICNSNamesResponse } from "lib/services/ns"; import type { HumanAddr, Option, PublicDetail } from "lib/types"; +import { TotalAccountValue } from "./TotalAccountValue"; + interface AccounHeaderProps { publicName: Option; publicDetail: Option; @@ -22,69 +24,95 @@ export const AccountHeader = ({ const displayName = icnsName?.primary_name || "Account Details"; return ( - - - {publicDetail?.logo || icnsName?.primary_name ? ( - {publicDetail?.name - ) : ( - + + + + {publicDetail?.logo || icnsName?.primary_name ? ( + {publicDetail?.name + ) : ( + + )} + + {publicName ?? displayName} + + + + + + Wallet Address: + + + + + + HEX: + + {/* TODO: HEX */} + + + + {icnsName?.primary_name && ( + + + Registered ICNS names: + + + {icnsName.names.map((name) => ( + + {name === icnsName.primary_name && } + + + ))} + + )} - - {publicName ?? displayName} - - - - Wallet Address: - - + + - {icnsName?.primary_name && ( - - - Registered ICNS names: - - - {icnsName.names.map((name) => ( - - {name === icnsName.primary_name && } - - - ))} - - - )} ); }; diff --git a/src/lib/pages/account-details/components/TotalAccountValue.tsx b/src/lib/pages/account-details/components/TotalAccountValue.tsx index a04406164..f82c23da2 100644 --- a/src/lib/pages/account-details/components/TotalAccountValue.tsx +++ b/src/lib/pages/account-details/components/TotalAccountValue.tsx @@ -1,7 +1,6 @@ -import { Flex, Heading, Text } from "@chakra-ui/react"; +import { Flex, Heading, Skeleton, Text } from "@chakra-ui/react"; import { useAccountTotalValue } from "../data"; -import { Loading } from "lib/components/Loading"; import type { HumanAddr } from "lib/types"; import { formatPrice } from "lib/utils"; @@ -14,17 +13,24 @@ export const TotalAccountValue = ({ const { totalAccountValue, isLoading } = useAccountTotalValue(accountAddress); return ( Total Account Value {isLoading ? ( - + ) : ( void; + totalAsset: number; +} + +const ModuleTitle = ({ + onViewMore, + totalAsset, +}: { + onViewMore: ModuleListsProps["onViewMore"]; + totalAsset: number; +}) => { + const isMobile = useMobile(); + if (isMobile && onViewMore) + return ( + + + + + ); + return ( + + ); +}; +export const ModuleLists = ({ onViewMore, totalAsset }: ModuleListsProps) => { + const isMobile = useMobile(); + return ( + + + {!(isMobile && onViewMore) && ( + + + + + + + + + + + + )} + {!isMobile && onViewMore && } + + ); +}; diff --git a/src/lib/pages/account-details/components/resources/ResourceLists.tsx b/src/lib/pages/account-details/components/resources/ResourceLists.tsx new file mode 100644 index 000000000..b2b204989 --- /dev/null +++ b/src/lib/pages/account-details/components/resources/ResourceLists.tsx @@ -0,0 +1,68 @@ +import { Flex, SimpleGrid } from "@chakra-ui/react"; + +import { useMobile } from "lib/app-provider"; +import { CustomIcon } from "lib/components/icon"; +import { ResourceCard } from "lib/components/resource/ResourceCard"; +import { TableTitle, ViewMore } from "lib/components/table"; + +interface ResourcesListsProps { + onViewMore?: () => void; + totalAsset: number; +} +const ResourceTitle = ({ + onViewMore, + totalAsset, +}: { + onViewMore: ResourcesListsProps["onViewMore"]; + totalAsset: number; +}) => { + const isMobile = useMobile(); + if (!isMobile && !onViewMore) return null; + if (isMobile && onViewMore) + return ( + + + + + ); + return ( + + ); +}; +export const ResourceLists = ({ + onViewMore, + totalAsset, +}: ResourcesListsProps) => { + const isMobile = useMobile(); + return ( + + + {!isMobile && ( + // TODO: data + + + + + + + )} + {!isMobile && onViewMore && } + + ); +}; diff --git a/src/lib/pages/account-details/components/resources/ResourceSection.tsx b/src/lib/pages/account-details/components/resources/ResourceSection.tsx new file mode 100644 index 000000000..2f98adb7e --- /dev/null +++ b/src/lib/pages/account-details/components/resources/ResourceSection.tsx @@ -0,0 +1,82 @@ +import { + Accordion, + AccordionButton, + AccordionItem, + AccordionPanel, + Badge, + Flex, + Heading, + Text, +} from "@chakra-ui/react"; + +import { CustomIcon } from "lib/components/icon"; +import { ResourceCard } from "lib/components/resource/ResourceCard"; +import { ResourceDetailCard } from "lib/components/resource/ResourceDetailCard"; +import { TableTitle } from "lib/components/table"; + +export const ResourceSection = () => { + return ( + + + + + + + + + + 0x1 + + + + + + + + + + + + + + + + + + 0x2 + + + + + + + + + + + + + + + + + + + 0x1::dex + + + 9 + + + + + + + + + ); +}; diff --git a/src/lib/pages/account-details/index.tsx b/src/lib/pages/account-details/index.tsx index bc69af68d..d60142cad 100644 --- a/src/lib/pages/account-details/index.tsx +++ b/src/lib/pages/account-details/index.tsx @@ -11,6 +11,7 @@ import { useCallback, useEffect } from "react"; import { useInternalNavigate, + useMoveConfig, useValidateAddress, useWasmConfig, } from "lib/app-provider"; @@ -34,6 +35,9 @@ import { getFirstQueryParam, truncate } from "lib/utils"; import { AccountHeader } from "./components/AccountHeader"; import { AssetsSection } from "./components/asset"; import { DelegationsSection } from "./components/delegations"; +import { ModuleLists } from "./components/modules/ModuleLists"; +import { ResourceLists } from "./components/resources/ResourceLists"; +import { ResourceSection } from "./components/resources/ResourceSection"; import { AdminContractsTable, InstantiatedContractsTable, @@ -41,7 +45,6 @@ import { StoredCodesTable, TxsTable, } from "./components/tables"; -import { TotalAccountValue } from "./components/TotalAccountValue"; const tableHeaderId = "accountDetailsTab"; @@ -49,10 +52,13 @@ enum TabIndex { Overview = "overview", Assets = "assets", Delegations = "delegations", + NFTs = "ntfs", Txs = "txs", Codes = "codes", Contracts = "contracts", Admins = "admins", + Resources = "resouces", + Modules = "modules", Proposals = "proposals", } @@ -64,6 +70,7 @@ const InvalidAccount = () => ; const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => { const wasm = useWasmConfig({ shouldRedirect: false }); + const move = useMoveConfig({ shouldRedirect: false }); const navigate = useInternalNavigate(); const router = useRouter(); // TODO: remove assertion later @@ -187,6 +194,14 @@ const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => { Delegations + { > Admins + + { - { /> )} + {move.enabled && ( + <> + + + + )} { + nft @@ -315,6 +358,12 @@ const AccountDetailsBody = ({ accountAddress }: AccountDetailsBodyProps) => { refetchCount={refetchContractsAdminCount} /> + + + + + + Date: Mon, 4 Sep 2023 15:58:49 +0700 Subject: [PATCH 015/368] feat(components): module detail overview --- src/lib/components/Breadcrumb.tsx | 1 - .../module-details/components/ModuleInfo.tsx | 168 +++++++++++++++ .../components/ModuleSourceCode.tsx | 50 +++++ .../module-details/components/ModuleTop.tsx | 197 ++++++++++++++++++ .../module-details/components/QuickAccess.tsx | 62 ++++++ src/lib/pages/module-details/index.tsx | 109 ++++++++++ .../[network]/modules/[modulePath]/[tab].tsx | 3 + .../modules/[modulePath]}/index.tsx | 0 src/pages/[network]/modules/index.tsx | 3 - src/pages/modules/[modulePath]/[tab].tsx | 3 + src/pages/modules/[modulePath]/index.tsx | 3 + 11 files changed, 595 insertions(+), 4 deletions(-) create mode 100644 src/lib/pages/module-details/components/ModuleInfo.tsx create mode 100644 src/lib/pages/module-details/components/ModuleSourceCode.tsx create mode 100644 src/lib/pages/module-details/components/ModuleTop.tsx create mode 100644 src/lib/pages/module-details/components/QuickAccess.tsx create mode 100644 src/lib/pages/module-details/index.tsx create mode 100644 src/pages/[network]/modules/[modulePath]/[tab].tsx rename src/pages/{modules => [network]/modules/[modulePath]}/index.tsx (100%) delete mode 100644 src/pages/[network]/modules/index.tsx create mode 100644 src/pages/modules/[modulePath]/[tab].tsx create mode 100644 src/pages/modules/[modulePath]/index.tsx diff --git a/src/lib/components/Breadcrumb.tsx b/src/lib/components/Breadcrumb.tsx index e7d3265ec..569fd1a62 100644 --- a/src/lib/components/Breadcrumb.tsx +++ b/src/lib/components/Breadcrumb.tsx @@ -47,7 +47,6 @@ export const Breadcrumb = ({ items, mb = 0 }: BreadcrumbProps) => ( variant={{ base: "body3", md: "body2" }} className="ellipsis" fontWeight={600} - width="250px" color="text.dark" > {item.text} diff --git a/src/lib/pages/module-details/components/ModuleInfo.tsx b/src/lib/pages/module-details/components/ModuleInfo.tsx new file mode 100644 index 000000000..a56a16ec4 --- /dev/null +++ b/src/lib/pages/module-details/components/ModuleInfo.tsx @@ -0,0 +1,168 @@ +import { + Flex, + Heading, + TabList, + TabPanel, + TabPanels, + Tabs, + Text, +} from "@chakra-ui/react"; + +import { CustomTab } from "lib/components/CustomTab"; +import { ExplorerLink } from "lib/components/ExplorerLink"; +import { CustomIcon } from "lib/components/icon"; +import { LabelText } from "lib/components/LabelText"; +import { useContractDetailsTableCounts } from "lib/model/contract"; +import { + MigrationTable, + RelatedProposalsTable, + TxsTable, +} from "lib/pages/contract-details/components/tables"; +import { useAccountId } from "lib/services/accountService"; +import type { ContractAddr } from "lib/types"; + +import { ModuleSourceCode } from "./ModuleSourceCode"; + +interface ModuleInfoProps { + isVerified?: boolean; + contractAddress?: ContractAddr; +} + +export const ModuleInfo = ({ + isVerified = false, + contractAddress = "" as ContractAddr, +}: ModuleInfoProps) => { + const { data: contractAccountId } = useAccountId(contractAddress); + const tableHeaderId = "ModuleTxsTableHeader"; + const { + tableCounts, + refetchMigration, + refetchTransactions, + refetchRelatedProposals, + } = useContractDetailsTableCounts(contractAddress); + return ( + + + + Module Information + + {isVerified && ( + + + + This module's verification is supported by its provided source + code. + + + )} + + {/* TODO data */} + + + ARBITRARY + + + + + + + + + + + + + + + + + + History + + + + + Transactions + + Migrations + + Related Proposals + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/lib/pages/module-details/components/ModuleSourceCode.tsx b/src/lib/pages/module-details/components/ModuleSourceCode.tsx new file mode 100644 index 000000000..a1b2dda15 --- /dev/null +++ b/src/lib/pages/module-details/components/ModuleSourceCode.tsx @@ -0,0 +1,50 @@ +import { + Accordion, + AccordionButton, + AccordionIcon, + AccordionItem, + AccordionPanel, + Button, + Flex, + Text, +} from "@chakra-ui/react"; + +import { CustomIcon } from "lib/components/icon"; + +export const ModuleSourceCode = () => { + return ( + + + + + + + + Module Source Code + + + The source code is uploaded by the deployer and pulled from + Initia API + + + + + + + + + + TODO JSON INPUT HERE + + + + + ); +}; diff --git a/src/lib/pages/module-details/components/ModuleTop.tsx b/src/lib/pages/module-details/components/ModuleTop.tsx new file mode 100644 index 000000000..d54e4bff2 --- /dev/null +++ b/src/lib/pages/module-details/components/ModuleTop.tsx @@ -0,0 +1,197 @@ +import { Button, Flex, Heading, Text } from "@chakra-ui/react"; + +import { useMobile } from "lib/app-provider"; +import { Breadcrumb } from "lib/components/Breadcrumb"; +import { CopyLink } from "lib/components/CopyLink"; +import { CustomIcon } from "lib/components/icon"; +import { Tooltip } from "lib/components/Tooltip"; + +interface ModuleTopProps { + isVerified?: boolean; +} + +export const ModuleTop = ({ isVerified = false }: ModuleTopProps) => { + const isMobile = useMobile(); + // TODO Name + const displayName = "Module Name"; + + return ( + + + + + + + + {displayName} + + {isVerified && ( + + + + + + )} + + + + Module Path: + + {/* TODO module path */} + + + cltn13a2sywe6g4a9w84g98w4g1dasdrfafdstlju97:: + + + beeb + + + + + + Creator: + + {/* TODO Creator */} + + + + + Friends: + + {/* TODO Friends */} + + + 0x1::any, + + + 0x1::copyable_any + + + 0x1::copyable_any::function_name + + + + + + + + + + + + ); +}; diff --git a/src/lib/pages/module-details/components/QuickAccess.tsx b/src/lib/pages/module-details/components/QuickAccess.tsx new file mode 100644 index 000000000..1d3d798a6 --- /dev/null +++ b/src/lib/pages/module-details/components/QuickAccess.tsx @@ -0,0 +1,62 @@ +import { Flex, Heading, Text } from "@chakra-ui/react"; + +import { CustomIcon, type IconKeys } from "lib/components/icon"; + +interface ActionInfo { + icon: IconKeys; + iconColor: string; + name: string; + count: number; +} + +export const QuickAccess = () => { + const actionList: ActionInfo[] = [ + { + icon: "query" as IconKeys, + iconColor: "primary.main", + name: "View Functions", + count: 430, + }, + { + icon: "execute" as IconKeys, + iconColor: "accent.main", + name: "Execute Functions", + count: 30, + }, + { + icon: "list" as IconKeys, + iconColor: "gray.600", + name: "Transactions", + count: 30, + }, + ]; + + return ( + + {actionList.map((item) => ( + // TODO Navigate to tab + + + + + + {item.name} + + + {item.count} + + + + + + ))} + + ); +}; diff --git a/src/lib/pages/module-details/index.tsx b/src/lib/pages/module-details/index.tsx new file mode 100644 index 000000000..a79b296ee --- /dev/null +++ b/src/lib/pages/module-details/index.tsx @@ -0,0 +1,109 @@ +import { Flex, Tabs, TabList, TabPanel, TabPanels } from "@chakra-ui/react"; +import { useRouter } from "next/router"; +import { useCallback, useEffect } from "react"; + +import { useInternalNavigate } from "lib/app-provider"; +import { CustomTab } from "lib/components/CustomTab"; +import PageContainer from "lib/components/PageContainer"; +import { AmpTrackUseTab } from "lib/services/amplitude"; +import { getFirstQueryParam } from "lib/utils"; + +import { ModuleInfo } from "./components/ModuleInfo"; +import { ModuleTop } from "./components/ModuleTop"; +import { QuickAccess } from "./components/QuickAccess"; + +interface ModuleDetailsProps { + modulePath: string; +} + +const tableHeaderId = "moduleDetailsTab"; +export enum TabIndex { + Overview = "overview", + Function = "function", + Txs = "txs", + Structs = "structs", +} +export const ModuleDetails = ({ + modulePath = "moduletest", +}: ModuleDetailsProps) => { + const navigate = useInternalNavigate(); + const router = useRouter(); + const tab = getFirstQueryParam(router.query.tab) as TabIndex; + + const handleTabChange = useCallback( + (nextTab: TabIndex) => () => { + if (nextTab === tab) return; + AmpTrackUseTab(nextTab); + navigate({ + pathname: "/modules/[modulePath]/[tab]", + query: { + modulePath, + tab: nextTab, + }, + options: { + shallow: true, + }, + }); + }, + [modulePath, tab, navigate] + ); + + useEffect(() => { + if (router.isReady && (!tab || !Object.values(TabIndex).includes(tab))) { + navigate({ + replace: true, + pathname: "/modules/[modulePath]/[tab]", + query: { + modulePath, + tab: TabIndex.Overview, + }, + options: { + shallow: true, + }, + }); + } + }, [router.isReady, tab, modulePath, navigate]); + + return ( + + + + + + Overview + + + Function + + + Transactions + + + Structs + + + + + + + + + + 2 + 3 + 4 + + + + ); +}; diff --git a/src/pages/[network]/modules/[modulePath]/[tab].tsx b/src/pages/[network]/modules/[modulePath]/[tab].tsx new file mode 100644 index 000000000..d5deadd8b --- /dev/null +++ b/src/pages/[network]/modules/[modulePath]/[tab].tsx @@ -0,0 +1,3 @@ +import { ModuleDetails } from "lib/pages/module-details"; + +export default ModuleDetails; diff --git a/src/pages/modules/index.tsx b/src/pages/[network]/modules/[modulePath]/index.tsx similarity index 100% rename from src/pages/modules/index.tsx rename to src/pages/[network]/modules/[modulePath]/index.tsx diff --git a/src/pages/[network]/modules/index.tsx b/src/pages/[network]/modules/index.tsx deleted file mode 100644 index 9d66bb8ef..000000000 --- a/src/pages/[network]/modules/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import Modules from "pages/modules"; - -export default Modules; diff --git a/src/pages/modules/[modulePath]/[tab].tsx b/src/pages/modules/[modulePath]/[tab].tsx new file mode 100644 index 000000000..d5deadd8b --- /dev/null +++ b/src/pages/modules/[modulePath]/[tab].tsx @@ -0,0 +1,3 @@ +import { ModuleDetails } from "lib/pages/module-details"; + +export default ModuleDetails; diff --git a/src/pages/modules/[modulePath]/index.tsx b/src/pages/modules/[modulePath]/index.tsx new file mode 100644 index 000000000..49cccb88d --- /dev/null +++ b/src/pages/modules/[modulePath]/index.tsx @@ -0,0 +1,3 @@ +import { Modules } from "lib/pages/modules"; + +export default Modules; From f0ca9825a6fe1a760c10c55c026dd8f630a628ec Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Mon, 4 Sep 2023 16:06:04 +0700 Subject: [PATCH 016/368] feat(components): add txs table in module detail --- .../module-details/components/ModuleInfo.tsx | 88 +---------- src/lib/pages/module-details/index.tsx | 148 +++++++++++++++++- 2 files changed, 146 insertions(+), 90 deletions(-) diff --git a/src/lib/pages/module-details/components/ModuleInfo.tsx b/src/lib/pages/module-details/components/ModuleInfo.tsx index a56a16ec4..1848f68d3 100644 --- a/src/lib/pages/module-details/components/ModuleInfo.tsx +++ b/src/lib/pages/module-details/components/ModuleInfo.tsx @@ -1,45 +1,16 @@ -import { - Flex, - Heading, - TabList, - TabPanel, - TabPanels, - Tabs, - Text, -} from "@chakra-ui/react"; +import { Flex, Heading, Text } from "@chakra-ui/react"; -import { CustomTab } from "lib/components/CustomTab"; import { ExplorerLink } from "lib/components/ExplorerLink"; import { CustomIcon } from "lib/components/icon"; import { LabelText } from "lib/components/LabelText"; -import { useContractDetailsTableCounts } from "lib/model/contract"; -import { - MigrationTable, - RelatedProposalsTable, - TxsTable, -} from "lib/pages/contract-details/components/tables"; -import { useAccountId } from "lib/services/accountService"; -import type { ContractAddr } from "lib/types"; import { ModuleSourceCode } from "./ModuleSourceCode"; interface ModuleInfoProps { isVerified?: boolean; - contractAddress?: ContractAddr; } -export const ModuleInfo = ({ - isVerified = false, - contractAddress = "" as ContractAddr, -}: ModuleInfoProps) => { - const { data: contractAccountId } = useAccountId(contractAddress); - const tableHeaderId = "ModuleTxsTableHeader"; - const { - tableCounts, - refetchMigration, - refetchTransactions, - refetchRelatedProposals, - } = useContractDetailsTableCounts(contractAddress); +export const ModuleInfo = ({ isVerified = false }: ModuleInfoProps) => { return ( @@ -108,61 +79,6 @@ export const ModuleInfo = ({ - - - History - - - - - Transactions - - Migrations - - Related Proposals - - - - - - - - - - - - - - - ); }; diff --git a/src/lib/pages/module-details/index.tsx b/src/lib/pages/module-details/index.tsx index a79b296ee..21da71f17 100644 --- a/src/lib/pages/module-details/index.tsx +++ b/src/lib/pages/module-details/index.tsx @@ -1,11 +1,26 @@ -import { Flex, Tabs, TabList, TabPanel, TabPanels } from "@chakra-ui/react"; +import { + Flex, + Tabs, + TabList, + TabPanel, + TabPanels, + Heading, +} from "@chakra-ui/react"; import { useRouter } from "next/router"; import { useCallback, useEffect } from "react"; +import { + MigrationTable, + RelatedProposalsTable, + TxsTable, +} from "../contract-details/components/tables"; import { useInternalNavigate } from "lib/app-provider"; import { CustomTab } from "lib/components/CustomTab"; import PageContainer from "lib/components/PageContainer"; +import { useContractDetailsTableCounts } from "lib/model/contract"; +import { useAccountId } from "lib/services/accountService"; import { AmpTrackUseTab } from "lib/services/amplitude"; +import type { ContractAddr } from "lib/types"; import { getFirstQueryParam } from "lib/utils"; import { ModuleInfo } from "./components/ModuleInfo"; @@ -63,7 +78,15 @@ export const ModuleDetails = ({ }); } }, [router.isReady, tab, modulePath, navigate]); - + const contractAddress = "" as ContractAddr; + const { data: contractAccountId } = useAccountId(contractAddress); + const txTableHeaderId = "ModuleTxsTableHeader"; + const { + tableCounts, + refetchMigration, + refetchTransactions, + refetchRelatedProposals, + } = useContractDetailsTableCounts(contractAddress); return ( @@ -96,11 +119,128 @@ export const ModuleDetails = ({ - + + {/* TODO History */} + + + History + + + + + Transactions + + + Migrations + + + Related Proposals + + + + + + + + + + + + + + + 2 - 3 + + {/* TODO History */} + + + History + + + + + Transactions + + + Migrations + + + Related Proposals + + + + + + + + + + + + + + + + 4 From 665f9f0b1876ee59e3b7dbd5214d32a01e240598 Mon Sep 17 00:00:00 2001 From: poomthiti Date: Mon, 4 Sep 2023 16:52:21 +0700 Subject: [PATCH 017/368] feat: initia select module drawer wireup --- CHANGELOG.md | 1 + src/config/chain/initia.ts | 11 +- src/lib/app-provider/env.ts | 2 + src/lib/app-provider/hooks/index.ts | 1 + src/lib/app-provider/hooks/useAddress.ts | 6 + .../hooks/useConvertHexAddress.ts | 17 ++ src/lib/chain-registry/initiatestnet.ts | 87 ++++++++++ src/lib/components/CopyLink.tsx | 8 +- src/lib/components/InputWithIcon.tsx | 36 +++- src/lib/components/forms/TextInput.tsx | 9 +- src/lib/components/module/FunctionCard.tsx | 58 ++++--- src/lib/components/module/ModuleCard.tsx | 65 ++++++-- .../component/ModuleSelectDrawerButton.tsx | 57 ------- .../body}/ModuleEmptyState.tsx | 22 ++- .../drawer/body/ModuleFunctionBody.tsx | 156 ++++++++++++++++++ .../drawer/body/ModuleSelectMainBody.tsx | 151 +++++++++++++++++ .../component/drawer/body/index.ts | 2 + .../interaction/component/drawer/index.tsx | 86 ++++++++++ .../drawer/selector/SelectorDisplay.tsx | 54 ++++++ .../drawer/selector/SelectorInput.tsx | 136 +++++++++++++++ .../component/drawer/selector/index.tsx | 45 +++++ .../interaction/component/drawer/types.ts | 14 ++ .../select-module/ModuleSelectBody.tsx | 87 ---------- .../select-module/ModuleSelector.tsx | 67 -------- .../hooks/useValidateModuleInput.ts | 37 +++++ src/lib/pages/interaction/index.tsx | 21 ++- src/lib/providers/cosmos-kit.tsx | 9 +- src/lib/services/module.ts | 35 ++++ src/lib/services/moduleService.ts | 67 ++++++++ src/lib/types/abi.ts | 81 +++++++++ src/lib/types/addrs.ts | 2 + src/lib/types/index.ts | 1 + src/lib/utils/abi.ts | 20 +++ src/lib/utils/address.ts | 15 ++ src/lib/utils/validate.ts | 10 ++ 35 files changed, 1195 insertions(+), 281 deletions(-) create mode 100644 src/lib/app-provider/hooks/useConvertHexAddress.ts create mode 100644 src/lib/chain-registry/initiatestnet.ts delete mode 100644 src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx rename src/lib/pages/interaction/component/{select-module => drawer/body}/ModuleEmptyState.tsx (56%) create mode 100644 src/lib/pages/interaction/component/drawer/body/ModuleFunctionBody.tsx create mode 100644 src/lib/pages/interaction/component/drawer/body/ModuleSelectMainBody.tsx create mode 100644 src/lib/pages/interaction/component/drawer/body/index.ts create mode 100644 src/lib/pages/interaction/component/drawer/index.tsx create mode 100644 src/lib/pages/interaction/component/drawer/selector/SelectorDisplay.tsx create mode 100644 src/lib/pages/interaction/component/drawer/selector/SelectorInput.tsx create mode 100644 src/lib/pages/interaction/component/drawer/selector/index.tsx create mode 100644 src/lib/pages/interaction/component/drawer/types.ts delete mode 100644 src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx delete mode 100644 src/lib/pages/interaction/component/select-module/ModuleSelector.tsx create mode 100644 src/lib/pages/interaction/hooks/useValidateModuleInput.ts create mode 100644 src/lib/services/module.ts create mode 100644 src/lib/services/moduleService.ts create mode 100644 src/lib/types/abi.ts create mode 100644 src/lib/utils/abi.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 758dc34ef..dd1ef6dde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#515](https://github.com/alleslabs/celatone-frontend/pull/515) Initia select module drawer wireup - [#494](https://github.com/alleslabs/celatone-frontend/pull/494) Initia select module drawer UI - [#490](https://github.com/alleslabs/celatone-frontend/pull/490) Add initia module interaction page - [#488](https://github.com/alleslabs/celatone-frontend/pull/488) Add initia navigation and sidebar diff --git a/src/config/chain/initia.ts b/src/config/chain/initia.ts index 3a885c3c0..20ef860b6 100644 --- a/src/config/chain/initia.ts +++ b/src/config/chain/initia.ts @@ -5,12 +5,11 @@ import type { ChainConfigs } from "./types"; export const INITIA_CHAIN_CONFIGS: ChainConfigs = { "stone-9": { chain: "initia", - registryChainName: "osmosis", + registryChainName: "initiatestnet", prettyName: "Initia Testnet", - // TODO change to initia - lcd: "https://lcd.osmosis.zone", - rpc: "https://rpc.osmosis.zone:443", - indexer: "https://osmosis-mainnet-graphql.alleslabs.dev/v1/graphql", + lcd: "https://stone-rest.initia.tech", + rpc: "https://stone-rpc.initia.tech:443", + indexer: "https://initia-tesnet-graphql.alleslabs.dev/v1/graphql", api: "https://celatone-api.alleslabs.dev", wallets: [...keplrWallets], features: { @@ -36,7 +35,7 @@ export const INITIA_CHAIN_CONFIGS: ChainConfigs = { gas: { gasPrice: { tokenPerGas: 0.025, - denom: "init", + denom: "uinit", }, gasAdjustment: 1.5, maxGasLimit: 25_000_000, diff --git a/src/lib/app-provider/env.ts b/src/lib/app-provider/env.ts index fb00400ca..0e477df7a 100644 --- a/src/lib/app-provider/env.ts +++ b/src/lib/app-provider/env.ts @@ -92,4 +92,6 @@ export enum CELATONE_QUERY_KEYS { POOL_INFO_BY_IDS = "CELATONE_QUERY_POOL_INFO_BY_IDS", POOL_TRANSACTION_BY_ID = "CELATONE_QUERY_POOL_TRANSACTION_BY_ID", POOL_TRANSACTION_BY_ID_COUNT = "CELATONE_QUERY_POOL_TRANSACTION_BY_ID_COUNT", + // MODULES + ACCOUNT_MODULES = "CELATONE_QUERY_ACCOUNT_MODULES", } diff --git a/src/lib/app-provider/hooks/index.ts b/src/lib/app-provider/hooks/index.ts index 76a5b2de0..98b3d19b0 100644 --- a/src/lib/app-provider/hooks/index.ts +++ b/src/lib/app-provider/hooks/index.ts @@ -13,3 +13,4 @@ export * from "./useBaseApiRoute"; export * from "./useRPCEndpoint"; export * from "./useConfig"; export * from "./useCurrentChain"; +export * from "./useConvertHexAddress"; diff --git a/src/lib/app-provider/hooks/useAddress.ts b/src/lib/app-provider/hooks/useAddress.ts index 0762dae0d..b69e26a93 100644 --- a/src/lib/app-provider/hooks/useAddress.ts +++ b/src/lib/app-provider/hooks/useAddress.ts @@ -2,6 +2,7 @@ import { fromBech32 } from "@cosmjs/encoding"; import { useCallback, useMemo } from "react"; import type { Option } from "lib/types"; +import { isHexAddress } from "lib/utils"; import { useCurrentChain } from "./useCurrentChain"; import { useExampleAddresses } from "./useExampleAddresses"; @@ -134,5 +135,10 @@ export const useValidateAddress = () => { ), [bech32Prefix, getAddressTypeByLength] ), + validateHexAddress: useCallback( + (address: string) => + !address.startsWith(bech32Prefix) && isHexAddress(address), + [bech32Prefix] + ), }; }; diff --git a/src/lib/app-provider/hooks/useConvertHexAddress.ts b/src/lib/app-provider/hooks/useConvertHexAddress.ts new file mode 100644 index 000000000..749d4ec8b --- /dev/null +++ b/src/lib/app-provider/hooks/useConvertHexAddress.ts @@ -0,0 +1,17 @@ +import { useCallback } from "react"; + +import type { HexAddr } from "lib/types"; +import { hexToBech32Address } from "lib/utils"; + +import { useCurrentChain } from "./useCurrentChain"; + +export const useConvertHexAddress = () => { + const { + chain: { bech32_prefix: bech32Prefix }, + } = useCurrentChain(); + + return useCallback( + (hexAddr: HexAddr) => hexToBech32Address(bech32Prefix, hexAddr), + [bech32Prefix] + ); +}; diff --git a/src/lib/chain-registry/initiatestnet.ts b/src/lib/chain-registry/initiatestnet.ts new file mode 100644 index 000000000..715686927 --- /dev/null +++ b/src/lib/chain-registry/initiatestnet.ts @@ -0,0 +1,87 @@ +import type { Chain, AssetList } from "@chain-registry/types"; + +export const initiatestnet: Chain = { + $schema: "../chain.schema.json", + chain_name: "initiatestnet", + status: "live", + network_type: "testnet", + pretty_name: "Initia Testnet", + chain_id: "stone-9", + bech32_prefix: "init", + daemon_name: "initd", + node_home: "$HOME/.init", + key_algos: ["secp256k1"], + slip44: 118, + fees: { + fee_tokens: [ + { + denom: "uinit", + fixed_min_gas_price: 0, + low_gas_price: 0, + average_gas_price: 0.025, + high_gas_price: 0.04, + }, + ], + }, + staking: { + staking_tokens: [ + { + denom: "uinit", + }, + ], + }, + codebase: { + git_repo: "https://github.com/sei-protocol/sei-chain", + recommended_version: "v3.0.0", + compatible_versions: ["v3.0.0"], + cosmos_sdk_version: "", + cosmwasm_enabled: true, + genesis: { + genesis_url: "", + }, + versions: [ + { + name: "v1", + }, + ], + }, + logo_URIs: { + png: "", + svg: "", + }, + apis: { + rpc: [ + { + address: "https://stone-rpc.initia.tech:443", + }, + ], + rest: [ + { + address: "https://stone-rest.initia.tech", + }, + ], + }, +}; +export const initiatestnetAssets: AssetList = { + $schema: "../assetlist.schema.json", + chain_name: "initiatestnet", + assets: [ + { + description: "The native staking token of Initia.", + denom_units: [ + { + denom: "uinit", + exponent: 0, + }, + { + denom: "init", + exponent: 6, + }, + ], + base: "uinit", + name: "Init", + display: "init", + symbol: "INIT", + }, + ], +}; diff --git a/src/lib/components/CopyLink.tsx b/src/lib/components/CopyLink.tsx index 16530a7c7..1190e867e 100644 --- a/src/lib/components/CopyLink.tsx +++ b/src/lib/components/CopyLink.tsx @@ -1,6 +1,6 @@ import type { FlexProps, IconProps } from "@chakra-ui/react"; import { Flex, Text, useClipboard } from "@chakra-ui/react"; -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useCurrentChain } from "lib/app-provider"; import { AmpTrackCopier } from "lib/services/amplitude"; @@ -25,7 +25,7 @@ export const CopyLink = ({ ...flexProps }: CopyLinkProps) => { const { address } = useCurrentChain(); - const { onCopy, hasCopied } = useClipboard(value); + const { onCopy, hasCopied, setValue } = useClipboard(value); const [isHover, setIsHover] = useState(false); // TODO - Refactor @@ -39,6 +39,10 @@ export const CopyLink = ({ return undefined; }, [showCopyOnHover, isHover]); + useEffect(() => { + setValue(value); + }, [value, setValue]); + return ( ) => void; size?: InputProps["size"]; + my?: InputProps["my"]; action?: string; + iconPosition?: "start" | "end"; + onChange: (e: ChangeEvent) => void; } +const SearchIcon = () => ; + const InputWithIcon = ({ placeholder, value, size, + my, action, + iconPosition = "end", onChange, }: InputWithIconProps) => ( - + AmpTrack(AmpEvent.USE_SEARCH_INPUT) : undefined} /> - - - + {iconPosition === "end" ? ( + + + + ) : ( + + + + )} ); diff --git a/src/lib/components/forms/TextInput.tsx b/src/lib/components/forms/TextInput.tsx index e0b13ce70..a3dcda016 100644 --- a/src/lib/components/forms/TextInput.tsx +++ b/src/lib/components/forms/TextInput.tsx @@ -23,6 +23,7 @@ import { getResponseMsg, getStatusIcon } from "./FormStatus"; export interface TextInputProps extends FormControlProps { value: string; setInputState: Dispatch>; + autoFocus?: boolean; label?: string; labelBgColor?: string; helperText?: string; @@ -36,6 +37,7 @@ export interface TextInputProps extends FormControlProps { export const TextInput = ({ value, setInputState, + autoFocus, label = "", labelBgColor = "background.main", helperText, @@ -62,6 +64,7 @@ export const TextInput = ({ {error} ) : ( - + {status?.message ? ( getResponseMsg(status, helperText) ) : ( - {helperText} + + {helperText} + )} )} diff --git a/src/lib/components/module/FunctionCard.tsx b/src/lib/components/module/FunctionCard.tsx index d670203d6..d05998b59 100644 --- a/src/lib/components/module/FunctionCard.tsx +++ b/src/lib/components/module/FunctionCard.tsx @@ -4,22 +4,22 @@ import { Flex, Text } from "@chakra-ui/react"; import { DotSeparator } from "../DotSeparator"; import { CustomIcon } from "../icon"; import { Tooltip } from "lib/components/Tooltip"; +import type { ExposedFunction, Visibility } from "lib/types"; interface FunctionCardProps { - isView?: boolean; - disabled?: boolean; - visibility?: "public" | "friends" | "script"; + exposedFn: ExposedFunction; + onFunctionSelect: (fn: ExposedFunction) => void; } -const getIcon = (visibility: FunctionCardProps["visibility"]) => { +const getIcon = (visibility: Visibility) => { switch (visibility) { - case "friends": - return ; + case "friend": + return ; case "script": - return ; + return ; case "public": default: - return ; + return ; } }; @@ -28,6 +28,7 @@ const disabledStyle: { [key in `${boolean}`]: FlexProps } = { bgColor: "gray.900", _hover: { bg: "gray.900" }, cursor: "not-allowed", + pointerEvents: "none", borderColor: "gray.700", }, false: { @@ -39,10 +40,12 @@ const disabledStyle: { [key in `${boolean}`]: FlexProps } = { }; export const FunctionCard = ({ - isView = true, - disabled = false, - visibility = "public", + exposedFn, + onFunctionSelect, }: FunctionCardProps) => { + const { is_entry: isEntry, is_view: isView, visibility, name } = exposedFn; + const disabled = !isEntry || visibility !== "public"; + return ( onFunctionSelect(exposedFn)} {...disabledStyle[String(disabled) as `${boolean}`]} > @@ -66,28 +70,30 @@ export const FunctionCard = ({ - - - - - - + {!disabled && ( + <> + + + + + + + + )} {getIcon(visibility)} - + {visibility} - Function Name + + {name} + ); }; diff --git a/src/lib/components/module/ModuleCard.tsx b/src/lib/components/module/ModuleCard.tsx index 72cb75431..131632cee 100644 --- a/src/lib/components/module/ModuleCard.tsx +++ b/src/lib/components/module/ModuleCard.tsx @@ -1,31 +1,60 @@ -import { Flex } from "@chakra-ui/react"; +import { Flex, Grid, Text } from "@chakra-ui/react"; +import type { Dispatch, SetStateAction } from "react"; import { CustomIcon } from "../icon"; +import type { IndexedModule } from "lib/services/moduleService"; +import type { Option } from "lib/types"; import { CountBadge } from "./CountBadge"; -export const ModuleCard = () => { +interface ModuleCardProps { + module: IndexedModule; + selectedModule: Option; + setSelectedModule: Dispatch>>; +} + +export const ModuleCard = ({ + module, + selectedModule, + setSelectedModule, +}: ModuleCardProps) => { + const noExposedFunctions = !module.parsedAbi.exposed_functions.length; return ( - setSelectedModule(module)} + gap={1} + templateColumns="20px 1fr auto" + _hover={{ + bg: "gray.700", + }} + transition=".25s all ease-out" > - - - Module name - + + + + {module.moduleName} + + {!noExposedFunctions && ( + + )} - - - + + + - + ); }; diff --git a/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx b/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx deleted file mode 100644 index ca11aa024..000000000 --- a/src/lib/pages/interaction/component/ModuleSelectDrawerButton.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { - Button, - Heading, - useDisclosure, - Drawer, - DrawerOverlay, - DrawerContent, - DrawerHeader, - DrawerCloseButton, - DrawerBody, -} from "@chakra-ui/react"; - -import { CustomIcon } from "lib/components/icon"; - -import { ModuleSelectBody } from "./select-module/ModuleSelectBody"; -import { - ModuleSelectorDisplay, - ModuleSelectorInput, -} from "./select-module/ModuleSelector"; -// import { ModuleEmptyState } from "./select-module/ModuleEmptyState"; - -interface ModuleSelectDrawerButtonProps { - buttonText?: string; -} - -export const ModuleSelectDrawerButton = ({ - buttonText = "Select Module", -}: ModuleSelectDrawerButtonProps) => { - const { isOpen, onClose, onOpen } = useDisclosure(); - return ( - <> - - - - - - - - Select Module - - - - - {/* Input */} - - {/* Selected Address */} - - - {/* */} - - - - - ); -}; diff --git a/src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx b/src/lib/pages/interaction/component/drawer/body/ModuleEmptyState.tsx similarity index 56% rename from src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx rename to src/lib/pages/interaction/component/drawer/body/ModuleEmptyState.tsx index a408d333b..b36e654f1 100644 --- a/src/lib/pages/interaction/component/select-module/ModuleEmptyState.tsx +++ b/src/lib/pages/interaction/component/drawer/body/ModuleEmptyState.tsx @@ -1,3 +1,4 @@ +import type { FlexProps } from "@chakra-ui/react"; import { Text, Flex } from "@chakra-ui/react"; import { StateImage } from "lib/components/state"; @@ -5,24 +6,35 @@ import { StateImage } from "lib/components/state"; interface ModuleEmptyStateProps { description?: string; imageWidth?: string; + hasImage?: boolean; + noBorder?: boolean; + h?: FlexProps["h"]; + p?: FlexProps["p"]; } export const ModuleEmptyState = ({ description = "Available functions for selected modules will display here", imageWidth = "160px", + hasImage = true, + noBorder = false, + h = "full", + p, }: ModuleEmptyStateProps) => { return ( - - + {hasImage && ( + + )} + {description} diff --git a/src/lib/pages/interaction/component/drawer/body/ModuleFunctionBody.tsx b/src/lib/pages/interaction/component/drawer/body/ModuleFunctionBody.tsx new file mode 100644 index 000000000..5e3065c5e --- /dev/null +++ b/src/lib/pages/interaction/component/drawer/body/ModuleFunctionBody.tsx @@ -0,0 +1,156 @@ +import type { FlexProps, GridItemProps } from "@chakra-ui/react"; +import { GridItem, Heading, Flex, Text } from "@chakra-ui/react"; +import { useCallback, useMemo, useState } from "react"; + +import type { ModuleSelectFunction } from "../types"; +import InputWithIcon from "lib/components/InputWithIcon"; +import { CountBadge } from "lib/components/module/CountBadge"; +import { FunctionCard } from "lib/components/module/FunctionCard"; +import type { IndexedModule } from "lib/services/moduleService"; +import type { ExposedFunction, Option } from "lib/types"; + +import { ModuleEmptyState } from "./ModuleEmptyState"; + +const functionGridBaseStyle: FlexProps = { + border: "1px solid", + borderRadius: 8, + borderColor: "gray.700", + p: 4, + direction: "column", +}; + +const EmptyFunctionState = ({ + desc = "No exposed_functions available.", +}: { + desc?: string; +}) => ( + +); + +interface RenderFunctionsProps { + exposedFnsLength: number; + filtered: Option; + onFunctionSelect: (fn: ExposedFunction) => void; +} +const RenderFunctions = ({ + exposedFnsLength, + filtered, + onFunctionSelect, +}: RenderFunctionsProps) => { + if (!exposedFnsLength) return ; + return filtered?.length ? ( + <> + {filtered.map((fn) => ( + + ))} + + ) : ( + + ); +}; + +interface ModuleFunctionBodyProps extends GridItemProps { + module: Option; + handleModuleSelect: ModuleSelectFunction; + closeModal: () => void; +} + +export const ModuleFunctionBody = ({ + module, + handleModuleSelect, + closeModal, + ...gridItemProps +}: ModuleFunctionBodyProps) => { + const [keyword, setKeyword] = useState(""); + const [filteredView, filteredExecute] = useMemo( + () => [ + module?.viewFunctions.filter((el) => el.name.includes(keyword)), + module?.executeFunctions.filter((el) => el.name.includes(keyword)), + ], + [module?.viewFunctions, module?.executeFunctions, keyword] + ); + + const onFunctionSelect = useCallback( + (fn: ExposedFunction) => { + if (module) { + handleModuleSelect(module, fn); + closeModal(); + } + }, + [module, handleModuleSelect, closeModal] + ); + + // TODO: 100% - element and margin hack, find better way to setup the height + const maxHeight = "calc(100% - 22px - 32px - 40px)"; + return ( + + {module ? ( + <> + + {module.moduleName} + + setKeyword(e.target.value)} + placeholder="Search functions ..." + my={4} + /> + + + + + View Functions + + + + + + + + + + + Execute Functions + + + + + + + + + + ) : ( + + )} + + ); +}; diff --git a/src/lib/pages/interaction/component/drawer/body/ModuleSelectMainBody.tsx b/src/lib/pages/interaction/component/drawer/body/ModuleSelectMainBody.tsx new file mode 100644 index 000000000..d25c0622e --- /dev/null +++ b/src/lib/pages/interaction/component/drawer/body/ModuleSelectMainBody.tsx @@ -0,0 +1,151 @@ +import { Grid, GridItem, Text, Flex, Box, Button } from "@chakra-ui/react"; +import type { Dispatch, SetStateAction } from "react"; +import { useMemo, useState } from "react"; + +import type { DisplayMode, ModuleSelectFunction } from "../types"; +import InputWithIcon from "lib/components/InputWithIcon"; +import { CountBadge } from "lib/components/module/CountBadge"; +import { ModuleCard } from "lib/components/module/ModuleCard"; +import type { IndexedModule } from "lib/services/moduleService"; +import type { Option } from "lib/types"; + +import { ModuleEmptyState } from "./ModuleEmptyState"; +import { ModuleFunctionBody } from "./ModuleFunctionBody"; + +interface ModuleSelectBodyProps { + modules: IndexedModule[]; + mode: DisplayMode; + handleModuleSelect: ModuleSelectFunction; + closeModal: () => void; +} + +const RenderModules = ({ + modulesLength, + filtered, + selectedModule, + setSelectedModule, +}: { + modulesLength: number; + filtered: IndexedModule[]; + selectedModule: Option; + setSelectedModule: Dispatch>>; +}) => { + if (!modulesLength) + return ( + + ); + return filtered?.length ? ( + <> + {filtered.map((module) => ( + + ))} + + ) : ( + + ); +}; + +export const ModuleSelectMainBody = ({ + mode, + modules, + handleModuleSelect, + closeModal, +}: ModuleSelectBodyProps) => { + const [keyword, setKeyword] = useState(""); + const [selectedModule, setSelectedModule] = useState(); + + const filteredModules = useMemo( + () => modules.filter((el) => el.moduleName.includes(keyword)), + [modules, keyword] + ); + + return ( + + {mode === "input" && ( + + )} + + setKeyword(e.target.value)} + placeholder="Search module ..." + /> + + + Modules + + + + + + + + {/* Left */} + + + + + + ); +}; diff --git a/src/lib/pages/interaction/component/drawer/body/index.ts b/src/lib/pages/interaction/component/drawer/body/index.ts new file mode 100644 index 000000000..06b4852b5 --- /dev/null +++ b/src/lib/pages/interaction/component/drawer/body/index.ts @@ -0,0 +1,2 @@ +export * from "./ModuleEmptyState"; +export * from "./ModuleSelectMainBody"; diff --git a/src/lib/pages/interaction/component/drawer/index.tsx b/src/lib/pages/interaction/component/drawer/index.tsx new file mode 100644 index 000000000..cf5d0916a --- /dev/null +++ b/src/lib/pages/interaction/component/drawer/index.tsx @@ -0,0 +1,86 @@ +import { + Button, + Heading, + useDisclosure, + Drawer, + DrawerOverlay, + DrawerContent, + DrawerHeader, + DrawerCloseButton, + DrawerBody, + Flex, +} from "@chakra-ui/react"; +import { useState } from "react"; + +import { CustomIcon } from "lib/components/icon"; +import type { IndexedModule } from "lib/services/moduleService"; +import type { HexAddr, HumanAddr } from "lib/types"; + +import { ModuleEmptyState, ModuleSelectMainBody } from "./body"; +import { ModuleSelector } from "./selector"; +import type { + DisplayMode, + SelectedAddress, + ModuleSelectFunction, +} from "./types"; + +interface ModuleSelectDrawerTriggerProps { + buttonText?: string; + handleModuleSelect: ModuleSelectFunction; +} + +export const ModuleSelectDrawerTrigger = ({ + buttonText = "Select Module", + handleModuleSelect, +}: ModuleSelectDrawerTriggerProps) => { + const { isOpen, onClose, onOpen } = useDisclosure(); + const [modules, setModules] = useState(); + const [mode, setMode] = useState("input"); + const [selectedAddress, setSelectedAddress] = useState({ + address: "" as HumanAddr, + hex: "" as HexAddr, + }); + + return ( + <> + + + + + + + + Select Module + + + + + + + {modules ? ( + + ) : ( + + )} + + + + + + ); +}; diff --git a/src/lib/pages/interaction/component/drawer/selector/SelectorDisplay.tsx b/src/lib/pages/interaction/component/drawer/selector/SelectorDisplay.tsx new file mode 100644 index 000000000..a3127dd2c --- /dev/null +++ b/src/lib/pages/interaction/component/drawer/selector/SelectorDisplay.tsx @@ -0,0 +1,54 @@ +import { Flex, Button } from "@chakra-ui/react"; +import type { Dispatch, SetStateAction } from "react"; + +import type { DisplayMode, SelectedAddress } from "../types"; +import { CopyLink } from "lib/components/CopyLink"; +import { CustomIcon } from "lib/components/icon"; +import { LabelText } from "lib/components/LabelText"; + +interface ModuleSelectorDisplayProps { + selectedAddress: SelectedAddress; + setMode: Dispatch>; +} + +export const ModuleSelectorDisplay = ({ + selectedAddress, + setMode, +}: ModuleSelectorDisplayProps) => ( + + + + + + + + + +); diff --git a/src/lib/pages/interaction/component/drawer/selector/SelectorInput.tsx b/src/lib/pages/interaction/component/drawer/selector/SelectorInput.tsx new file mode 100644 index 000000000..067c4b70e --- /dev/null +++ b/src/lib/pages/interaction/component/drawer/selector/SelectorInput.tsx @@ -0,0 +1,136 @@ +import { Flex, Button } from "@chakra-ui/react"; +import type { Dispatch, SetStateAction, KeyboardEvent } from "react"; +import { useState, useMemo, useCallback } from "react"; + +import type { + SelectedAddress, + DisplayMode, + ModuleSelectFunction, +} from "../types"; +import { + useConvertHexAddress, + useValidateAddress, + useExampleAddresses, +} from "lib/app-provider"; +import { TextInput } from "lib/components/forms"; +import { useValidateModuleInput } from "lib/pages/interaction/hooks/useValidateModuleInput"; +import type { IndexedModule } from "lib/services/moduleService"; +import { useAddressModules } from "lib/services/moduleService"; +import type { AccountAddr, HexAddr, HumanAddr, Option } from "lib/types"; +import { bech32AddressToHex } from "lib/utils"; + +export interface ModuleSelectorInputProps { + selectedAddress: SelectedAddress; + setSelectedAddress: Dispatch>; + handleModuleSelect: ModuleSelectFunction; + setModules: Dispatch>>; + setMode: Dispatch>; + closeModal: () => void; +} + +export const ModuleSelectorInput = ({ + selectedAddress, + setSelectedAddress, + handleModuleSelect, + setModules, + setMode, + closeModal, +}: ModuleSelectorInputProps) => { + const [keyword, setKeyword] = useState(""); + const [error, setError] = useState(""); + const [addr, moduleName] = useMemo(() => keyword.split("::"), [keyword]); + + const convertHexAddr = useConvertHexAddress(); + const { validateHexAddress } = useValidateAddress(); + const validateModuleInput = useValidateModuleInput(); + const { user } = useExampleAddresses(); + const { refetch, isFetching } = useAddressModules({ + address: addr as AccountAddr, + moduleName, + options: { + refetchOnWindowFocus: false, + enabled: false, + retry: false, + onSuccess: (data) => { + setError(""); + setSelectedAddress(() => { + const isHex = validateHexAddress(addr); + return isHex + ? { + address: convertHexAddr(addr as HexAddr), + hex: addr as HexAddr, + } + : { + address: addr as HumanAddr, + hex: bech32AddressToHex(addr as HumanAddr), + }; + }); + if (Array.isArray(data)) { + setModules(data); + setMode("display"); + } else { + handleModuleSelect(data); + closeModal(); + } + }, + onError: (err) => setError((err as Error).message), + }, + }); + + const handleSubmit = useCallback(() => { + const err = validateModuleInput(keyword); + return err ? setError(err) : refetch(); + }, [keyword, refetch, validateModuleInput]); + + const handleKeydown = useCallback( + (event: KeyboardEvent) => { + if (event.key === "Enter") { + handleSubmit(); + } + }, + [handleSubmit] + ); + + return ( + + + + + + + + ); +}; diff --git a/src/lib/pages/interaction/component/drawer/selector/index.tsx b/src/lib/pages/interaction/component/drawer/selector/index.tsx new file mode 100644 index 000000000..031897f8d --- /dev/null +++ b/src/lib/pages/interaction/component/drawer/selector/index.tsx @@ -0,0 +1,45 @@ +import { Box } from "@chakra-ui/react"; + +import type { DisplayMode, SelectedAddress } from "../types"; + +import { ModuleSelectorDisplay } from "./SelectorDisplay"; +import type { ModuleSelectorInputProps } from "./SelectorInput"; +import { ModuleSelectorInput } from "./SelectorInput"; + +interface ModuleSelectorProps extends ModuleSelectorInputProps { + mode: DisplayMode; + selectedAddress: SelectedAddress; +} + +export const ModuleSelector = ({ + mode, + selectedAddress, + setSelectedAddress, + setMode, + setModules, + handleModuleSelect, + closeModal, +}: ModuleSelectorProps) => { + const showDisplay = mode === "display" && Boolean(selectedAddress.address); + return ( + + + + + ); +}; diff --git a/src/lib/pages/interaction/component/drawer/types.ts b/src/lib/pages/interaction/component/drawer/types.ts new file mode 100644 index 000000000..80d96c08b --- /dev/null +++ b/src/lib/pages/interaction/component/drawer/types.ts @@ -0,0 +1,14 @@ +import type { IndexedModule } from "lib/services/moduleService"; +import type { HumanAddr, HexAddr, ExposedFunction } from "lib/types"; + +export interface SelectedAddress { + address: HumanAddr; + hex: HexAddr; +} + +export type DisplayMode = "input" | "display"; + +export type ModuleSelectFunction = ( + selectedModule: IndexedModule, + fn?: ExposedFunction +) => void; diff --git a/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx b/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx deleted file mode 100644 index 5d305e46c..000000000 --- a/src/lib/pages/interaction/component/select-module/ModuleSelectBody.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import type { FlexProps } from "@chakra-ui/react"; -import { Text, Flex, Badge, Skeleton, Button, Heading } from "@chakra-ui/react"; - -import { CountBadge } from "lib/components/module/CountBadge"; -import { FunctionCard } from "lib/components/module/FunctionCard"; -import { ModuleCard } from "lib/components/module/ModuleCard"; -// import { ModuleEmptyState } from "./ModuleEmptyState"; - -const functionSelectBaseStyle: FlexProps = { - border: "1px solid", - borderRadius: 8, - borderColor: "gray.700", - p: 4, - direction: "column", -}; - -export const ModuleSelectBody = () => { - return ( - <> - - {/* Left */} - - - input here - - - - - Modules - - - 0 - - - - - - - {/* Right */} - {/* - - */} - - - Module name goes here - - input here - - - - - View Functions - - - - - - - - - - - Execute Functions - - - - - - - - - - - - - ); -}; diff --git a/src/lib/pages/interaction/component/select-module/ModuleSelector.tsx b/src/lib/pages/interaction/component/select-module/ModuleSelector.tsx deleted file mode 100644 index 66d9110b2..000000000 --- a/src/lib/pages/interaction/component/select-module/ModuleSelector.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { Button, Flex } from "@chakra-ui/react"; - -import { CopyLink } from "lib/components/CopyLink"; -import { CustomIcon } from "lib/components/icon"; -import { LabelText } from "lib/components/LabelText"; - -export const ModuleSelectorInput = () => { - return ( - - - input here - - - - - - - ); -}; - -export const ModuleSelectorDisplay = () => { - return ( - - - - - - - - - - ); -}; diff --git a/src/lib/pages/interaction/hooks/useValidateModuleInput.ts b/src/lib/pages/interaction/hooks/useValidateModuleInput.ts new file mode 100644 index 000000000..33a3b9fbd --- /dev/null +++ b/src/lib/pages/interaction/hooks/useValidateModuleInput.ts @@ -0,0 +1,37 @@ +import { useCallback } from "react"; + +import { + useCurrentChain, + useExampleAddresses, + useValidateAddress, +} from "lib/app-provider"; +import { isHexAddress, truncate } from "lib/utils"; + +export const useValidateModuleInput = () => { + const { validateUserAddress } = useValidateAddress(); + const { + chain: { bech32_prefix: prefix }, + } = useCurrentChain(); + + const { user } = useExampleAddresses(); + const truncateExampleAddr = truncate(user); + const errText = `Input must be address (${truncateExampleAddr} or “0x123...456) or module path (“${truncateExampleAddr}::module_name” or “0x123...456::module_name)`; + + return useCallback( + (input: string): string | null => { + const inputArr = input.split("::"); + // Allow only module path for now + if (inputArr.length > 2) return errText; + const [address, module] = inputArr; + const addrErr = validateUserAddress(address); + const invalidAddress = address.startsWith(prefix) + ? addrErr + : !isHexAddress(address); + + if (invalidAddress || module === "") return errText; + + return null; + }, + [errText, prefix, validateUserAddress] + ); +}; diff --git a/src/lib/pages/interaction/index.tsx b/src/lib/pages/interaction/index.tsx index 1a8f4da28..29c23e530 100644 --- a/src/lib/pages/interaction/index.tsx +++ b/src/lib/pages/interaction/index.tsx @@ -1,17 +1,19 @@ import type { FlexProps } from "@chakra-ui/react"; import { Button, Flex, Heading, Text } from "@chakra-ui/react"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { CountBadge } from "lib/components/module/CountBadge"; import PageContainer from "lib/components/PageContainer"; import { EmptyState } from "lib/components/state"; +import type { IndexedModule } from "lib/services/moduleService"; +import type { ExposedFunction } from "lib/types"; +import { ModuleSelectDrawerTrigger } from "./component/drawer"; import { InteractionTypeSwitch, InteractionTabs, } from "./component/InteractionTypeSwitch"; -import { ModuleSelectDrawerButton } from "./component/ModuleSelectDrawerButton"; const containerBaseStyle: FlexProps = { direction: "column", @@ -25,6 +27,19 @@ const containerBaseStyle: FlexProps = { export const Interaction = () => { const { query, isReady } = useRouter(); const [tab, setTab] = useState(); + // TODO: Remove when wiring up this page + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [module, setModule] = useState(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [selectedFn, setSelectedFn] = useState(); + + const handleModuleSelect = useCallback( + (selectedModule: IndexedModule, fn?: ExposedFunction) => { + setModule(selectedModule); + setSelectedFn(fn); + }, + [] + ); useEffect(() => { if (isReady) { @@ -50,7 +65,7 @@ export const Interaction = () => { my={8} >

Select module to interact with ...

- +
{/* Left side */} diff --git a/src/lib/providers/cosmos-kit.tsx b/src/lib/providers/cosmos-kit.tsx index fe53fc3f0..ba27b0a0d 100644 --- a/src/lib/providers/cosmos-kit.tsx +++ b/src/lib/providers/cosmos-kit.tsx @@ -4,6 +4,10 @@ import { assets, chains } from "chain-registry"; import { CHAIN_CONFIGS } from "config/chain"; import { useCelatoneApp } from "lib/app-provider"; +import { + initiatestnet, + initiatestnetAssets, +} from "lib/chain-registry/initiatestnet"; import { localosmosis, localosmosisAsset, @@ -35,12 +39,13 @@ export const ChainProvider = ({ children }: { children: React.ReactNode }) => { return ( { endpoints: availableChainsEndpoints, }} signerOptions={{ - preferredSignType: () => "direct", + preferredSignType: () => "amino", }} > {children} diff --git a/src/lib/services/module.ts b/src/lib/services/module.ts new file mode 100644 index 000000000..7daad4658 --- /dev/null +++ b/src/lib/services/module.ts @@ -0,0 +1,35 @@ +import axios from "axios"; + +import type { + AccountAddr, + ResponseModule, + ResponseModules, + InternalModule, +} from "lib/types"; +import { snakeToCamel } from "lib/utils"; + +interface ModuleReturn { + module: ResponseModule; +} + +export const getAccountModules = async ( + baseEndpoint: string, + address: AccountAddr +): Promise => { + const { data } = await axios.get( + `${baseEndpoint}/initia/move/v1/accounts/${address}/modules` + ); + // Implement LCD pagination + return snakeToCamel(data.modules); +}; + +export const getAccountModule = async ( + baseEndpoint: string, + address: AccountAddr, + moduleName: string +): Promise => { + const { data } = await axios.get( + `${baseEndpoint}/initia/move/v1/accounts/${address}/modules/${moduleName}` + ); + return snakeToCamel(data.module); +}; diff --git a/src/lib/services/moduleService.ts b/src/lib/services/moduleService.ts new file mode 100644 index 000000000..a10144ca0 --- /dev/null +++ b/src/lib/services/moduleService.ts @@ -0,0 +1,67 @@ +import type { + QueryFunction, + UseQueryOptions, + UseQueryResult, +} from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; + +import { CELATONE_QUERY_KEYS, useBaseApiRoute } from "lib/app-provider"; +import type { + AccountAddr, + ExposedFunction, + InternalModule, + ResponseABI, +} from "lib/types"; +import { parseJsonABI, splitViewExecuteFunctions } from "lib/utils/abi"; + +import { getAccountModule, getAccountModules } from "./module"; + +export interface IndexedModule extends InternalModule { + parsedAbi: ResponseABI; + viewFunctions: ExposedFunction[]; + executeFunctions: ExposedFunction[]; +} + +const indexModuleResponse = (module: InternalModule): IndexedModule => { + const parsedAbi = parseJsonABI(module.abi); + const { view, execute } = splitViewExecuteFunctions( + parsedAbi.exposed_functions + ); + return { + ...module, + parsedAbi, + viewFunctions: view, + executeFunctions: execute, + }; +}; + +export const useAddressModules = ({ + address, + moduleName, + options = {}, +}: { + address: AccountAddr; + moduleName: string; + options?: Omit, "queryKey">; +}): UseQueryResult => { + const baseEndpoint = useBaseApiRoute("rest"); + const queryFn: QueryFunction = () => + moduleName + ? getAccountModule(baseEndpoint, address, moduleName).then((module) => + indexModuleResponse(module) + ) + : getAccountModules(baseEndpoint, address).then((modules) => + modules.map((module) => indexModuleResponse(module)) + ); + + return useQuery( + [ + CELATONE_QUERY_KEYS.ACCOUNT_MODULES, + baseEndpoint, + address as string, + moduleName, + ], + queryFn, + options + ); +}; diff --git a/src/lib/types/abi.ts b/src/lib/types/abi.ts new file mode 100644 index 000000000..369de68d7 --- /dev/null +++ b/src/lib/types/abi.ts @@ -0,0 +1,81 @@ +import type { HexAddr } from "./addrs"; +import type { SnakeToCamelCaseNested } from "./converter"; + +export enum UpgradePolicy { + ARBITRARY = "ARBITRARY", + COMPATIBLE = "COMPATIBLE", + IMMUTABLE = "IMMUTABLE", +} + +export type Visibility = "public" | "friend" | "private" | "script"; + +export interface ABIModule { + address: string; + name: string; + functions: ABIFunction[]; +} + +interface ABIFunction { + method: "query" | "tx"; + moduleAddress: string; + moduleName: string; + functionName: string; + typeArgsLength: number; + argsTypes: string[]; +} + +/* response */ +export interface ResponseModule { + address: string; + module_name: string; + abi: string; + raw_bytes: string; + upgrade_policy: UpgradePolicy; +} + +export interface ResponseModules { + modules: ResponseModule[]; + pagination: ModulePagination; +} + +export interface ModulePagination { + next_key: null; + total: string; +} + +export interface ResponseABI { + address: HexAddr; + name: string; + friends: string[]; + exposed_functions: ExposedFunction[]; + structs: Struct[]; +} + +export type InternalModule = SnakeToCamelCaseNested; + +export interface ExposedFunction { + name: string; + visibility: Visibility; + is_view: boolean; + is_entry: boolean; + generic_type_params: GenericTypeParam[]; + params: string[]; + return: string[]; +} + +interface Struct { + name: string; + is_native: boolean; + abilities: string[]; + generic_type_params: GenericTypeParam[]; + fields: Field[]; +} + +interface GenericTypeParam { + constraints: string[]; +} + +interface Field { + name: string; + type: string; +} diff --git a/src/lib/types/addrs.ts b/src/lib/types/addrs.ts index 3d1deb162..d1d3db035 100644 --- a/src/lib/types/addrs.ts +++ b/src/lib/types/addrs.ts @@ -1,7 +1,9 @@ import type { NominalType } from "./currency/common"; export type HumanAddr = string & NominalType<"HumanAddr">; +export type HexAddr = string & NominalType<"HexAddr">; export type ContractAddr = string & NominalType<"ContractAddr">; export type ValidatorAddr = string & NominalType<"ValidatorAddr">; export type Addr = HumanAddr | ContractAddr; +export type AccountAddr = HumanAddr | HexAddr; diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 64b2db555..2796947ca 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -15,3 +15,4 @@ export * from "./upload"; export * from "./block"; export * from "./asset"; export * from "./converter"; +export * from "./abi"; diff --git a/src/lib/utils/abi.ts b/src/lib/utils/abi.ts new file mode 100644 index 000000000..8f50645fc --- /dev/null +++ b/src/lib/utils/abi.ts @@ -0,0 +1,20 @@ +import type { ExposedFunction, ResponseABI } from "lib/types"; + +export const parseJsonABI = (jsonString: string): ResponseABI => { + try { + return JSON.parse(jsonString); + } catch { + throw new Error("Fail to parse ABI json string"); + } +}; + +export const splitViewExecuteFunctions = (functions: ExposedFunction[]) => { + const functionMap: { [key in "view" | "execute"]: ExposedFunction[] } = { + view: [], + execute: [], + }; + functions.forEach((fn) => + fn.is_view ? functionMap.view.push(fn) : functionMap.execute.push(fn) + ); + return functionMap; +}; diff --git a/src/lib/utils/address.ts b/src/lib/utils/address.ts index 3388b2e25..c3f258b9d 100644 --- a/src/lib/utils/address.ts +++ b/src/lib/utils/address.ts @@ -1,4 +1,7 @@ +import { fromBech32, fromHex, toBech32, toHex } from "@cosmjs/encoding"; + import type { AddressReturnType } from "lib/app-provider"; +import type { HexAddr, HumanAddr } from "lib/types"; export const getAddressTypeText = (addressType: AddressReturnType) => { switch (addressType) { @@ -13,3 +16,15 @@ export const getAddressTypeText = (addressType: AddressReturnType) => { return "(Invalid Address)"; } }; + +export const bech32AddressToHex = (addr: HumanAddr): HexAddr => + "0x".concat(toHex(fromBech32(addr).data)) as HexAddr; + +export const hexToBech32Address = ( + prefix: string, + hexAddr: HexAddr +): HumanAddr => { + let strip = hexAddr.replace("0x", ""); + if (strip.length < 40) strip = strip.padStart(40, "0"); + return toBech32(prefix, fromHex(strip)) as HumanAddr; +}; diff --git a/src/lib/utils/validate.ts b/src/lib/utils/validate.ts index b9e9ff2e7..58605251c 100644 --- a/src/lib/utils/validate.ts +++ b/src/lib/utils/validate.ts @@ -18,3 +18,13 @@ export const isBlock = (input: string): boolean => { const numberValue = Number(input); return Number.isInteger(numberValue) && numberValue > 0; }; + +export const isHexAddress = (address: string): boolean => { + const strip = address.replace("0x", ""); + try { + fromHex(strip); + } catch { + return false; + } + return strip.length <= 40; +}; From 02190a06d9c31d9f8d158b7b5840724667ac5d4f Mon Sep 17 00:00:00 2001 From: Jennie Alles Date: Mon, 4 Sep 2023 17:50:02 +0700 Subject: [PATCH 018/368] feat(components): layout with sticky multiple sidebar --- src/lib/layout/index.tsx | 4 +- src/lib/pages/publish-module/index.tsx | 275 ++++++++++++++++++++++++- 2 files changed, 269 insertions(+), 10 deletions(-) diff --git a/src/lib/layout/index.tsx b/src/lib/layout/index.tsx index 180746438..a74dbb9d7 100644 --- a/src/lib/layout/index.tsx +++ b/src/lib/layout/index.tsx @@ -90,7 +90,9 @@ const Layout = ({ children }: LayoutProps) => { )} -
{children}
+
+ {children} +