diff --git a/packages/beacon-dapp/src/events.ts b/packages/beacon-dapp/src/events.ts index f65316f8a..22218c82e 100644 --- a/packages/beacon-dapp/src/events.ts +++ b/packages/beacon-dapp/src/events.ts @@ -244,7 +244,7 @@ const showSentToast = async (data: RequestSentInfo): Promise => { text: 'Wallet not receiving request?', actionText: 'Reset Connection', actionCallback: async (): Promise => { - await closeToast() + closeToast() // eslint-disable-next-line @typescript-eslint/unbound-method const resetCallback = data.extraInfo.resetCallback if (resetCallback) { @@ -261,7 +261,7 @@ const showSentToast = async (data: RequestSentInfo): Promise => { timer: isMobileOS(window) ? SUCCESS_TIMER : 0, actions, openWalletAction - }).catch((toastError) => console.error(toastError)) + }) } const showAcknowledgedToast = async (data: { @@ -273,7 +273,7 @@ const showAcknowledgedToast = async (data: { body: 'Awaiting confirmation in\u00A0 {{wallet}}', state: 'acknowledge', walletInfo: data.walletInfo - }).catch((toastError) => console.error(toastError)) + }) } const showPrepare = async (data: { walletInfo?: WalletInfo }): Promise => { @@ -284,28 +284,24 @@ const showPrepare = async (data: { walletInfo?: WalletInfo }): Promise => body: text, state: 'prepare', walletInfo: data.walletInfo - }).catch((toastError) => console.error(toastError)) + }) } const hideUI = async (elements?: ('alert' | 'toast')[]): Promise => { - // if (elements) { - // if (elements.includes('alert')) { - // await closeAlerts() - // } - // if (elements.includes('toast')) { - // await closeToast() - // } - // } else { - // await closeToast() - // } - console.log('todo', elements) + if (elements?.includes('alert')) { + closeAlerts() + } + + if (elements?.includes('toast') || !elements) { + closeToast() + } } /** * Show a "No Permission" alert */ const showNoPermissionAlert = async (): Promise => { - await openAlert({ + openAlert({ title: 'No Permission', body: 'Please allow the wallet to handle this type of request.' }) @@ -315,7 +311,7 @@ const showNoPermissionAlert = async (): Promise => { * Show a */ const showInvalidActiveAccountState = async (): Promise => { - await openAlert({ + openAlert({ title: 'Invalid state', body: `An active account has been received, but no active subscription was found for BeaconEvent.ACTIVE_ACCOUNT_SET. For more information, visit: https://docs.walletbeacon.io/guides/migration-guide` @@ -377,8 +373,8 @@ const showErrorToast = async ( text: '', actionText: 'Show Details', actionCallback: async (): Promise => { - await closeToast() - await openAlert({ + closeToast() + openAlert({ title: error.title, // eslint-disable-next-line @typescript-eslint/unbound-method body: error.fullDescription.description, @@ -389,7 +385,7 @@ const showErrorToast = async ( }) } - await openToast({ + openToast({ body: `{{wallet}}\u00A0 has returned an error`, timer: response.errorResponse.errorType === BeaconErrorType.ABORTED_ERROR @@ -424,7 +420,7 @@ const showExtensionConnectedAlert = async (): Promise => { * Show a "channel closed" alert for 1.5 seconds */ const showChannelClosedAlert = async (): Promise => { - // await openAlert({ + // openAlert({ // title: 'Channel closed', // body: `Your peer has closed the connection.`, // buttons: [{ text: 'Done', style: 'outline' }], @@ -444,7 +440,7 @@ const showInternalErrorAlert = async ( body: data.text, buttons } - await openAlert(alertConfig) + openAlert(alertConfig) } /** @@ -469,7 +465,7 @@ const showPairAlert = async (data: BeaconEventType[BeaconEvent.PAIR_INIT]): Prom analytics: data.analytics, featuredWallets: data.featuredWallets } - await openAlert(alertConfig) + openAlert(alertConfig) } /** @@ -482,7 +478,7 @@ const showPermissionSuccessAlert = async ( ): Promise => { const { output } = data - await openToast({ + openToast({ body: `{{wallet}}\u00A0 has granted permission`, timer: SUCCESS_TIMER, walletInfo: data.walletInfo, @@ -510,7 +506,7 @@ const showProofOfEventChallengeSuccessAlert = async ( ): Promise => { const { output } = data - await openToast({ + openToast({ body: `{{wallet}}\u00A0 has ${output.isAccepted ? 'accepted' : 'refused'} the challenge`, timer: SUCCESS_TIMER, walletInfo: data.walletInfo, @@ -529,7 +525,7 @@ const showProofOfEventChallengeSuccessAlert = async ( logger.error('showSignSuccessAlert', 'Could not copy text to clipboard: ', err) } ) - await closeToast() + closeToast() } } ] @@ -542,7 +538,7 @@ const showSimulatedProofOfEventChallengeSuccessAlert = async ( ): Promise => { const { output } = data - await openToast({ + openToast({ body: !output.errorMessage ? `{{wallet}}\u00A0 has returned the list of operation` : `{{wallet}}\u00A0 has returned an error`, @@ -563,7 +559,7 @@ const showSimulatedProofOfEventChallengeSuccessAlert = async ( logger.error('showSignSuccessAlert', 'Could not copy text to clipboard: ', err) } ) - await closeToast() + closeToast() } } ] @@ -581,7 +577,7 @@ const showOperationSuccessAlert = async ( ): Promise => { const { account, output, blockExplorer } = data - await openToast({ + openToast({ body: `{{wallet}}\u00A0 successfully submitted operation`, timer: SUCCESS_TIMER, state: 'finished', @@ -598,7 +594,7 @@ const showOperationSuccessAlert = async ( account.network ) window.open(link, '_blank', 'noopener') - await closeToast() + closeToast() } } ] @@ -614,7 +610,7 @@ const showSignSuccessAlert = async ( data: BeaconEventType[BeaconEvent.SIGN_REQUEST_SUCCESS] ): Promise => { const output = data.output - await openToast({ + openToast({ body: `{{wallet}}\u00A0 successfully signed payload`, timer: SUCCESS_TIMER, state: 'finished', @@ -632,7 +628,7 @@ const showSignSuccessAlert = async ( logger.error('showSignSuccessAlert', 'Could not copy text to clipboard: ', err) } ) - await closeToast() + closeToast() } } ] @@ -649,7 +645,7 @@ const showSignSuccessAlert = async ( // data: BeaconEventType[BeaconEvent.ENCRYPT_REQUEST_SUCCESS] // ): Promise => { // const output = data.output -// await openToast({ +// openToast({ // body: `{{wallet}}\u00A0 successfully ${ // data.output.cryptoOperation === EncryptionOperation.ENCRYPT ? 'encrypted' : 'decrypted' // } payload`, @@ -669,7 +665,7 @@ const showSignSuccessAlert = async ( // logger.error('showSignSuccessAlert', 'Could not copy text to clipboard: ', err) // } // ) -// await closeToast() +// closeToast() // } // } // ] @@ -686,7 +682,7 @@ const showBroadcastSuccessAlert = async ( ): Promise => { const { network, output, blockExplorer } = data - await openToast({ + openToast({ body: `{{wallet}}\u00A0 successfully injected operation`, timer: SUCCESS_TIMER, state: 'finished', @@ -703,7 +699,7 @@ const showBroadcastSuccessAlert = async ( network ) window.open(link, '_blank', 'noopener') - await closeToast() + closeToast() } } ] diff --git a/packages/beacon-ui/src/components/alert/index.tsx b/packages/beacon-ui/src/components/alert/index.tsx index e79e52edf..842fc4a0a 100644 --- a/packages/beacon-ui/src/components/alert/index.tsx +++ b/packages/beacon-ui/src/components/alert/index.tsx @@ -1,20 +1,11 @@ import React from 'react' import { CloseIcon, LeftIcon, LogoIcon } from '../icons' import Loader from '../loader' -import { AlertProps } from '../../ui/alert/common' +import { AlertProps } from '../../ui/common' import './styles.css' import useIsMobile from '../../ui/alert/hooks/useIsMobile' const Alert: React.FC> = (props) => { - // useEffect(() => { - // const prevBodyOverflow = document.body.style.overflow - // document.body.style.overflow = 'hidden' - - // return () => { - // document.body.style.overflow = prevBodyOverflow - // } - // }, []) - const isMobile = useIsMobile() return ( diff --git a/packages/beacon-ui/src/components/pair-other/pair-other.tsx b/packages/beacon-ui/src/components/pair-other/pair-other.tsx index 1577ed091..34a83dde7 100644 --- a/packages/beacon-ui/src/components/pair-other/pair-other.tsx +++ b/packages/beacon-ui/src/components/pair-other/pair-other.tsx @@ -1,7 +1,9 @@ import React, { useState, useEffect } from 'react' import QR from '../qr' -import { PairOtherProps } from '../../ui/alert/common' +import { PairOtherProps } from '../../ui/common' + +import './styles.css' const PairOther: React.FC = (props: PairOtherProps) => { const [uiState, setUiState] = useState<'selection' | 'p2p' | 'walletconnect'>('selection') diff --git a/packages/beacon-ui/src/components/pairing.tsx b/packages/beacon-ui/src/components/pairing.tsx index 11a6c6d79..18297c8ee 100644 --- a/packages/beacon-ui/src/components/pairing.tsx +++ b/packages/beacon-ui/src/components/pairing.tsx @@ -1,18 +1,6 @@ import { desktopList, extensionList, iOSList, webList } from '../ui/alert/wallet-lists' import { DesktopApp, App, ExtensionApp, WebApp } from '@airgap/beacon-types' -// export interface PairingProps {} - -// const Pairing: Component = (props: PairingProps) => { -// return ( -//
-//

Pairing Component

-//
-// ) -// } - -// export default Pairing - /** * Initialize with tezos wallets for backwards compatibility */ diff --git a/packages/beacon-ui/src/components/toast/index.tsx b/packages/beacon-ui/src/components/toast/index.tsx index 51820ae29..beb15e138 100644 --- a/packages/beacon-ui/src/components/toast/index.tsx +++ b/packages/beacon-ui/src/components/toast/index.tsx @@ -1,19 +1,22 @@ -import React, { useState, useEffect } from "react"; -import { CloseIcon } from "../icons"; -import Loader from "../loader"; -import { isMobileOS } from "../../utils/platform"; +import React, { useState, useEffect } from 'react' +import { CloseIcon } from '../icons' +import Loader from '../loader' +import { isMobileOS } from '../../utils/platform' +import { ToastProps } from '../../ui/common' + +import './styles.css' function parseWallet( inputString: string, walletInfo: { - deeplink?: string; - icon?: string; - name: string; - type?: string; + deeplink?: string + icon?: string + name: string + type?: string } ) { - const regex = /({{\s*wallet\s*}})/g; - const parts = inputString.split(regex); + const regex = /({{\s*wallet\s*}})/g + const parts = inputString.split(regex) return parts.map((part, index) => { if (part.match(regex)) { @@ -22,123 +25,92 @@ function parseWallet( {`${walletInfo.name}

{walletInfo.name}

- ); + ) } else { return (

{part}

- ); + ) } - }); -} - -export interface ToastProps { - label: string; - open: boolean; - onClickClose: () => void; - actions?: { - text: string; - isBold?: boolean; - actionText?: string; - actionCallback?: () => void; - }[]; - walletInfo?: { - deeplink?: string; - icon?: string; - name: string; - type?: string; - }; - openWalletAction?: () => void; + }) } const Toast: React.FC = (props: ToastProps) => { - const [showMoreInfo, setShowMoreInfo] = useState(true); + const [showMoreInfo, setShowMoreInfo] = useState(true) const [divPosition, setDivPosition] = useState<{ x: number; y: number }>({ x: isMobileOS(window) ? 12 : window.innerWidth - 460, - y: 12, - }); - const [isDragging, setIsDragging] = useState(false); - const offset = { x: 0, y: 0 }; + y: 12 + }) + const [isDragging, setIsDragging] = useState(false) + const offset = { x: 0, y: 0 } - const hasWalletObject = - props.label.includes("{{wallet}}") && props.walletInfo; - const isRequestSentToast = props.label.includes("Request sent to"); + const hasWalletObject = props.label.includes('{{wallet}}') && props.walletInfo + const isRequestSentToast = props.label.includes('Request sent to') useEffect(() => { if (isRequestSentToast) { - setShowMoreInfo(false); + setShowMoreInfo(false) setTimeout(() => { - setShowMoreInfo(true); - }, 3000); + setShowMoreInfo(true) + }, 3000) } - }, [isRequestSentToast]); + }, [isRequestSentToast]) const onMouseDownHandler = (event: React.MouseEvent) => { - event.preventDefault(); // prevents inner text highlighting - const target = event.target as HTMLElement; - - if ( - target.className !== "toast-header" && - target.parentElement?.className !== "toast-header" - ) { - return; + event.preventDefault() // prevents inner text highlighting + const target = event.target as HTMLElement + + if (target.className !== 'toast-header' && target.parentElement?.className !== 'toast-header') { + return } - const boundingRect = target.getBoundingClientRect(); - offset.x = event.clientX - boundingRect.x; - offset.y = event.clientY - boundingRect.y; - setIsDragging(true); - }; + const boundingRect = target.getBoundingClientRect() + offset.x = event.clientX - boundingRect.x + offset.y = event.clientY - boundingRect.y + setIsDragging(true) + } const onMouseMoveHandler = (event: MouseEvent) => { if (isDragging && event.buttons === 1) { - const newX = Math.min( - Math.max(event.clientX - offset.x, 0), - window.innerWidth - 460 - ); - const newY = Math.min( - Math.max(event.clientY - offset.y, 0), - window.innerHeight - 12 - ); + const newX = Math.min(Math.max(event.clientX - offset.x, 0), window.innerWidth - 460) + const newY = Math.min(Math.max(event.clientY - offset.y, 0), window.innerHeight - 12) setDivPosition({ x: newX, - y: newY, - }); + y: newY + }) } - }; + } const onMouseUpHandler = () => { - setIsDragging(false); - }; + setIsDragging(false) + } useEffect(() => { if (isDragging) { - window.addEventListener("mousemove", onMouseMoveHandler); - window.addEventListener("mouseup", onMouseUpHandler); + window.addEventListener('mousemove', onMouseMoveHandler) + window.addEventListener('mouseup', onMouseUpHandler) } else { - window.removeEventListener("mousemove", onMouseMoveHandler); - window.removeEventListener("mouseup", onMouseUpHandler); + window.removeEventListener('mousemove', onMouseMoveHandler) + window.removeEventListener('mouseup', onMouseUpHandler) } return () => { - window.removeEventListener("mousemove", onMouseMoveHandler); - window.removeEventListener("mouseup", onMouseUpHandler); - }; - }, [isDragging]); + window.removeEventListener('mousemove', onMouseMoveHandler) + window.removeEventListener('mouseup', onMouseUpHandler) + } + }, [isDragging]) return (
- {hasWalletObject && props.walletInfo && ( - <>{parseWallet(props.label, props.walletInfo)} - )} + {hasWalletObject && props.walletInfo && <>{parseWallet(props.label, props.walletInfo)}} {!hasWalletObject &&

{props.label}

} {!isMobileOS(window) && props.openWalletAction && (
@@ -153,18 +125,11 @@ const Toast: React.FC = (props: ToastProps) => {
{props.actions.map((action, index) => (
-

+

{action.text}

{action.actionText && ( -
+
{action.actionText}
)} @@ -173,7 +138,7 @@ const Toast: React.FC = (props: ToastProps) => {
)}
- ); -}; + ) +} -export default Toast; +export default Toast diff --git a/packages/beacon-ui/src/index.ts b/packages/beacon-ui/src/index.ts index f2ff4199b..f4ed7398a 100644 --- a/packages/beacon-ui/src/index.ts +++ b/packages/beacon-ui/src/index.ts @@ -1,5 +1,5 @@ export { openAlert, closeAlert, closeAlerts } from './ui/alert' -export type { AlertButton, AlertConfig } from './ui/alert/common' +export type { AlertButton, AlertConfig, ToastAction } from './ui/common' export { Pairing, @@ -14,7 +14,6 @@ export { } from './components/pairing' export { closeToast, openToast } from './ui/toast' -export type { ToastAction } from './ui/toast' export { getColorMode, setColorMode } from './utils/colorMode' diff --git a/packages/beacon-ui/src/ui/alert/common.ts b/packages/beacon-ui/src/ui/alert/common.ts deleted file mode 100644 index b84d9acd1..000000000 --- a/packages/beacon-ui/src/ui/alert/common.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { NetworkType, AnalyticsInterface } from '@airgap/beacon-types' -import { MergedWallet } from '../../utils/wallets' - -export interface AlertButton { - text: string - style?: 'solid' | 'outline' - actionCallback?(): Promise -} - -export interface AlertConfig { - title: string - body?: string - data?: string - timer?: number - buttons?: AlertButton[] - pairingPayload?: { - p2pSyncCode: string - postmessageSyncCode: string - walletConnectSyncCode: string - networkType: NetworkType - } - closeButtonCallback?: () => void - disclaimerText?: string - analytics?: AnalyticsInterface - featuredWallets?: string[] -} - -export interface AlertProps { - open: boolean - showMore?: boolean - extraContent?: any - loading?: boolean - onCloseClick: () => void - onClickShowMore?: () => void - onBackClick?: () => void -} - -export interface PairOtherProps { - walletList: MergedWallet[] - p2pPayload: string - wcPayload: string - onClickLearnMore: () => void -} diff --git a/packages/beacon-ui/src/ui/alert/hooks/useConnect.tsx b/packages/beacon-ui/src/ui/alert/hooks/useConnect.tsx index 65fbd6386..dff91e49a 100644 --- a/packages/beacon-ui/src/ui/alert/hooks/useConnect.tsx +++ b/packages/beacon-ui/src/ui/alert/hooks/useConnect.tsx @@ -6,7 +6,7 @@ import { isTwBrowser, isAndroid, isMobileOS, isIOS } from 'src/utils/platform' import { MergedWallet, OSLink } from 'src/utils/wallets' import getDefaultLogo from '../getDefautlLogo' import { parseUri } from '@walletconnect/utils' -import { AlertConfig } from '../common' +import { AlertConfig } from '../../common' import useIsMobile from './useIsMobile' const logger = new Logger('useConnect') diff --git a/packages/beacon-ui/src/ui/alert/index.tsx b/packages/beacon-ui/src/ui/alert/index.tsx index 8606c5016..5b70324a3 100644 --- a/packages/beacon-ui/src/ui/alert/index.tsx +++ b/packages/beacon-ui/src/ui/alert/index.tsx @@ -1,7 +1,7 @@ import { createRoot } from 'react-dom/client' import { useEffect, useState } from 'react' import { Subject } from '../../utils/subject' -import { AlertConfig } from './common' +import { AlertConfig } from '../common' import PairingAlert from './components/pairing-alert' let initDone: boolean = false @@ -30,7 +30,8 @@ const closeAlerts = () => { const AlertRoot = (props: AlertConfig) => { const [isAlertVisible, setIsAlertVisible] = useState(true) useEffect(() => { - show$.subscribe((value) => setIsAlertVisible(value)) + const sub = show$.subscribe((value) => setIsAlertVisible(value)) + return () => sub.unsubscribe() }, []) return <>{isAlertVisible && } } diff --git a/packages/beacon-ui/src/ui/common.ts b/packages/beacon-ui/src/ui/common.ts new file mode 100644 index 000000000..39020e4f3 --- /dev/null +++ b/packages/beacon-ui/src/ui/common.ts @@ -0,0 +1,84 @@ +import { NetworkType, AnalyticsInterface, WalletInfo } from '@airgap/beacon-types' +import { MergedWallet } from '../utils/wallets' + +// ALERT + +export interface AlertButton { + text: string + style?: 'solid' | 'outline' + actionCallback?(): Promise +} + +export interface AlertConfig { + title: string + body?: string + data?: string + timer?: number + buttons?: AlertButton[] + pairingPayload?: { + p2pSyncCode: string + postmessageSyncCode: string + walletConnectSyncCode: string + networkType: NetworkType + } + closeButtonCallback?: () => void + disclaimerText?: string + analytics?: AnalyticsInterface + featuredWallets?: string[] +} + +export interface AlertProps { + open: boolean + showMore?: boolean + extraContent?: any + loading?: boolean + onCloseClick: () => void + onClickShowMore?: () => void + onBackClick?: () => void +} + +export interface PairOtherProps { + walletList: MergedWallet[] + p2pPayload: string + wcPayload: string + onClickLearnMore: () => void +} + +// TOAST + +export interface ToastAction { + text: string + isBold?: boolean + actionText?: string + actionLogo?: 'external' + actionCallback?(): Promise +} + +export interface ToastConfig { + body: string + timer?: number + forceNew?: boolean + state: 'prepare' | 'loading' | 'acknowledge' | 'finished' + actions?: ToastAction[] + walletInfo?: WalletInfo + openWalletAction?(): Promise +} + +export interface ToastProps { + label: string + open: boolean + onClickClose: () => void + actions?: { + text: string + isBold?: boolean + actionText?: string + actionCallback?: () => void + }[] + walletInfo?: { + deeplink?: string + icon?: string + name: string + type?: string + } + openWalletAction?: () => void +} diff --git a/packages/beacon-ui/src/ui/toast/index.tsx b/packages/beacon-ui/src/ui/toast/index.tsx index f15640b6b..d5923bdcb 100644 --- a/packages/beacon-ui/src/ui/toast/index.tsx +++ b/packages/beacon-ui/src/ui/toast/index.tsx @@ -1,138 +1,65 @@ -import { useState } from 'react' import { createRoot } from 'react-dom/client' -import { WalletInfo } from '@airgap/beacon-types' -import { generateGUID } from '@airgap/beacon-utils' - -import Toast from '../../components/toast' - -import * as toastStyles from '../../components/toast/styles.css' -import * as loaderStyles from '../../components/loader/styles.css' - -// INTERFACES -export interface ToastAction { - text: string - isBold?: boolean - actionText?: string - actionLogo?: 'external' - actionCallback?(): Promise -} - -export interface ToastConfig { - body: string - timer?: number - forceNew?: boolean - state: 'prepare' | 'loading' | 'acknowledge' | 'finished' - actions?: ToastAction[] - walletInfo?: WalletInfo - openWalletAction?(): Promise +import { useEffect, useState } from 'react' +import { Subject } from '../../utils/subject' +import Toast from 'src/components/toast' +import { ToastConfig } from '../common' + +let initDone: boolean = false +const render$ = new Subject() + +const createToast = () => { + const el = document.createElement('beacon-toast') + document.body.prepend(el) + setTimeout(() => createRoot(el).render(), 50) + initDone = true } -const useToastState = () => { - const [isOpen, setIsOpen] = useState(false) - const [renderLast, setRenderLast] = useState('') - - return { - isOpen, - setIsOpen, - renderLast, - setRenderLast - } +const openToast = (config: ToastConfig) => { + !initDone && createToast() + render$.next(config) } -const ANIMATION_TIME = 300 -let globalTimeout: NodeJS.Timeout - -const createToast = (config: ToastConfig) => { - const { isOpen, setIsOpen } = useToastState() - - const shadowRootEl = document.createElement('div') - if (document.getElementById('beacon-toast-wrapper')) { - ;(document.getElementById('beacon-toast-wrapper') as HTMLElement).remove() - } - shadowRootEl.setAttribute('id', 'beacon-toast-wrapper') - shadowRootEl.style.height = '0px' - const shadowRoot = shadowRootEl.attachShadow({ mode: 'open' }) - - // Toast styles - const style = document.createElement('style') - style.textContent = toastStyles.default - shadowRoot.appendChild(style) - - // Loader styles - const style2 = document.createElement('style') - style2.textContent = loaderStyles.default - shadowRoot.appendChild(style2) - - // Inject font styles - const styleFonts = document.createElement('style') - styleFonts.textContent = - "* { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif;}" - shadowRoot.appendChild(styleFonts) - - createRoot(shadowRootEl).render( - { - closeToast() - }} - actions={config.actions} - walletInfo={config.walletInfo} - openWalletAction={config.openWalletAction} - /> - ) - - // Create toast - document.body.prepend(shadowRootEl) - - // Open toast - setTimeout(() => { - setIsOpen(true) - }, 50) - - // Add close timer if in config - clearTimeout(globalTimeout) - if (config.timer) { - globalTimeout = setTimeout(() => closeToast(), config.timer) - } +const closeToast = () => { + render$.next(undefined) } -/** - * Close a toast - */ -const closeToast = (): Promise => - new Promise((resolve) => { - const { setIsOpen } = useToastState() - - if (typeof window === 'undefined') { - console.log('DO NOT RUN ON SERVER') - resolve() +const ToastRoot = () => { + const [config, setConfig] = useState(undefined) + useEffect(() => { + const sub = render$.subscribe((config) => { + setConfig(config) + }) + return () => sub.unsubscribe() + }, []) + + useEffect(() => { + if (!config || !config.timer) { + return } - setIsOpen(false) - setTimeout(() => { - if (document.getElementById('beacon-toast-wrapper')) - (document.getElementById('beacon-toast-wrapper') as HTMLElement).remove() - resolve() - }, ANIMATION_TIME) - }) - -/** - * Create a new toast - * - * @param toastConfig Configuration of the toast - */ -const openToast = async (config: ToastConfig): Promise => { - const { renderLast, setRenderLast } = useToastState() - if (typeof window === 'undefined') { - console.log('DO NOT RUN ON SERVER') - return - } - const id = await generateGUID() - setRenderLast(id) - - await closeToast() - if (id === renderLast) createToast(config) + const id = setTimeout(() => { + setConfig(undefined) // Hide the toast + }, config.timer) + + return () => clearTimeout(id) + }, [config?.timer]) + + return ( + <> + {config && ( + { + closeToast() + }} + actions={config.actions} + walletInfo={config.walletInfo} + openWalletAction={config.openWalletAction} + /> + )} + + ) } export { closeToast, openToast } diff --git a/packages/beacon-ui/src/utils/subject.ts b/packages/beacon-ui/src/utils/subject.ts index d9954e609..c7161ef0f 100644 --- a/packages/beacon-ui/src/utils/subject.ts +++ b/packages/beacon-ui/src/utils/subject.ts @@ -1,8 +1,22 @@ +export interface Subscription { + unsubscribe: () => void +} + export class Subject { private subscribers: ((value: T) => void)[] = [] - subscribe(callback: (value: T) => void): void { + subscribe(callback: (value: T) => void): Subscription { this.subscribers.push(callback) + + const unsubscribe = () => { + this.unsubscribe(callback) + } + + return { unsubscribe } + } + + private unsubscribe(observer: Function): void { + this.subscribers = this.subscribers.filter((subscriber) => subscriber !== observer) } next(value: T): void {