diff --git a/developer-tools/dashboard/index.md b/developer-tools/dashboard/index.md index 5135c082816..824c821396b 100644 --- a/developer-tools/dashboard/index.md +++ b/developer-tools/dashboard/index.md @@ -9,7 +9,7 @@ import Banner from '@site/src/components/Banner' # Infura dashboard documentation -The Infura dashboard provides users with a comprehensive overview and control of their blockchain infrastructure. It serves +The Infura dashboard provides developers with a comprehensive overview and control of their blockchain infrastructure. It serves as a central hub for managing API keys and access, monitoring usage, and accessing account and billing information. diff --git a/docusaurus.config.js b/docusaurus.config.js index fb31c55b729..bb9a7a3d0ca 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -199,6 +199,11 @@ const config = { label: "What's new?", position: "right", }, + { + href: "https://support.metamask.io/", + label: "User support", + position: "right", + }, /* Language drop down { type: "localeDropdown", @@ -225,17 +230,9 @@ const config = { title: "Documentation", items: [ { - label: "Home", - to: "/", - }, - { - label: "MetaMask wallet", + label: "Wallet", to: "/wallet", }, - { - label: "MetaMask SDK", - to: "/wallet/how-to/use-sdk", - }, { label: "Snaps", to: "/snaps", @@ -244,6 +241,10 @@ const config = { label: "Services", to: "/services", }, + { + label: "Infura dashboard", + to: "/developer-tools/dashboard", + }, ], }, { @@ -282,6 +283,10 @@ const config = { label: "Contribute to the docs", href: "https://github.com/MetaMask/metamask-docs/blob/main/CONTRIBUTING.md", }, + { + label: "MetaMask user support", + href: "https://support.metamask.io/", + }, ], }, { diff --git a/services/reference/blast/index.md b/services/reference/blast/index.md index f269746918f..920203f3525 100644 --- a/services/reference/blast/index.md +++ b/services/reference/blast/index.md @@ -11,6 +11,8 @@ import CardList from '@site/src/components/CardList' Blast is supported through the [DIN](https://www.infura.io/solutions/decentralized-infrastructure-service) service, meaning calls to the network are routed to [partner infrastructure providers](#partners-and-privacy-policies). +Infura provides Open Beta access to Blast. During this period, there might be feature limitations. Performance issues are not expected, but they are possible as we optimize and stabilize the service. + ::: Blast is an Ethereum layer 2 (L2) scaling solution that uses an optimistic rollup architecture, providing a diff --git a/services/reference/mantle/index.md b/services/reference/mantle/index.md index 6e0ae5e030c..f77cef6c9b4 100644 --- a/services/reference/mantle/index.md +++ b/services/reference/mantle/index.md @@ -11,6 +11,8 @@ import CardList from '@site/src/components/CardList' Mantle is supported through the [DIN](https://www.infura.io/solutions/decentralized-infrastructure-service) service, meaning calls to the network are routed to [partner infrastructure providers](#partners-and-privacy-policies). +Infura provides Open Beta access to Mantle. During this period, there might be feature limitations. Performance issues are not expected, but they are possible as we optimize and stabilize the service. + ::: Mantle is an Ethereum layer 2 (L2) scaling solution that leverages an optimistic rollup architecture diff --git a/services/reference/zksync/index.md b/services/reference/zksync/index.md index 6efffa6bfa2..0719d34bd81 100644 --- a/services/reference/zksync/index.md +++ b/services/reference/zksync/index.md @@ -11,7 +11,7 @@ import CardList from '@site/src/components/CardList' ZKsync Era is supported through the [DIN](https://www.infura.io/solutions/decentralized-infrastructure-service) service, meaning calls to the network are routed to [partner infrastructure providers](#partners-and-privacy-policies). -Infura provides public beta access to ZKsync Era. +Infura provides Open Beta access to the ZKsync Era network. During this period, there might be feature limitations. Performance issues are not expected, but they are possible as we optimize and stabilize the service. Currently, historical state data starting from June 19th, 2024 is available. Full historical state information for ZKsync Era will be announced when it is available. diff --git a/snaps/index.mdx b/snaps/index.mdx index e4fb5e1ee27..843f95f0898 100644 --- a/snaps/index.mdx +++ b/snaps/index.mdx @@ -189,3 +189,7 @@ MetaMask Snaps team and community on [GitHub discussions](https://github.com/Met and the **mm-snaps-dev** channel on [Consensys Discord](https://discord.gg/consensys). See the full list of [Snaps resources](learn/resources.md) for more information. + +:::info MetaMask user support +If you need MetaMask user support, visit the [MetaMask Help Center](https://support.metamask.io/). +::: diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 064ae73de6a..7ed76237c3a 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -11,7 +11,7 @@ export type CardItem = { export default function Card({ title, link, description }: CardItem) { return ( -
+

