localStorage as context provider added to the app

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2026-04-07 14:57:56 +03:00
parent f6a2fc6917
commit 3f9003883d
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
10 changed files with 121 additions and 92 deletions

View File

@ -1,7 +1,7 @@
{
"name": "ghost-dao-interface",
"private": true,
"version": "0.6.18",
"version": "0.7.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@ -1,8 +1,9 @@
import { ArrowBack } from "@mui/icons-material";
import { Box, Link, Skeleton, Typography } from "@mui/material";
import { useEffect, useState, useMemo } from "react";
import { useEffect, useState, useMemo, useCallback } from "react";
import { Link as RouterLink, useLocation, useParams, useNavigate } from "react-router-dom";
import { useAccount, useSwitchChain } from "wagmi";
import { isAddress } from "viem";
import ReactGA from "react-ga4";
import PageTitle from "../../components/PageTitle/PageTitle";
@ -20,6 +21,7 @@ import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
import { NetworkId } from "../../constants";
import { formatCurrency } from "../../helpers";
import { useLocalStorage } from "../../hooks/localstorage";
import { useLiveBonds } from "../../hooks/bonds";
import { useFtsoPrice } from "../../hooks/prices";
@ -38,29 +40,38 @@ export const BondModal = ({ bond, chainId, address, connect }) => {
const { network } = useParams();
const { pathname } = useLocation();
const [slippage, setSlippage] = useState(localStorage.getItem("bond-slippage") || "10");
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("bond-decimals") || "5");
const [isSettingsOpen, setSettingsOpen] = useState(false);
const [recipientAddress, setRecipientAddress] = useState(address);
const { getStorageValue, setStorageValue } = useLocalStorage();
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "bond-slippage", "10"));
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "bond-decimals", "5"));
const [recipientAddress, setRecipientAddressInner] = useState(() => getStorageValue(chainId, address, "bond-recipient", address));
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: pathname });
}, [])
const setSlippageInner = (value) => {
const setRecipientAddress = useCallback((value) => {
setRecipientAddressInner(value);
if (isAddress(value)) {
setStorageValue(chainId, address, "bond-recipient", value);
}
}, [chainId, address]);
const setSlippageInner = useCallback((value) => {
const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) {
setSlippage(value);
localStorage.setItem("bond-slippage", value);
setStorageValue(chainId, address, "bond-slippage", value);
}
}
}, [chainId, address]);
const setFormatDecimalsInner = (value) => {
const setFormatDecimalsInner = useCallback((value) => {
if (Number(value) <= 17) {
setFormatDecimals(value);
localStorage.setItem("bond-decimals", value);
setStorageValue(chainId, address, "bond-decimals", value);
}
}
}, [chainId, address]);
useEffect(() => {
const handleKeyDown = (event) => {

View File

@ -46,14 +46,13 @@ import {
useLatestBlockNumber,
useEraIndex,
} from "../../hooks/ghost";
import { useLocalStorage } from "../../hooks/localstorage";
import { ValidatorTable } from "./ValidatorTable";
import { BridgeModal, BridgeConfirmModal } from "./BridgeModal";
import { BridgeHeader } from "./BridgeHeader";
import { BridgeCardAction, BridgeCardHistory } from "./BridgeCard";
const STORAGE_PREFIX = "storedTransactions"
const Bridge = ({ chainId, address, config, connect }) => {
const isBigScreen = useMediaQuery("(max-width: 980px)")
const isSmallScreen = useMediaQuery("(max-width: 650px)");
@ -67,14 +66,15 @@ const Bridge = ({ chainId, address, config, connect }) => {
const [bridgeAction, setBridgeAction] = useState(true);
const [currentTime, setCurrentTime] = useState(Date.now());
const { getStorageValue, setStorageValue } = useLocalStorage();
useEffect(() => {
const interval = setInterval(() => setCurrentTime(Date.now()), 1000);
return () => clearInterval(interval);
}, []);
const initialStoredTransactions = localStorage.getItem(STORAGE_PREFIX);
const [storedTransactions, setStoredTransactions] = useState(
initialStoredTransactions ? JSON.parse(initialStoredTransactions) : []
const [storedTransactions, setStoredTransactions] = useState(() =>
getStorageValue(chainId, address, "bridge-txs", [])
);
const { gatekeeperAddress } = useGatekeeperAddress(chainId);
@ -281,7 +281,7 @@ const Bridge = ({ chainId, address, config, connect }) => {
const removeStoredRecord = useCallback(() => {
const newStoredTransactions = storedTransactions.filter((_, index) => index !== activeTxIndex)
setStoredTransactions(newStoredTransactions);
localStorage.setItem(storagePrefix, JSON.stringify(newStoredTransactions));
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
setActiveTxIndex(-1);
}, [storedTransactions, activeTxIndex, setStoredTransactions, setActiveTxIndex]);
@ -307,8 +307,8 @@ const Bridge = ({ chainId, address, config, connect }) => {
const newStoredTransactions = [transaction, ...storedTransactions];
setStoredTransactions(newStoredTransactions);
localStorage.setItem(STORAGE_PREFIX, JSON.stringify(newStoredTransactions));
setActiveTxIndex(0)
setStorageValue(chainId, address, "bridge-txs", newStoredTransactions);
setActiveTxIndex(0);
}
return (

View File

@ -10,9 +10,10 @@ import {
useTheme,
} from "@mui/material";
import SettingsIcon from '@mui/icons-material/Settings';
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState, useCallback } from "react";
import { useParams, useLocation, useSearchParams } from "react-router-dom";
import { Helmet } from "react-helmet";
import { isAddress } from "viem";
import ReactGA from "react-ga4";
import InfoTooltip from "../../components/Tooltip/InfoTooltip";
@ -31,6 +32,7 @@ import {
EMPTY_ADDRESS,
WETH_ADDRESSES,
} from "../../constants/addresses";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol } from "../../hooks/tokens";
import { getTokenAddress } from "../../hooks/helpers";
@ -43,6 +45,7 @@ const Dex = ({ chainId, address, connect, config }) => {
const pathname = useParams();
const theme = useTheme();
const { getStorageValue, setStorageValue } = useLocalStorage();
const isSmallScreen = useMediaQuery("(max-width: 650px)");
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
@ -55,10 +58,10 @@ const Dex = ({ chainId, address, connect, config }) => {
const [topTokenListOpen, setTopTokenListOpen] = useState(false);
const [bottomTokenListOpen, setBottomTokenListOpen] = useState(false);
const [secondsToWait, setSecondsToWait] = useState(localStorage.getItem("dex-deadline") || "60");
const [slippage, setSlippage] = useState(localStorage.getItem("dex-slippage") || "5");
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("dex-decimals") || "5");
const [actualDestinationAddress, setActualDestinationAddress] = useState(localStorage.getItem("dex-destination"));
const [secondsToWait, setSecondsToWait] = useState(() => getStorageValue(chainId, address, "dex-deadline", "60"));
const [slippage, setSlippage] = useState(() => getStorageValue(chainId, address, "dex-slippage", "5"));
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "dex-decimals", "5"));
const [actualDestinationAddress, setActualDestinationAddress] = useState(() => getStorageValue(chainId, address, "dex-destination", address));
const [destinationAddress, setDestinationAddress] = useState(actualDestinationAddress);
const [tokenAddressTop, setTokenAddressTop] = useState(EMPTY_ADDRESS);
@ -180,38 +183,34 @@ const Dex = ({ chainId, address, connect, config }) => {
setSearchParams(newQueryParameters);
}
const setSlippageInner = (value) => {
const setSlippageInner = useCallback((value) => {
const maybeValue = parseFloat(value);
if (!maybeValue || parseFloat(value) <= 100) {
setSlippage(value);
localStorage.setItem("dex-slippage", value);
setStorageValue(chainId, address, "dex-slippage", value);
}
}
}, [chainId, address]);
const setSecondsToWaitInner = (value) => {
localStorage.setItem("dex-deadline", value);
const setSecondsToWaitInner = useCallback((value) => {
setSecondsToWait(value);
}
setStorageValue(chainId, address, "dex-deadline", value);
}, [chainId, address]);
const setFormatDecimalsInner = (value) => {
const setFormatDecimalsInner = useCallback((value) => {
if (Number(value) <= 17) {
localStorage.setItem("dex-decimals", value);
setFormatDecimals(value);
setStorageValue(chainId, address, "dex-decimals", value);
}
}
}, [chainId, address]);
const setDestinationAddressInner = (value) => {
const setDestinationAddressInner = useCallback((value) => {
const cleanedValue = value.trim();
const isEvmAddress = /^0x[a-fA-F0-9]{40}$/.test(cleanedValue);
if (isEvmAddress) {
localStorage.setItem("dex-destination", value);
setActualDestinationAddress(value);
} else if (!isEvmAddress && actualDestinationAddress) {
localStorage.removeItem("dex-destination");
setActualDestinationAddress(undefined);
}
setDestinationAddress(value);
}
if (isAddress(cleanedValue)) {
setActualDestinationAddress(value);
setStorageValue(chainId, address, "dex-destination", value);
}
}, [chainId, address]);
const handleCloseSetting = () => {
setDestinationAddress(undefined);
@ -255,7 +254,7 @@ const Dex = ({ chainId, address, connect, config }) => {
}}
>
<Modal
maxWidth="376px"
maxWidth="450px"
minHeight="200px"
open={settingsOpen}
headerText={"Settings"}

View File

@ -28,6 +28,7 @@ import PageTitle from "../../components/PageTitle/PageTitle";
import { PrimaryButton, TertiaryButton } from "../../components/Button";
import { TokenAllowanceGuard } from "../../components/TokenAllowanceGuard/TokenAllowanceGuard";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol, useBalance } from "../../hooks/tokens";
import { useProposalThreshold, useProposalHash, propose } from "../../hooks/governance";
import { DecimalBigNumber } from "../../helpers/DecimalBigNumber";
@ -42,11 +43,10 @@ const NewProposal = ({ config, address, connect, chainId }) => {
const isVerySmallScreen = useMediaQuery("(max-width: 379px)");
const theme = useTheme();
const { getStorageValue, setStorageValue } = useLocalStorage();
const myStoredProposals = localStorage.getItem(`${MY_PROPOSALS_PREFIX}-${chainId}-${address}`);
const [myProposals, setMyProposals] = useState(
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
);
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
const [myProposals, setMyProposalsInner] = useState(() => myStoredProposals.map(id => BigInt(id)));
const [isPending, setIsPending] = useState(false);
const [isModalOpened, setIsModalOpened] = useState(false);
@ -61,10 +61,10 @@ const NewProposal = ({ config, address, connect, chainId }) => {
proposalDescription
} = useProposalHash(chainId, proposalFunctions);
useEffect(() => {
const toStore = JSON.stringify(myProposals.map(id => id.toString()));
localStorage.setItem(`${MY_PROPOSALS_PREFIX}-${chainId}-${address}`, toStore);
}, [myProposals]);
const setMyProposals = useCallback((proposals) => {
setMyProposalsInner(proposals);
setStorageValue(chainId, address, MY_PROPOSALS_PREFIX, proposals.map(id => id.toString()))
}, [chainId, address]);
useEffect(() => {
ReactGA.send({ hitType: "pageview", page: "/governance/create" });

View File

@ -1,4 +1,4 @@
import { useEffect, useState, useMemo } from "react";
import { useEffect, useState, useMemo, useCallback } from "react";
import ReactGA from "react-ga4";
import { useParams } from 'react-router-dom';
import { useQueryClient } from '@tanstack/react-query';
@ -38,6 +38,7 @@ import { parseFunctionCalldata } from "./components/functions";
import { networkAvgBlockSpeed } from "../../constants";
import { useLocalStorage } from "../../hooks/localstorage";
import { useTokenSymbol, usePastTotalSupply, usePastVotes, useBalance } from "../../hooks/tokens";
import {
getVoteValue,
@ -75,7 +76,6 @@ import RepeatIcon from '@mui/icons-material/Repeat';
const HUNDRED = new DecimalBigNumber(100n, 0);
const ProposalDetails = ({ chainId, address, connect, config }) => {
const { id } = useParams();
const proposalId = BigInt(id);
@ -89,6 +89,7 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
const theme = useTheme();
const queryClient = useQueryClient();
const { getStorageValue, setStorageValue } = useLocalStorage();
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const { balance } = useBalance(chainId, "GHST", address);
@ -149,20 +150,19 @@ const ProposalDetails = ({ chainId, address, connect, config }) => {
return url;
}, [proposalProposer, config]);
const handleVote = async (support) => {
const handleVote = useCallback(async (support) => {
setIsPending(true);
const result = await castVote(chainId, address, proposalId, support);
if (result) {
const storedVotedProposals = localStorage.getItem(`${VOTED_PROPOSALS_PREFIX}-${chainId}-${address}`);
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []);
const proposals = JSON.parse(storedVotedProposals || "[]").map(id => BigInt(id));
proposals.push(proposalId);
const toStore = JSON.stringify(proposals.map(id => id.toString()));
localStorage.setItem(`${VOTED_PROPOSALS_PREFIX}-${chainId}-${address}`, toStore);
setStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, proposals.map(id => id.toString()));
await queryClient.invalidateQueries();
}
setIsPending(false);
}
}, [chainId, address, proposalId]);
const handleExecute = async () => {
setIsPending(true);

View File

@ -42,6 +42,7 @@ import {
import { useScreenSize } from "../../../hooks/useScreenSize";
import { useProposals } from "../../../hooks/governance";
import { useLocalStorage } from "../../../hooks/localstorage";
const MAX_PROPOSALS_TO_SHOW = 10;
@ -51,18 +52,15 @@ const ProposalsList = ({ chainId, address, config }) => {
const theme = useTheme();
const { network } = useParams();
const { getStorageValue, setStorageValue } = useLocalStorage();
const [proposalsFilter, setProposalFilter] = useState("active");
const myStoredProposals = localStorage.getItem(`${MY_PROPOSALS_PREFIX}-${chainId}-${address}`);
const [myProposals, setMyProposals] = useState(
myStoredProposals ? JSON.parse(myStoredProposals).map(id => BigInt(id)) : []
);
const myStoredProposals = getStorageValue(chainId, address, MY_PROPOSALS_PREFIX, []);
const [myProposals, setMyProposals] = useState(() => myStoredProposals.map(id => BigInt(id)));
const storedVotedProposals = localStorage.getItem(`${VOTED_PROPOSALS_PREFIX}-${chainId}-${address}`);
const [votedProposals, setVotedProposals] = useState(
storedVotedProposals ? JSON.parse(storedVotedProposals).map(id => BigInt(id)) : []
);
const storedVotedProposals = getStorageValue(chainId, address, VOTED_PROPOSALS_PREFIX, []);
const [votedProposals, setVotedProposals] = useState(() => storedVotedProposals.map(id => BigInt(id)));
const searchedIndexes = useMemo(() => {
switch (proposalsFilter) {

View File

@ -1,7 +1,7 @@
import { Avatar, Box, Link } from "@mui/material";
import { styled } from "@mui/material/styles";
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useCallback } from "react";
import { useSearchParams } from "react-router-dom";
import { DecimalBigNumber } from "../../../helpers/DecimalBigNumber";
@ -23,6 +23,7 @@ import {
STAKING_ADDRESSES,
} from "../../../constants/addresses";
import { useCurrentIndex } from "../../../hooks/staking";
import { useLocalStorage } from "../../../hooks/localstorage";
import { useBalance, useTokenSymbol } from "../../../hooks/tokens";
import { formatNumber } from "../../../helpers";
@ -82,20 +83,11 @@ export const StakeInputArea = ({
const [bottomTokenModalOpen, setBottomTokenModalOpen] = useState(false);
const [confirmationModalOpen, setConfirmationModalOpen] = useState(false);
const [formatDecimals, setFormatDecimals] = useState(localStorage.getItem("stake-decimals")
? localStorage.getItem("stake-decimals")
: "5"
);
const { getStorageValue, setStorageValue } = useLocalStorage();
const [isClaim, setIsClaim] = useState(localStorage.getItem("stake-isClaim")
? localStorage.getItem("stake-isClaim") === 'true'
: true
);
const [isTrigger, setIsTrigger] = useState(localStorage.getItem("stake-isTrigger")
? localStorage.getItem("stake-isTrigger") === 'true'
: true
);
const [formatDecimals, setFormatDecimals] = useState(() => getStorageValue(chainId, address, "stake-decimals", "5"));
const [isClaim, setIsClaim] = useState(() => getStorageValue(chainId, address, "stake-isClaim", true));
const [isTrigger, setIsTrigger] = useState(() => getStorageValue(chainId, address, "stake-isTrigger", true));
const [amount, setAmount] = useState("");
const [receiveAmount, setReceiveAmount] = useState("");
@ -110,23 +102,22 @@ export const StakeInputArea = ({
const { symbol: stnkSymbol } = useTokenSymbol(chainId, "STNK");
const { symbol: ghstSymbol } = useTokenSymbol(chainId, "GHST");
const setIsClaimInner = (value) => {
const setIsClaimInner = useCallback((value) => {
setIsClaim(value);
localStorage.setItem("stake-isClaim", value);
setStorageValue(chainId, address, "stake-isClaim", value);
}, [chainId, address]);
}
const setIsTriggerInner = (value) => {
const setIsTriggerInner = useCallback((value) => {
setIsTrigger(value);
localStorage.setItem("stake-isTrigger", value);
}
setStorageValue(chainId, address, "stake-isTrigger", value);
}, [chainId, address]);
const setFormatDecimalsInner = (value) => {
const setFormatDecimalsInner = useCallback((value) => {
if (Number(value) <= 17) {
setFormatDecimals(value);
localStorage.setItem("staking-decimals", value);
setStorageValue(chainId, address, "stake-decimals", value);
}
}
}, [chainId, address]);
useEffect(() => {
const innerBalance = upperToken === ghstSymbol ?

View File

@ -0,0 +1,27 @@
import { createContext, useContext, useState, useEffect } from "react";
const LocalStorageContext = createContext();
export const useLocalStorage = () => useContext(LocalStorageContext);
export const LocalStorageProvider = ({ children }) => {
const getStorageKey = (chainId, address, target) => {
return `${chainId}:${address}:${target}`;
}
const getStorageValue = (chainId, address, target, defaultValue) => {
const key = getStorageKey(chainId, address, target);
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : defaultValue;
}
const setStorageValue = (chainId, address, target, value) => {
const key = getStorageKey(prefix, chainId, address, target);
localStorage.setItem(key, JSON.stringify(value));
}
return (
<LocalStorageContext.Provider value={{ getStorageValue, setStorageValue }}>
{children}
</LocalStorageContext.Provider>
)
}

View File

@ -9,6 +9,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { WagmiProvider } from "wagmi";
import { config } from "./config";
import { UnstableProviderProvider, MetadataProviderProvider } from "./hooks/ghost"
import { LocalStorageProvider } from "./hooks/localstorage";
import ReactGA from "react-ga4";
const queryClient = new QueryClient();
@ -24,7 +25,9 @@ ReactDOM.createRoot(document.getElementById('root')).render(
<StyledEngineProvider injectFirst>
<UnstableProviderProvider>
<MetadataProviderProvider>
<App />
<LocalStorageProvider>
<App />
</LocalStorageProvider>
</MetadataProviderProvider>
</UnstableProviderProvider>
</StyledEngineProvider>