diff --git a/packages/evm-connector/src/types.ts b/packages/evm-connector/src/types.ts index bc5cfdef..6be86814 100644 --- a/packages/evm-connector/src/types.ts +++ b/packages/evm-connector/src/types.ts @@ -23,3 +23,8 @@ export interface EIP1193Provider extends EventEmitter { params?: unknown[]; }): Promise; } + +export interface SignatureData { + message: string; + signature: string; +} diff --git a/packages/react/src/providers/FuelUIProvider.tsx b/packages/react/src/providers/FuelUIProvider.tsx index e971a5f1..7cadf5b4 100644 --- a/packages/react/src/providers/FuelUIProvider.tsx +++ b/packages/react/src/providers/FuelUIProvider.tsx @@ -1,4 +1,4 @@ -import type { Fuel, FuelConfig, FuelConnector } from 'fuels'; +import type { FuelConfig, FuelConnector } from 'fuels'; import { type ReactNode, createContext, @@ -21,6 +21,11 @@ export type FuelUIProviderProps = { theme?: string; }; +export enum Routes { + INSTALL = 'install', + CONNECTING = 'connecting', +} + export type FuelUIContextType = { fuelConfig: FuelConfig; theme: string; @@ -37,6 +42,11 @@ export type FuelUIContextType = { isOpen: boolean; back: () => void; connect: (connector: FuelConnector) => void; + retryConnect: () => Promise; + // @TODO: Remove this to use tiny router library + // react-router maybe too big for the bundle + route: Routes; + setRoute: (state: Routes) => void; }; }; @@ -80,18 +90,40 @@ export function FuelUIProvider({ theme, }: FuelUIProviderProps) { const { fuel } = useFuel(); - const { isPending: isConnecting, isError, connect } = useConnect(); + const { + isPending: isConnecting, + data: isConnected, + isError, + connectAsync, + } = useConnect(); const { connectors, isLoading: isLoadingConnectors } = useConnectors({ query: { select: sortConnectors }, }); const [connector, setConnector] = useState(null); + const [dialogRoute, setDialogRoute] = useState(Routes.INSTALL); const [isOpen, setOpen] = useState(false); const [error, setError] = useState(null); - const handleCancel = () => { + // If connectors list is updated we need to update the data of the current + // selected connector and change routes depending on the dialog route + useEffect(() => { + if (!connectors.length) return; + const selectedConnector = connectors.find((c: FuelConnector) => { + return c.name === connector?.name; + }); + if (selectedConnector) { + setConnector(selectedConnector); + if (selectedConnector.installed && dialogRoute === Routes.INSTALL) { + setDialogRoute(Routes.CONNECTING); + } + } + }, [connectors, dialogRoute, connector]); + + const handleCancel = useCallback(() => { setOpen(false); setConnector(null); - }; + setError(null); + }, []); const handleConnect = () => { setOpen(true); @@ -99,32 +131,46 @@ export function FuelUIProvider({ const handleBack = () => { setConnector(null); + setError(null); }; useEffect(() => { - if (connector?.installed) { - handleBack(); + if (!isConnected) return; + handleCancel(); + }, [isConnected, handleCancel]); + + const handleRetryConnect = useCallback(async () => { + if (!connector) return; + try { + setError(null); + await connectAsync(connector.name); + } catch (err) { + setError(err as Error); } - }, [connector?.installed, handleBack]); + }, [connectAsync, connector]); const handleSelectConnector = useCallback( async (connector: FuelConnector) => { if (!fuel) return setConnector(connector); - + setConnector(connector); if (connector.installed) { - handleCancel(); + setDialogRoute(Routes.CONNECTING); try { - await connect(connector.name); + await connectAsync(connector.name); } catch (err) { setError(err as Error); } } else { - setConnector(connector); + setDialogRoute(Routes.INSTALL); } }, - [fuel, connect, handleCancel], + [fuel, connectAsync], ); + const setRoute = useCallback((state: Routes) => { + setDialogRoute(state); + }, []); + const isLoading = useMemo(() => { const hasLoadedConnectors = (fuelConfig.connectors || []).length > connectors.length; @@ -145,9 +191,12 @@ export function FuelUIProvider({ connect: handleConnect, cancel: handleCancel, dialog: { + route: dialogRoute, + setRoute, connector, isOpen, connect: handleSelectConnector, + retryConnect: handleRetryConnect, back: handleBack, }, }} diff --git a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx new file mode 100644 index 00000000..4597b141 --- /dev/null +++ b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx @@ -0,0 +1,58 @@ +import type { FuelConnector } from 'fuels'; + +import { useConnectUI } from '../../../../providers/FuelUIProvider'; +import { ConnectorIcon } from '../ConnectorIcon'; + +import { Spinner } from '../Spinner/Spinner'; +import { + ConnectorButton, + ConnectorContent, + ConnectorDescription, + ConnectorDescriptionError, + ConnectorImage, + ConnectorTitle, +} from './styles'; + +type ConnectorProps = { + theme?: string; + className?: string; + connector: FuelConnector; +}; + +export function Connecting({ className, connector, theme }: ConnectorProps) { + const { + error, + isConnecting, + dialog: { retryConnect }, + } = useConnectUI(); + + return ( +
+ + + + + {connector.name} + {error ? ( + {error.message} + ) : ( + + Requesting connection to
{connector.name}. +
+ )} +
+ + {isConnecting ? ( + + ) : ( + 'Connect' + )} + +
+ ); +} diff --git a/packages/react/src/ui/Connect/components/Connector/Connector.tsx b/packages/react/src/ui/Connect/components/Connector/Connector.tsx index 74d0187b..b70e6e84 100644 --- a/packages/react/src/ui/Connect/components/Connector/Connector.tsx +++ b/packages/react/src/ui/Connect/components/Connector/Connector.tsx @@ -1,14 +1,15 @@ import type { FuelConnector } from 'fuels'; -import { useEffect, useState } from 'react'; -import { useConnectUI } from '../../../../providers/FuelUIProvider'; import { ConnectorIcon } from '../ConnectorIcon'; -import { Spinner } from '../Spinner/Spinner'; +import { useQuery } from '@tanstack/react-query'; +import { useConnectUI } from '../../../../providers'; +import { Routes } from '../../../../providers/FuelUIProvider'; import { ConnectorButton, ConnectorContent, ConnectorDescription, + ConnectorFooterHelper, ConnectorImage, ConnectorTitle, } from './styles'; @@ -20,30 +21,23 @@ type ConnectorProps = { }; export function Connector({ className, connector, theme }: ConnectorProps) { + const { + dialog: { setRoute }, + } = useConnectUI(); const { install: { action, link, description }, } = connector.metadata; - const { - setError, - dialog: { connect }, - } = useConnectUI(); - const [isLoading, setLoading] = useState(!connector.installed); - - useEffect(() => { - const ping = async () => { - try { - await connector.ping(); - connector.installed = true; - connect(connector); - } catch (error) { - setLoading(false); - setError(error as Error); - } - }; - - ping(); - }, [connector, connect, setError]); + // Ping exetensin if it's installed it will trigger connector + useQuery({ + queryKey: ['CONNECTOR_PING', connector.name, connector.installed], + queryFn: async () => { + const isInstall = await connector.ping(); + if (isInstall) setRoute(Routes.CONNECTING); + return isInstall; + }, + staleTime: Number.POSITIVE_INFINITY, + }); const actionText = action || 'Install'; @@ -61,13 +55,12 @@ export function Connector({ className, connector, theme }: ConnectorProps) { {connector.name} {description} - - {isLoading ? ( - - ) : ( - actionText - )} + + {actionText} + + If you have install and is not detected
try to refresh the page. +
); } diff --git a/packages/react/src/ui/Connect/components/Connector/styles.tsx b/packages/react/src/ui/Connect/components/Connector/styles.tsx index 667f61e6..1006eabf 100644 --- a/packages/react/src/ui/Connect/components/Connector/styles.tsx +++ b/packages/react/src/ui/Connect/components/Connector/styles.tsx @@ -9,9 +9,26 @@ export const ConnectorTitle = styled.h2` `; export const ConnectorDescription = styled.p` + font-weight: 400; text-align: center; margin: 0 1.2em; line-height: 1.2em; + padding: 0 2em; + opacity: 0.8; +`; + +export const ConnectorFooterHelper = styled.p` + font-size: 0.8em; + font-weight: 400; + text-align: center; + margin: 0.6em 1.2em; + line-height: 1.2em; + padding: 0 2em; + opacity: 0.5; +`; + +export const ConnectorDescriptionError = styled(ConnectorDescription)` + color: var(--fuel-color-error); `; export const ConnectorImage = styled.div` @@ -19,7 +36,7 @@ export const ConnectorImage = styled.div` justify-content: center; height: 6.2em; width: 100%; - margin-top: 1.4em; + margin-top: 1.6em; margin-bottom: 1.2em; `; @@ -27,6 +44,7 @@ export const ConnectorButton = styled.a` display: flex; box-sizing: border-box; text-decoration: none; + cursor: pointer; justify-content: center; align-items: center; margin: 1.4em 1em 0; diff --git a/packages/react/src/ui/Connect/index.tsx b/packages/react/src/ui/Connect/index.tsx index 73fb8db7..e798e366 100644 --- a/packages/react/src/ui/Connect/index.tsx +++ b/packages/react/src/ui/Connect/index.tsx @@ -1,7 +1,7 @@ import * as Dialog from '@radix-ui/react-dialog'; import { useEffect, useState } from 'react'; -import { useConnectUI } from '../../providers/FuelUIProvider'; +import { Routes, useConnectUI } from '../../providers/FuelUIProvider'; import { Connector } from './components/Connector/Connector'; import { Connectors } from './components/Connectors'; @@ -18,6 +18,29 @@ import { import { getThemeVariables } from './themes'; import './index.css'; +import type { FuelConnector } from 'fuels'; +import { Connecting } from './components/Connector/Connecting'; + +const ConnectRoutes = ({ + state, + connector, + theme, +}: { + theme: string; + state: Routes; + connector?: FuelConnector | null; +}) => { + if (!connector) return ; + + switch (state) { + case Routes.INSTALL: + return ; + case Routes.CONNECTING: + return ; + default: + return null; + } +}; export function Connect() { // Fix hydration problem between nextjs render and frontend render @@ -27,7 +50,7 @@ export function Connect() { const { theme, cancel, - dialog: { isOpen, connector, back }, + dialog: { isOpen, route: state, connector, back }, } = useConnectUI(); useEffect(() => { @@ -60,11 +83,11 @@ export function Connect() { - {connector ? ( - - ) : ( - - )} + diff --git a/packages/react/src/ui/Connect/styles.tsx b/packages/react/src/ui/Connect/styles.tsx index d262a0ba..fc9ab19e 100644 --- a/packages/react/src/ui/Connect/styles.tsx +++ b/packages/react/src/ui/Connect/styles.tsx @@ -157,7 +157,7 @@ export const FuelRoot = styled.div` width: 100%; margin: 0; padding: 0; - z-index: 9999; + z-index: 99; font-size: var(--fuel-font-size); & * { diff --git a/packages/react/src/ui/Connect/themes.tsx b/packages/react/src/ui/Connect/themes.tsx index fe71f527..984ab428 100644 --- a/packages/react/src/ui/Connect/themes.tsx +++ b/packages/react/src/ui/Connect/themes.tsx @@ -10,6 +10,7 @@ const commonTheme = { '--fuel-items-gap': '8px', /* Border */ '--fuel-border': '1px solid var(--fuel-border-color)', + '--fuel-color-error': '#f25a68', }; const lightTheme = {