diff --git a/packages/extension-polkagate/src/hooks/useAlerts.ts b/packages/extension-polkagate/src/hooks/useAlerts.ts index 2c6324ab2..0fa16db2d 100644 --- a/packages/extension-polkagate/src/hooks/useAlerts.ts +++ b/packages/extension-polkagate/src/hooks/useAlerts.ts @@ -3,20 +3,30 @@ import type { Severity } from '../util/types'; -import { useCallback, useContext } from 'react'; +import { Chance } from 'chance'; +import { useCallback, useContext, useMemo } from 'react'; import { AlertContext } from '../components'; +export const TIME_TO_REMOVE_ALERT = 5000; // 5 secs + export default function useAlerts () { const { alerts, setAlerts } = useContext(AlertContext); - const notify = useCallback((text: string, severity?: Severity) => { - setAlerts((prev) => [...prev, { severity: severity || 'info', text }]); - }, [setAlerts]); + const random = useMemo(() => new Chance(), []); - const removeAlert = useCallback((index: number) => { - setAlerts((prev) => prev.filter((_, i) => i !== index)); + const removeAlert = useCallback((idToRemove: string) => { + setAlerts((prev) => prev.filter(({ id }) => id !== idToRemove)); }, [setAlerts]); + const notify = useCallback((text: string, severity?: Severity) => { + const id = random.string({ length: 10 }); + + setAlerts((prev) => [...prev, { id, severity: severity || 'info', text }]); + const timeout = setTimeout(() => removeAlert(id), TIME_TO_REMOVE_ALERT); + + return () => clearTimeout(timeout); + }, [random, removeAlert, setAlerts]); + return { alerts, notify, removeAlert }; } diff --git a/packages/extension-polkagate/src/hooks/useAssetsBalances.ts b/packages/extension-polkagate/src/hooks/useAssetsBalances.ts index a31e18eee..d8546b4df 100644 --- a/packages/extension-polkagate/src/hooks/useAssetsBalances.ts +++ b/packages/extension-polkagate/src/hooks/useAssetsBalances.ts @@ -9,6 +9,7 @@ import type { MetadataDef } from '@polkadot/extension-inject/types'; import type { AlertType, DropdownOption, UserAddedChains } from '../util/types'; import { createAssets } from '@polkagate/apps-config/assets'; +import { Chance } from 'chance'; import { type Dispatch, type SetStateAction, useCallback, useEffect, useMemo, useState } from 'react'; import { BN, isObject } from '@polkadot/util'; @@ -19,6 +20,7 @@ import { updateMetadata } from '../messaging'; import { ASSET_HUBS, RELAY_CHAINS_GENESISHASH, TEST_NETS } from '../util/constants'; import getChainName from '../util/getChainName'; import { isHexToBn } from '../util/utils'; +import { TIME_TO_REMOVE_ALERT } from './useAlerts'; import useSelectedChains from './useSelectedChains'; import { useIsTestnetEnabled, useTranslation } from '.'; @@ -135,6 +137,8 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl const isTestnetEnabled = useIsTestnetEnabled(); const selectedChains = useSelectedChains(); + const random = useMemo(() => new Chance(), []); + /** to limit calling of this heavy call on just home and account details */ const SHOULD_FETCH_ASSETS = window.location.hash === '#/' || window.location.hash.startsWith('#/accountfs'); @@ -147,6 +151,15 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl const [workersCalled, setWorkersCalled] = useState(); const [isUpdate, setIsUpdate] = useState(false); + const addAlert = useCallback(() => { + const id = random.string({ length: 10 }); + + setAlerts((perv) => [...perv, { id, severity: 'success', text: t('Accounts\' balances updated!') }]); + const timeout = setTimeout(() => setAlerts((prev) => prev.filter(({ id: alertId }) => alertId !== id)), TIME_TO_REMOVE_ALERT); + + return () => clearTimeout(timeout); + }, [random, setAlerts, t]); + useEffect(() => { SHOULD_FETCH_ASSETS && getStorage(ASSETS_NAME_IN_STORAGE, true).then((savedAssets) => { const _timeStamp = (savedAssets as SavedAssets)?.timeStamp; @@ -207,9 +220,9 @@ export default function useAssetsBalances (accounts: AccountJson[] | null, setAl /** when one round fetch is done, we will save fetched assets in storage */ if (addresses && workersCalled?.length === 0) { handleAccountsSaving(); - setAlerts((perv) => [...perv, { severity: 'success', text: t('Accounts\' balances updated!') }]); + addAlert(); } - }, [addresses, handleAccountsSaving, setAlerts, t, workersCalled?.length]); + }, [addAlert, addresses, handleAccountsSaving, workersCalled?.length]); useEffect(() => { /** chain list may have changed */ diff --git a/packages/extension-polkagate/src/partials/Alert.tsx b/packages/extension-polkagate/src/partials/Alert.tsx index 7c55afe51..9c1fb3150 100644 --- a/packages/extension-polkagate/src/partials/Alert.tsx +++ b/packages/extension-polkagate/src/partials/Alert.tsx @@ -8,9 +8,9 @@ import '@vaadin/icons'; import type { AlertType } from '../util/types'; import { Alert as MuiAlert, Slide } from '@mui/material'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback } from 'react'; -import { useTranslation } from '../hooks'; +import { useAlerts, useTranslation } from '../hooks'; interface Props { alert: AlertType; @@ -18,21 +18,14 @@ interface Props { function Alert ({ alert }: Props): React.ReactElement { const { t } = useTranslation(); + const { removeAlert } = useAlerts(); - const [showAlert, setShowAlert] = useState(true); - - useEffect(() => { - const timeoutId = setTimeout(() => { - setShowAlert(false); - }, 10000); - - return () => clearTimeout(timeoutId); - }, []); - - const closeAlert = useCallback(() => setShowAlert(false), []); + const closeAlert = useCallback(() => { + removeAlert(alert.id); + }, [alert.id, removeAlert]); return ( - + { - alerts.forEach((_, index) => { - const timeout = setTimeout( - () => removeAlert(index) - , TIME_TO_REMOVE_ALERT); - - return () => clearTimeout(timeout); - }); - }, [alerts, removeAlert]); - return ( {alerts.map((alert, index) => diff --git a/packages/extension-polkagate/src/util/types.ts b/packages/extension-polkagate/src/util/types.ts index b56f61e88..5c3899911 100644 --- a/packages/extension-polkagate/src/util/types.ts +++ b/packages/extension-polkagate/src/util/types.ts @@ -782,6 +782,7 @@ export interface AccountsAssetsContextType { export type Severity= 'error' | 'warning' | 'info' | 'success' export interface AlertType { + id: string; text: string; severity: Severity }