Skip to content

Commit

Permalink
fix: refactor fetchHistory function + improve log fetching and tokenL…
Browse files Browse the repository at this point in the history
…ist initialization
  • Loading branch information
VGau committed Oct 17, 2024
1 parent d9273ee commit 22d3687
Show file tree
Hide file tree
Showing 17 changed files with 409 additions and 398 deletions.
2 changes: 1 addition & 1 deletion bridge-ui/src/components/bridge/BridgeLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TokenType } from "@/config";
export default function BridgeLayout() {
const { isConnected } = useAccount();

const configContextValue = useTokenStore((state) => state.tokensConfig);
const configContextValue = useTokenStore((state) => state.tokensList);
const token = useChainStore((state) => state.token);

const methods = useForm<BridgeForm>({
Expand Down
2 changes: 1 addition & 1 deletion bridge-ui/src/components/bridge/modals/TokenDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default function TokenDetails({ token, onTokenClick, setValue, clearError
</div>
)}
{tokenNotFromCurrentLayer && (
<div className="text-left text-warning">
<div className="ml-10 text-left text-warning">
<p>Token is from other layer. Please swap networks to import token.</p>
</div>
)}
Expand Down
138 changes: 86 additions & 52 deletions bridge-ui/src/components/bridge/modals/TokenModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { isAddress, getAddress, Address, zeroAddress } from "viem";
import TokenDetails from "./TokenDetails";
import { NetworkType, TokenInfo, TokenType } from "@/config/config";
Expand All @@ -13,14 +13,15 @@ import { FieldValues, UseFormClearErrors, UseFormSetValue } from "react-hook-for
import { CiSearch } from "react-icons/ci";
import useTokenPrices from "@/hooks/useTokenPrices";
import { isEmptyObject } from "@/utils/utils";
import useDebounce from "@/hooks/useDebounce";

interface TokenModalProps {
setValue: UseFormSetValue<FieldValues>;
clearErrors: UseFormClearErrors<FieldValues>;
}

export default function TokenModal({ setValue, clearErrors }: TokenModalProps) {
const tokensConfig = useTokenStore((state) => state.tokensConfig);
const tokensList = useTokenStore((state) => state.tokensList);
const [filteredTokens, setFilteredTokens] = useState<TokenInfo[]>([]);
const [searchTokenIsNew, setSearchTokenIsNew] = useState<boolean>(false);
const { fillMissingTokenAddress } = useTokenFetch();
Expand All @@ -33,64 +34,102 @@ export default function TokenModal({ setValue, clearErrors }: TokenModalProps) {
}));
const { updateOrInsertUserTokenList } = useERC20Storage();
const [searchQuery, setSearchQuery] = useState("");

const { data } = useTokenPrices(
filteredTokens.map((token) =>
token.name === "Ether" ? zeroAddress : (safeGetAddress(token[networkLayer]) as Address),
useMemo(
() =>
filteredTokens.map((token) =>
token.name === "Ether" ? zeroAddress : (safeGetAddress(token[networkLayer]) as Address),
),
[filteredTokens, networkLayer],
),
fromChain?.id,
);

useMemo(async () => {
let found = false;
if (networkType === NetworkType.SEPOLIA || networkType === NetworkType.MAINNET) {
const filtered = (tokensConfig?.[networkType] ?? []).filter(
(token: TokenInfo) =>
(token[networkLayer] || token.type === TokenType.ETH) &&
(token.name.toLowerCase()?.includes(searchQuery) ||
token.symbol.toLowerCase()?.includes(searchQuery) ||
safeGetAddress(token[networkLayer])?.includes(searchQuery)),
);

if (filtered.length > 0) {
found = true;
setFilteredTokens(filtered);
setSearchTokenIsNew(false);
} else if (isAddress(searchQuery)) {
// Get token info from contract
const newToken = await fetchTokenInfo(searchQuery, networkType, fromChain);
if (newToken) {
await fillMissingTokenAddress(newToken);
const debouncedSearchQuery = useDebounce(searchQuery, 300);

const handleTokenSearch = useCallback(
async (query: string) => {
let found = false;

if (networkType === NetworkType.SEPOLIA || networkType === NetworkType.MAINNET) {
const currentNetworkTokens = tokensList?.[networkType] || [];

// Filter tokens based on the search query
const filtered = currentNetworkTokens.filter(
(token: TokenInfo) =>
(token[networkLayer] || token.type === TokenType.ETH) &&
(token.name.toLowerCase().includes(query) ||
token.symbol.toLowerCase().includes(query) ||
safeGetAddress(token[networkLayer])?.toLowerCase().includes(query)),
);

if (filtered.length > 0) {
found = true;
setFilteredTokens([newToken]);
setSearchTokenIsNew(true);
setFilteredTokens(filtered);
setSearchTokenIsNew(false);
} else if (isAddress(query)) {
// Fetch token info from the contract if the query is a valid address
const newToken = await fetchTokenInfo(query, networkType, fromChain);
if (newToken) {
await fillMissingTokenAddress(newToken);
found = true;
setFilteredTokens([newToken]);
setSearchTokenIsNew(true);
} else {
setSearchTokenIsNew(false);
}
} else {
setSearchTokenIsNew(false);
}
} else {
setSearchTokenIsNew(false);
}
}
if (!found) {
setFilteredTokens([]);
}
}, [searchQuery, networkType, networkLayer, tokensConfig, fromChain, fillMissingTokenAddress]);

const onTokenClick = (token: TokenInfo) => {
if (searchTokenIsNew && token[networkLayer]) {
updateOrInsertUserTokenList(token, networkType);
}
if (!found) {
setFilteredTokens([]);
}
},
[networkType, networkLayer, tokensList, fromChain, fillMissingTokenAddress],
);

setSearchTokenIsNew(false);
};
const handleTokenClick = useCallback(
(token: TokenInfo) => {
if (searchTokenIsNew && token[networkLayer]) {
updateOrInsertUserTokenList(token, networkType);
}
setSearchTokenIsNew(false);
},
[searchTokenIsNew, networkLayer, updateOrInsertUserTokenList, networkType],
);

const getTokenPrice = useCallback(
(token: TokenInfo): number | undefined => {
if (networkType === NetworkType.MAINNET && !isEmptyObject(data)) {
const tokenAddress = (safeGetAddress(token[networkLayer]) || zeroAddress).toLowerCase();
return data[tokenAddress]?.usd;
}
return undefined;
},
[networkType, networkLayer, data],
);

const normalizeInput = (input: string): string => {
if (isAddress(input)) {
return getAddress(input);
} else {
return input.toLowerCase();
}
return isAddress(input) ? getAddress(input) : input.toLowerCase();
};

useEffect(() => {
if (debouncedSearchQuery.trim() === "") {
if (networkType === NetworkType.SEPOLIA || networkType === NetworkType.MAINNET) {
setFilteredTokens(tokensList[networkType]);
setSearchTokenIsNew(false);
return;
}
setFilteredTokens([]);
setSearchTokenIsNew(false);
return;
}
handleTokenSearch(debouncedSearchQuery);
}, [debouncedSearchQuery, handleTokenSearch, networkType, tokensList]);

return (
<div id="token-picker-modal">
<form method="dialog" className="overflow-hidden">
Expand All @@ -111,16 +150,11 @@ export default function TokenModal({ setValue, clearErrors }: TokenModalProps) {
filteredTokens.map((token: TokenInfo, index: number) => (
<TokenDetails
token={token}
onTokenClick={onTokenClick}
key={index}
onTokenClick={handleTokenClick}
key={`token-details-${index}`}
setValue={setValue}
clearErrors={clearErrors}
tokenPrice={
(networkType === NetworkType.MAINNET &&
!isEmptyObject(data) &&
data[safeGetAddress(token[networkLayer])?.toLowerCase() || zeroAddress]?.usd) ||
undefined
}
tokenPrice={getTokenPrice(token)}
/>
))
) : (
Expand Down
6 changes: 3 additions & 3 deletions bridge-ui/src/components/transactions/NoTransaction.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Link from "next/link";
import ReloadHistoryButton from "./ReloadHistoryButton";
import RefreshHistoryButton from "./RefreshHistoryButton";
import { useFetchHistory } from "@/hooks";

export function NoTransactions() {
const { clearHistory } = useFetchHistory();
const { fetchHistory, isLoading } = useFetchHistory();
return (
<div className="rounded-lg border-2 border-card bg-cardBg p-4">
<ReloadHistoryButton clearHistory={clearHistory} />
<RefreshHistoryButton fetchHistory={fetchHistory} isLoading={isLoading} />
<div className="flex min-h-80 flex-col items-center justify-center gap-8 ">
<span className="text-[#C0C0C0]">No bridge transactions found</span>
<Link href="/" className="btn btn-primary max-w-xs rounded-full uppercase">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { Button } from "../ui";

export default function ReloadHistoryButton({ clearHistory }: { clearHistory: () => void }) {
export default function RefreshHistoryButton({
fetchHistory,
isLoading,
}: {
fetchHistory: () => void;
isLoading: boolean;
}) {
return (
<div className="flex justify-end">
<Button
id="reload-history-btn"
variant="link"
size="sm"
className="font-light normal-case text-gray-200 no-underline opacity-60 hover:text-primary hover:opacity-100"
onClick={clearHistory}
onClick={fetchHistory}
>
Reload history
{isLoading && <span className="loading loading-spinner loading-xs" />}
</Button>
</div>
);
Expand Down
23 changes: 8 additions & 15 deletions bridge-ui/src/components/transactions/Transactions.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
"use client";

import { useEffect, useMemo } from "react";
import { useBlockNumber } from "wagmi";
import TransactionItem from "./TransactionItem";
import { TransactionHistory } from "@/models/history";
import { formatDate, fromUnixTime } from "date-fns";
import { NoTransactions } from "./NoTransaction";
import { useFetchHistory } from "@/hooks";
import ReloadHistoryButton from "./ReloadHistoryButton";
import RefreshHistoryButton from "./RefreshHistoryButton";

const groupByDay = (transactions: TransactionHistory[]): Record<string, TransactionHistory[]> => {
return transactions.reduce(
Expand Down Expand Up @@ -61,13 +60,13 @@ function TransactionGroup({ date, transactions }: { date: string; transactions:
<div className="flex flex-col gap-2">
<span className="block text-base-content">{formatDate(date, "PPP")}</span>
{transactions.map((transaction, transactionIndex) => {
if (transaction.messages && transaction.messages.length > 0 && transaction.messages[0].status) {
const { messages, ...bridgingTransaction } = transaction;
if (transaction.message) {
const { message, ...bridgingTransaction } = transaction;
return (
<TransactionItem
key={`transaction-group-${date}-item-${transactionIndex}`}
transaction={bridgingTransaction}
message={messages[0]}
message={message}
/>
);
}
Expand All @@ -77,18 +76,12 @@ function TransactionGroup({ date, transactions }: { date: string; transactions:
}

export function Transactions() {
const { data: blockNumber } = useBlockNumber({
watch: true,
});

// Context
const { transactions, fetchHistory, isLoading, clearHistory } = useFetchHistory();
const { transactions, fetchHistory, isLoading } = useFetchHistory();

useEffect(() => {
if (blockNumber && blockNumber % 5n === 0n) {
fetchHistory();
}
}, [blockNumber, fetchHistory]);
fetchHistory();
}, [fetchHistory]);

const groupedTransactions = useMemo(() => groupByDay(transactions), [transactions]);

Expand All @@ -102,7 +95,7 @@ export function Transactions() {

return (
<div className="flex flex-col gap-8 rounded-lg border-2 border-card bg-cardBg p-4">
<ReloadHistoryButton clearHistory={clearHistory} />
<RefreshHistoryButton fetchHistory={fetchHistory} isLoading={isLoading} />
{Object.keys(groupedTransactions).map((date) => (
<TransactionGroup key={`transaction-group-${date}`} date={date} transactions={groupedTransactions[date]} />
))}
Expand Down
19 changes: 19 additions & 0 deletions bridge-ui/src/hooks/useDebounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useEffect, useState } from "react";

function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);

useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);

return () => {
clearTimeout(handler);
};
}, [value, delay]);

return debouncedValue;
}

export default useDebounce;
Loading

0 comments on commit 22d3687

Please sign in to comment.