{title}

diff --git a/src/components/CardSection.tsx b/src/components/CardSection.tsx new file mode 100644 index 00000000000..e22b4972ae6 --- /dev/null +++ b/src/components/CardSection.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import Card, { type CardItem } from "@site/src/components/Card"; + +const CardList: CardItem[] = [ + { + title: "Wallet", + link: "/wallet", + description: (<> + Integrate your dapp with MetaMask using the Wallet API. You can interact with your users' Ethereum accounts from multiple dapp platforms. + ), + }, + { + title: "Snaps", + link: "/snaps", + description: (<> + Extend the functionality of MetaMask using Snaps. You can create a Snap to add support for custom networks, account types, APIs, and more. + ), + }, + { + title: "Services", + link: "/services", + description: (<> + Power your dapp or Snap using services provided by MetaMask and Infura. This includes APIs aimed at optimizing essential development tasks. + ), + }, + { + title: "Infura dashboard", + link: "/developer-tools/dashboard", + description: (<> + Use the Infura dashboard as a central hub for managing your Infura API keys, monitoring usage, and accessing account and billing information. + ), + }, +]; + +export default function CardSection(): JSX.Element { + return ( +
+
+ {CardList.map((props, idx) => ())} +
+
+ ); +} diff --git a/src/components/CodeTerminal/CodeTerminal.jsx b/src/components/CodeTerminal/CodeTerminal.jsx index fc05c121cf0..068bbc94f32 100644 --- a/src/components/CodeTerminal/CodeTerminal.jsx +++ b/src/components/CodeTerminal/CodeTerminal.jsx @@ -1,13 +1,11 @@ -import React, { useState, useMemo } from "react" -import useUser from "@site/src/hooks/useUser" -import TerminalViewBox from "./TerminalViewBox" -import ControlPanel from "./ControlPanel" -import { INFO_MSG } from "./AlertMsg" -import MessageBox from "@site/src/components/MessageBox/MessageBox" -import Select from "react-dropdown-select" -import { INIT_REQ_SET } from "@site/src/lib/constants" -import { trackClickForSegmentAnalytics } from "@site/src/lib/segmentAnalytics" -import Heading from "@theme/Heading" +import React, { useState, useMemo } from "react"; +import useUser from "@site/src/hooks/useUser"; +import TerminalViewBox from "./TerminalViewBox"; +import ControlPanel from "./ControlPanel"; +import { INFO_MSG } from "./AlertMsg"; +import MessageBox from "@site/src/components/MessageBox/MessageBox"; +import { INIT_REQ_SET } from "@site/src/lib/constants"; +import Heading from '@theme/Heading' const CodeTerminal = () => { const { user, keys, loading: keysLoading } = useUser() @@ -98,12 +96,6 @@ const CodeTerminal = () => { method: "GET", } } - trackClickForSegmentAnalytics({ - type: "Submit Button", - clickText: "Send Request", - location: "https://docs.infura.io/", - userId: user.id, - }) try { const res = await fetch(URL, params) if (res.ok) { diff --git a/src/components/ParserOpenRPC/AuthBox/index.tsx b/src/components/ParserOpenRPC/AuthBox/index.tsx index 4ae3435e95b..fb761b0ec08 100644 --- a/src/components/ParserOpenRPC/AuthBox/index.tsx +++ b/src/components/ParserOpenRPC/AuthBox/index.tsx @@ -1,10 +1,13 @@ -import React from "react" -import Link from "@docusaurus/Link" -import styles from "./styles.module.css" -import global from "../global.module.css" +import React from "react"; +import Link from "@docusaurus/Link"; +import styles from "./styles.module.css"; +import global from "../global.module.css"; +import { EIP6963ProviderDetail } from "@site/src/hooks/store.ts" interface AuthBoxProps { - isMetamaskInstalled: boolean + metamaskProviders: any; + handleConnect: (i) => void; + selectedProvider?: number; } const MetamaskInstallMessage = () => ( @@ -19,6 +22,39 @@ const MetamaskInstallMessage = () => (
) +<<<<<<< HEAD export const AuthBox = ({ isMetamaskInstalled }: AuthBoxProps) => { return <>{!isMetamaskInstalled ? : null} } +======= +export const AuthBox = ({ metamaskProviders = [], selectedProvider, handleConnect }: AuthBoxProps) => { + if (metamaskProviders.length === 0) { + return + } + + if (metamaskProviders.length > 0) { + return null + } + + return ( +
+

Select a MetaMask provider to use interactive features:

+
+ {metamaskProviders.map((provider: EIP6963ProviderDetail, i) => ( +
+ +
+ ))} +
+
+ ); +}; +>>>>>>> main diff --git a/src/components/ParserOpenRPC/AuthBox/styles.module.css b/src/components/ParserOpenRPC/AuthBox/styles.module.css index 583582310b6..566c4efd249 100644 --- a/src/components/ParserOpenRPC/AuthBox/styles.module.css +++ b/src/components/ParserOpenRPC/AuthBox/styles.module.css @@ -17,3 +17,37 @@ font-size: 14px; line-height: 22px; } + +.mmBtnRow { + display: flex; + flex-wrap: wrap; +} + +.mmBtnCol { + width: 100%; + margin-bottom: 10px; +} + +.mmBtn { + position: relative; + display: flex; + align-items: center; + padding: 16px 30px 16px 20px; + width: 100%; + background: none; + border-radius: 10px; + outline: none; + font-size: 16px; + font-weight: 500; + line-height: 22px; + border: 1px solid rgba(3, 118, 201, 1); + color: rgba(3, 118, 201, 1); +} + +.mmBtnCheck { + position: absolute; + top: 50%; + transform: translateY(-50%); + right: 15px; + font-size: 16px; +} diff --git a/src/components/ParserOpenRPC/CollapseBox/CollapseBox.tsx b/src/components/ParserOpenRPC/CollapseBox/CollapseBox.tsx index b6b5efa4d23..665d0451fec 100644 --- a/src/components/ParserOpenRPC/CollapseBox/CollapseBox.tsx +++ b/src/components/ParserOpenRPC/CollapseBox/CollapseBox.tsx @@ -46,9 +46,7 @@ export const CollapseBox = ({ )} >
- - {children} - + {children}
) } diff --git a/src/components/ParserOpenRPC/CollapseBox/styles.module.css b/src/components/ParserOpenRPC/CollapseBox/styles.module.css index da14b4defc7..f7dcb0bb5fa 100644 --- a/src/components/ParserOpenRPC/CollapseBox/styles.module.css +++ b/src/components/ParserOpenRPC/CollapseBox/styles.module.css @@ -22,9 +22,9 @@ padding: 12px 22px; margin-bottom: 1px; cursor: pointer; - transition-property: "border-color", "background-color"; - transition-duration: 0.2s; - transition-timing-function: ease; + /*transition-property: 'border-color', 'background-color';*/ + /*transition-duration: .2s;*/ + /*transition-timing-function: ease;*/ } .collapseIcon { @@ -34,7 +34,7 @@ border-top: 2px solid #0376c9; border-right: 2px solid #0376c9; transform: rotate(45deg); - transition: transform 0.2s ease; + /*transition: transform .2s ease;*/ } .collapseBtn:hover { @@ -61,6 +61,7 @@ border-color: transparent; width: 100%; border-radius: 0; + border-bottom: 1px solid #848C96; } .collapsedBtnView:hover { diff --git a/src/components/ParserOpenRPC/DetailsBox/styles.module.css b/src/components/ParserOpenRPC/DetailsBox/styles.module.css index 0be0fc09a89..cc1a3f5c1ec 100644 --- a/src/components/ParserOpenRPC/DetailsBox/styles.module.css +++ b/src/components/ParserOpenRPC/DetailsBox/styles.module.css @@ -19,10 +19,13 @@ } .paramItemWrapper { - border-top: 1px solid #848c96; padding: 0 1rem; } +.paramItemWrapper:not(:first-child) { + border-top: 1px solid #848C96; +} + .borderTopLine { border-top: 1px solid #848c96; } diff --git a/src/components/ParserOpenRPC/InteractiveBox/fields/ConditionalField.tsx b/src/components/ParserOpenRPC/InteractiveBox/fields/ConditionalField.tsx index b6632ace6a4..c8d1792f925 100644 --- a/src/components/ParserOpenRPC/InteractiveBox/fields/ConditionalField.tsx +++ b/src/components/ParserOpenRPC/InteractiveBox/fields/ConditionalField.tsx @@ -77,13 +77,8 @@ export const ConditionalField = (props: FieldTemplateProps) => { className={clsx(styles.tableValueRow, styles.tableValueRowPadding)} > {formData === undefined ? "" : String(formData)} - - { - setIsOpened(!isOpened) - }} - > + { setIsOpened(!isOpened); }}> + {schema?.anyOf ? "anyOf" : "oneOf"} { onClick={onDropdownOptionClick} data-value={listItem.title} > - {`${listItem.title}: ${listItem?.enum ? "enum" : listItem.type}`} + {listItem.title} ))} diff --git a/src/components/ParserOpenRPC/InteractiveBox/styles.module.css b/src/components/ParserOpenRPC/InteractiveBox/styles.module.css index 2995312d1ac..3ad7ea4669d 100644 --- a/src/components/ParserOpenRPC/InteractiveBox/styles.module.css +++ b/src/components/ParserOpenRPC/InteractiveBox/styles.module.css @@ -61,10 +61,18 @@ line-height: 24px; font-size: 14px; } +.tableColumnTypeDropdown { + width: 100%; + justify-content: flex-end; +} +.tableColumnTypeDropdown:hover { + cursor: pointer; +} .tableLabelIconError { - width: 11px; + position: absolute; + left: 4px; + width: 13px; height: 11px; - margin-left: 8px; background: url("/img/icons/error-icon.svg") no-repeat 50% 50%; } .tableColumnIcon { @@ -105,9 +113,6 @@ .deleteIconCentered { top: 50%; } -.deleteIconComplex { - top: 27px; -} .dropdown { display: flex; align-items: center; diff --git a/src/components/ParserOpenRPC/InteractiveBox/templates/ArrayFieldTemplate.tsx b/src/components/ParserOpenRPC/InteractiveBox/templates/ArrayFieldTemplate.tsx index 15539519055..2e1790324d4 100644 --- a/src/components/ParserOpenRPC/InteractiveBox/templates/ArrayFieldTemplate.tsx +++ b/src/components/ParserOpenRPC/InteractiveBox/templates/ArrayFieldTemplate.tsx @@ -1,41 +1,38 @@ -import React, { useContext, useState } from "react" -import { useCollapsible, Collapsible } from "@docusaurus/theme-common" -import { ArrayFieldTemplateProps } from "@rjsf/utils" -import { BaseInputTemplate } from "@site/src/components/ParserOpenRPC/InteractiveBox/templates/BaseInputTemplate" -import styles from "@site/src/components/ParserOpenRPC/InteractiveBox/styles.module.css" -import clsx from "clsx" -import { ParserOpenRPCContext } from "@site/src/components/ParserOpenRPC" +import React, { useContext, useEffect, useState } from "react"; +import { useCollapsible, Collapsible } from "@docusaurus/theme-common"; +import { ArrayFieldTemplateProps } from "@rjsf/utils"; +import { BaseInputTemplate } from "@site/src/components/ParserOpenRPC/InteractiveBox/templates/BaseInputTemplate"; +import styles from "@site/src/components/ParserOpenRPC/InteractiveBox/styles.module.css"; +import clsx from "clsx"; +import { ParserOpenRPCContext } from "@site/src/components/ParserOpenRPC"; -export const ArrayFieldTemplate = ({ - items, - canAdd, - onAddClick, - title, - schema, - formData, - formContext, -}: ArrayFieldTemplateProps) => { - const [isComplexArrayEditView, setIsComplexArrayEditView] = useState(false) - const { - setIsDrawerContentFixed, - setDrawerLabel, - isComplexTypeView, - setIsComplexTypeView, - } = useContext(ParserOpenRPCContext) - const { collapsed, toggleCollapsed } = useCollapsible({ initialState: true }) - const itemsType = schema?.items?.type - const isSimpleArray = - itemsType === "string" || - itemsType === "boolean" || - itemsType === "number" || - itemsType === "integer" +export const ArrayFieldTemplate = ({ items, canAdd, onAddClick, title, schema, formData }: ArrayFieldTemplateProps) => { + const [isComplexArrayEditView, setIsComplexArrayEditView] = useState(false); + const { setIsDrawerContentFixed, setDrawerLabel, isComplexTypeView, setIsComplexTypeView } = useContext(ParserOpenRPCContext); + const { collapsed, toggleCollapsed } = useCollapsible({ initialState: true }); + const itemsType = schema?.items?.type; + const isSimpleArray = itemsType === "string" || itemsType === "boolean" || itemsType === "number" || itemsType === "integer"; const addComplexArray = () => { - onAddClick() - setDrawerLabel(title) - setIsDrawerContentFixed(true) - setIsComplexArrayEditView(true) - setIsComplexTypeView(true) + if(formData?.length === 0) { + onAddClick(); + } + setDrawerLabel(title); + setIsDrawerContentFixed(true); + setIsComplexArrayEditView(true); + setIsComplexTypeView(true); } + const addSimpleArray = () => { + toggleCollapsed(); + if(collapsed && formData?.length === 0) { + onAddClick(); + } + } + + useEffect(() => { + if (!isComplexTypeView) { + setIsComplexArrayEditView(false); + } + }, [isComplexTypeView]) return (
@@ -51,9 +48,8 @@ export const ArrayFieldTemplate = ({ {JSON.stringify(formData, null, " ")}
+ className={clsx(styles.tableColumnType, styles.tableColumnTypeDropdown)} + onClick={isSimpleArray ? addSimpleArray : addComplexArray}> {schema.type} {items.map(({ children, index, onDropIndexClick, hasRemove }) => (
+ {hasRemove ? + : + null + } {children} - {hasRemove && ( - - )}
))} {canAdd ? ( diff --git a/src/components/ParserOpenRPC/InteractiveBox/templates/BaseInputTemplate.tsx b/src/components/ParserOpenRPC/InteractiveBox/templates/BaseInputTemplate.tsx index 9ca620f70aa..1e942a39d8b 100644 --- a/src/components/ParserOpenRPC/InteractiveBox/templates/BaseInputTemplate.tsx +++ b/src/components/ParserOpenRPC/InteractiveBox/templates/BaseInputTemplate.tsx @@ -26,14 +26,11 @@ export const BaseInputTemplate = ({ const [isFocused, setIsFocused] = useState(false) const [inputValue, setInputValue] = useState(isNumber ? 0 : "") - const { isFormReseted } = formContext - const hasErrors = rawErrors?.length > 0 && !hideError - const debouncedOnChange = useCallback( - debounce((e, isInputNumber = false) => { - onChange(isInputNumber ? e : e?.target?.value) - }, 300), - [] - ) + const { isFormReseted } = formContext; + const hasErrors = rawErrors?.length > 0 && !hideError && value !== ""; + const debouncedOnChange = useCallback(debounce((e, isInputNumber = false) => { + onChange(isInputNumber ? e : e?.target?.value); + }, 300), []); const onInputChange = (e) => { setInputValue(e?.target?.value) debouncedOnChange(e) @@ -55,26 +52,15 @@ export const BaseInputTemplate = ({
{!isArray && (
-
)}
- +
+ {hasErrors && !isNumber ? : null} {value === undefined ? "" : String(value)} - - { - setIsOpened(!isOpened) - }} - > + { setIsOpened(!isOpened); }}> + {schema.type} {emptyValue ? "" : String(value)} - - { - setIsOpened(!isOpened) - }} - > - {schema?.enum ? "enum" : schema?.type} - - + { setIsOpened(!isOpened); }}> + + {schema?.enum ? 'enum' : schema?.type} + +
    (null) export default function ParserOpenRPC({ network, method }: ParserProps) { - if (!method || !network) return null - const [metamaskInstalled, setMetamaskInstalled] = useState(false) - const [isModalOpen, setModalOpen] = useState(false) - const [reqResult, setReqResult] = useState(null) - const [paramsData, setParamsData] = useState([]) - const [isDrawerContentFixed, setIsDrawerContentFixed] = useState(false) - const [drawerLabel, setDrawerLabel] = useState(null) - const [isComplexTypeView, setIsComplexTypeView] = useState(false) - const { colorMode } = useColorMode() - const openModal = () => setModalOpen(true) - const closeModal = () => setModalOpen(false) - - const { netData } = usePluginData("plugin-json-rpc") as { - netData?: ResponseItem[] - } - const currentNetwork = netData.find((net) => net.name === network) + if (!method || !network) return null; + const [isModalOpen, setModalOpen] = useState(false); + const [reqResult, setReqResult] = useState(null); + const [paramsData, setParamsData] = useState([]); + const [isDrawerContentFixed, setIsDrawerContentFixed] = useState(false); + const [drawerLabel, setDrawerLabel] = useState(null); + const [isComplexTypeView, setIsComplexTypeView] = useState(false); + const { colorMode } = useColorMode(); + const openModal = () => { + setModalOpen(true); + trackClickForSegment({ + eventName: "Customize Request", + clickType: "Customize Request", + userExperience: "B" + }) + }; + const closeModal = () => setModalOpen(false); + + const { netData } = usePluginData("plugin-json-rpc") as { netData?: ResponseItem[] }; + const currentNetwork = netData?.find(net => net.name === network); if (!currentNetwork && currentNetwork.error) return null @@ -92,10 +99,30 @@ export default function ParserOpenRPC({ network, method }: ParserProps) { if (currentMethodData === null) return null +<<<<<<< HEAD useEffect(() => { const installed = !!(window as any)?.ethereum setMetamaskInstalled(installed) }, []) +======= + const location = useLocation(); + + const [selectedWallet, setSelectedWallet] = useState(0); + const providers = useSyncProviders(); + + const handleConnect = (i:number) => { + setSelectedWallet(i); + } + + const metamaskProviders = useMemo(() => { + const isMetamasks = providers.filter(pr => pr?.info?.name?.includes("MetaMask")); + if (isMetamasks.length > 1) { + const indexWallet = isMetamasks.findIndex(item => item.info.name === "MetaMask"); + setSelectedWallet(indexWallet); + } + return isMetamasks; + }, [providers]); +>>>>>>> main const onParamsChangeHandle = (data) => { if ( @@ -105,16 +132,35 @@ export default function ParserOpenRPC({ network, method }: ParserProps) { ) { setParamsData([]) } +<<<<<<< HEAD setParamsData(Object.values(data)) +======= + setParamsData(Object.values(data)); + trackInputChangeForSegment({ + eventName: "Request Configuration Started", + userExperience: "B" + }) +>>>>>>> main } const onSubmitRequestHandle = async () => { + if (metamaskProviders.length === 0) return try { - const response = await (window as any).ethereum.request({ + const response = await metamaskProviders[selectedWallet].provider.request({ method: method, params: paramsData, }) +<<<<<<< HEAD setReqResult(response) +======= + setReqResult(response); + trackClickForSegment({ + eventName: "Request Sent", + clickType: "Request Sent", + userExperience: "B", + ...(response?.code && { responseStatus: response.code }) + }) +>>>>>>> main } catch (e) { setReqResult(e) } @@ -195,9 +241,13 @@ export default function ParserOpenRPC({ network, method }: ParserProps) {
- + 0} method={method} params={currentMethodData.params} paramsData={paramsData} diff --git a/src/components/SnapsSection.tsx b/src/components/SnapsSection.tsx index d9d57072a38..7355b1c0707 100644 --- a/src/components/SnapsSection.tsx +++ b/src/components/SnapsSection.tsx @@ -52,3 +52,4 @@ export default function SnapsSection(): JSX.Element { ) } + diff --git a/src/components/WalletSection.tsx b/src/components/WalletSection.tsx index c32d69ce38d..e69de29bb2d 100644 --- a/src/components/WalletSection.tsx +++ b/src/components/WalletSection.tsx @@ -1,54 +0,0 @@ -import React from "react" -import Card, { type CardItem } from "@site/src/components/Card" - -const CardList: CardItem[] = [ - { - title: "🏁 Wallet quickstart", - link: "/wallet/how-to/connect", - description: ( - <> - Get started quickly by connecting your React dapp to MetaMask and other - wallets in your users' browsers. - - ), - }, - { - title: "⚙️ Wallet tutorials", - link: "/wallet/tutorials", - description: ( - <> - Follow the step-by-step tutorials to create a simple React dapp and - integrate it with MetaMask. - - ), - }, - { - title: "🌐 Wallet API", - link: "/wallet/reference/json-rpc-api", - description: ( - <> - Use the JSON-RPC methods of MetaMask's Wallet API to interact with - your users' Ethereum accounts. - - ), - }, -] - -export default function WalletSection(): JSX.Element { - return ( -
-

Integrate your dapp with the MetaMask wallet

-

- Your dapp can use the Wallet API to request users' Ethereum - accounts, read data from connected blockchains, suggest that the user - sign messages and transactions, and perform other functions on MetaMask - from multiple dapp platforms. -

-
- {CardList.map((props, idx) => ( - - ))} -
-
- ) -} diff --git a/src/css/custom.css b/src/css/custom.css index 7883375c35c..ed7f5313ae8 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -324,6 +324,10 @@ button:hover { border-top-color: rgba(20, 22, 24, 1); } +[data-theme="light"] .tippy-popper[x-placement^=bottom] [x-arrow] { + border-bottom-color: rgba(20, 22, 24, 1); + } + [data-theme="dark"] .tippy-tooltip { background-color: rgba(255, 255, 255, 1); color: rgba(20, 22, 24, 1); @@ -332,3 +336,13 @@ button:hover { [data-theme="dark"] .tippy-popper[x-placement^="top"] [x-arrow] { border-top-color: rgba(255, 255, 255, 1); } + +[data-theme="dark"] .tippy-popper[x-placement^=bottom] [x-arrow] { + border-bottom-color: rgba(255, 255, 255, 1); +} + +@media (max-width: 1200px) { + .navbar__item, .navbar__link { + font-size: 14px; + } +} diff --git a/src/hooks/store.ts b/src/hooks/store.ts new file mode 100644 index 00000000000..2556b9d107b --- /dev/null +++ b/src/hooks/store.ts @@ -0,0 +1,49 @@ +declare global{ + interface WindowEventMap { + "eip6963:announceProvider": CustomEvent + } +} + +export interface EIP6963ProviderInfo { + walletId: string; + uuid: string; + name: string; + icon: string; +} + +export interface EIP1193Provider { + isStatus?: boolean; + host?: string; + path?: string; + sendAsync?: (request: { method: string, params?: Array }, callback: (error: Error | null, response: unknown) => void) => void; + send?: (request: { method: string, params?: Array }, callback: (error: Error | null, response: unknown) => void) => void; + request: (request: { method: string, params?: Array }) => Promise; +} + +export interface EIP6963ProviderDetail { + info: EIP6963ProviderInfo; + provider: EIP1193Provider; +} + +type EIP6963AnnounceProviderEvent = { + detail: { + info: EIP6963ProviderInfo; + provider: EIP1193Provider; + } +} + +let providers: EIP6963ProviderDetail[] = [] +export const store = { + value: ()=> providers, + subscribe: (callback: ()=> void) => { + function onAnnouncement(event: EIP6963AnnounceProviderEvent){ + if(providers.map(p => p.info.uuid).includes(event.detail.info.uuid)) return + providers = [...providers, event.detail] + callback() + } + window.addEventListener("eip6963:announceProvider", onAnnouncement); + window.dispatchEvent(new Event("eip6963:requestProvider")); + + return () => window.removeEventListener("eip6963:announceProvider", onAnnouncement) + } +} \ No newline at end of file diff --git a/src/hooks/useSyncProviders.ts b/src/hooks/useSyncProviders.ts new file mode 100644 index 00000000000..b9db3eff98f --- /dev/null +++ b/src/hooks/useSyncProviders.ts @@ -0,0 +1,4 @@ +import { useSyncExternalStore } from "react"; +import { store } from "./store"; + +export const useSyncProviders = ()=> useSyncExternalStore(store.subscribe, store.value, store.value) \ No newline at end of file diff --git a/src/lib/segmentAnalytics.js b/src/lib/segmentAnalytics.js index 9f7304adad0..19aedbdec77 100644 --- a/src/lib/segmentAnalytics.js +++ b/src/lib/segmentAnalytics.js @@ -1,25 +1,45 @@ -export const trackClickForSegmentAnalytics = ({ - type, - clickUrl, - clickText, - location, - userId, +export const trackPageViewForSegment = ({ + name, + path, + userExperience }) => { if (window.analytics) { - window.analytics.track(`CTA ${type} Clicked`, { - ...(clickUrl && { click_url: clickUrl }), - ...(clickText && { click_text: clickText }), - ...(location && { location }), - ...(userId && { user_id: userId }), - }) + window.analytics.page("Page viewed", name, { + ...(path && { path: path }), + ...(userExperience && { user_experience: userExperience }) + }); } } -export const trackPageForSegmentAnalytics = ({ name, path, userId }) => { +export const trackClickForSegment = ({ + eventName, + clickType, + userExperience, + responseStatus, + responseMsg, + timestamp, +}) => { if (window.analytics) { - window.analytics.page("Page view", name, { - ...(path && { path: path }), - ...(userId && { userId: userId }), - }) + window.analytics.track(`CTA ${clickType} Clicked`, { + ...(eventName && { event_name: eventName }), + ...(userExperience && { user_experience: userExperience }), + ...(responseStatus && { response_status: responseStatus }), + ...(responseMsg && { response_msg: responseMsg }), + ...(timestamp && { timestamp: timestamp }), + }); } -} +}; + +export const trackInputChangeForSegment = ({ + eventName, + userExperience, + timestamp, +}) => { + if (window.analytics) { + window.analytics.track(`Input changed`, { + ...(eventName && { event_name: eventName }), + ...(userExperience && { user_experience: userExperience }), + ...(timestamp && { timestamp: timestamp }), + }); + } +}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 00270e9f3e0..41dd4abe47d 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,11 +1,10 @@ -import React from "react" -import clsx from "clsx" -import Link from "@docusaurus/Link" -import useDocusaurusContext from "@docusaurus/useDocusaurusContext" -import Layout from "@theme/Layout" -import WalletSection from "@site/src/components/WalletSection" -import SnapsSection from "@site/src/components/SnapsSection" -import styles from "./index.module.css" +import React from "react"; +import clsx from "clsx"; +import Link from "@docusaurus/Link"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import Layout from "@theme/Layout"; +import CardSection from "@site/src/components/CardSection"; +import styles from "./index.module.css"; function HomepageHeader() { const { siteConfig } = useDocusaurusContext() @@ -86,8 +85,7 @@ export default function Home(): JSX.Element {
- - +
diff --git a/src/theme/Layout/index.tsx b/src/theme/Layout/index.tsx new file mode 100644 index 00000000000..10714da4a0c --- /dev/null +++ b/src/theme/Layout/index.tsx @@ -0,0 +1,80 @@ +import React, { useState, useEffect, useMemo } from "react"; +import BrowserOnly from '@docusaurus/BrowserOnly'; +import { usePluginData } from "@docusaurus/useGlobalData"; +import ldClient from "launchdarkly"; +import { useLocation } from "@docusaurus/router"; +import Layout from "@theme-original/Layout"; +import ParserOpenRPC from "@site/src/components/ParserOpenRPC"; +import { ResponseItem, NETWORK_NAMES } from "@site/src/plugins/plugin-json-rpc"; +import styles from "./styles.module.css"; + +const REF_FF = "mm-new-reference-enabled"; +const REF_PATH = "/wallet/reference/"; +const EXEPT_METHODS = [ + "wallet_requestPermissions", + "wallet_revokePermissions", + "eth_signTypedData_v4" +]; + +export default function LayoutWrapper({ children }) { + const location = useLocation(); + const { netData } = usePluginData("plugin-json-rpc") as { netData?: ResponseItem[] }; + const [ldReady, setLdReady] = useState(false); + const [newReferenceEnabled, setNewReferenceEnabled] = useState(false); + + const metamaskNetwork = netData?.find(net => net.name === NETWORK_NAMES.metamask); + const metamaskMethods = metamaskNetwork?.data?.methods?.map((item) => item.name) || []; + + const referencePageName = useMemo(() => { + const currentPath = location.pathname; + if (currentPath.includes(REF_PATH) && metamaskMethods.length > 0) { + const methodPath = currentPath.replace(REF_PATH, "").replace("/", ""); + const page = metamaskMethods.find(name => name.toLowerCase() === methodPath && !EXEPT_METHODS.includes(name)); + return page; + } + return false; + }, [location.pathname, metamaskMethods]); + + useEffect(() => { + ldClient.waitUntilReady().then(() => { + setNewReferenceEnabled(ldClient.variation(REF_FF, false)); + setLdReady(true); + }); + const handleChange = (current) => { + setNewReferenceEnabled(current); + }; + ldClient.on(`change:${REF_FF}`, handleChange); + return () => { + ldClient.off(`change:${REF_FF}`, handleChange); + }; + }, []); + + return ( + + { + () => { + return ( + <> + {newReferenceEnabled && ldReady && referencePageName ? ( + +
+ {children?.props?.children[0]?.type === "aside" && ( + <>{children.props.children[0]} + )} +
+
+ +
+
+
+
+ ) : ( + {children} + )} + + ) + } + } +
+ ); +} diff --git a/src/theme/Layout/styles.module.css b/src/theme/Layout/styles.module.css new file mode 100644 index 00000000000..bfd9ca409a2 --- /dev/null +++ b/src/theme/Layout/styles.module.css @@ -0,0 +1,21 @@ +.pageWrapper { + display: flex; + width: 100%; + flex: 1 0 0%; +} + +.mainContainer { + width: 100%; + padding: 20px 30px; +} + +.contentWrapper { + max-width: 1440px; + margin: 0 auto; +} + +@media (width <= 996px) { + .pageWrapper { + display: block; + } +} \ No newline at end of file diff --git a/static/img/icons/minus-icon.svg b/static/img/icons/minus-icon.svg new file mode 100644 index 00000000000..d0149ee598f --- /dev/null +++ b/static/img/icons/minus-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/vercel.json b/vercel.json index cdeae2bedec..3a4b3624c0d 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,6 @@ { "cleanUrls": true, + "trailingSlash": false, "redirects": [ { "source": "/guide", diff --git a/wallet/index.mdx b/wallet/index.mdx index 2f56c237e01..0e1878e5574 100644 --- a/wallet/index.mdx +++ b/wallet/index.mdx @@ -78,3 +78,7 @@ If you're new to integrating dapps with MetaMask, check out the following topics If you have questions about integrating your dapp with MetaMask, you can interact with the MetaMask team and community on the MetaMask channels on [Consensys Discord](https://discord.gg/consensys). + +:::info MetaMask user support +If you need MetaMask user support, visit the [MetaMask Help Center](https://support.metamask.io/). +::: diff --git a/wallet/reference/new-reference.mdx b/wallet/reference/new-reference.mdx index 7625876b13f..b50e55fb118 100644 --- a/wallet/reference/new-reference.mdx +++ b/wallet/reference/new-reference.mdx @@ -8,4 +8,8 @@ sidebar_class_name: "hidden" import ParserOpenRPC from "@site/src/components/ParserOpenRPC" import { NETWORK_NAMES } from "@site/src/plugins/plugin-json-rpc" +<<<<<<< HEAD +======= + +>>>>>>> main