Skip to content

Commit 0e286e7

Browse files
refactor: standardize shared configs, dialogs, and component structure (#627)
* refactor: extract helpers and types from large components * refactor(config): centralize chain IDs and replace magic numbers * feat(gas-estimator): display estimated USD gas cost with confidence indicator * refactor(dialog): replace ad-hoc confirmation flows with shared dialog
1 parent eb49809 commit 0e286e7

6 files changed

Lines changed: 193 additions & 25 deletions

File tree

src/components/GasEstimator.tsx

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,46 @@ interface GasEstimatorProps {
1818

1919
type GasSpeed = 'slow' | 'standard' | 'fast';
2020

21-
const ETH_PRICE_USD = 2500; // Mock ETH price
21+
type GasConfidence = {
22+
label: "High" | "Medium" | "Low";
23+
variance: string;
24+
};
25+
26+
const getGasConfidence = (
27+
gasPrice?: bigint
28+
): GasConfidence => {
29+
if (!gasPrice) {
30+
return {
31+
label: "Low",
32+
variance: "±20%",
33+
};
34+
}
35+
36+
const gwei = Number(formatUnits(gasPrice, 9));
37+
38+
if (gwei < 30) {
39+
return {
40+
label: "High",
41+
variance: "±5%",
42+
};
43+
}
44+
45+
if (gwei < 80) {
46+
return {
47+
label: "Medium",
48+
variance: "±10%",
49+
};
50+
}
51+
52+
return {
53+
label: "Low",
54+
variance: "±20%",
55+
};
56+
};
57+
58+
const confidence = getGasConfidence(adjustedGasPrice);
59+
60+
const ESTIMATED_ETH_PRICE_USD = 2500; // Mock ETH price
2261

2362
export const GasEstimator: React.FC<GasEstimatorProps> = ({
2463
to,
@@ -28,7 +67,7 @@ export const GasEstimator: React.FC<GasEstimatorProps> = ({
2867
}) => {
2968
const [selectedSpeed, setSelectedSpeed] = useState<GasSpeed>('standard');
3069
const [estimatedGas, setEstimatedGas] = useState<bigint | null>(null);
31-
70+
3271
const { data: baseGasPrice, isLoading: isGasPriceLoading } = useGasPrice();
3372
const { data: gasEstimate, isLoading: isGasEstimateLoading } = useEstimateGas({
3473
to: to as `0x${string}`,
@@ -62,7 +101,7 @@ export const GasEstimator: React.FC<GasEstimatorProps> = ({
62101
const totalCostWei = estimatedGas && adjustedGasPrice ? estimatedGas * adjustedGasPrice : null;
63102
const totalCostEth = totalCostWei ? formatUnits(totalCostWei, 18) : null;
64103
const totalCostUsd = totalCostEth ? (parseFloat(totalCostEth) * ETH_PRICE_USD).toFixed(2) : null;
65-
104+
66105
const isHighGas = adjustedGasPrice ? adjustedGasPrice > parseUnits('100', 9) : false;
67106

68107
return (
@@ -73,9 +112,9 @@ export const GasEstimator: React.FC<GasEstimatorProps> = ({
73112
<Gauge className="h-4 w-4 text-blue-500" />
74113
Gas Fee Estimator
75114
</CardTitle>
76-
<a
77-
href="https://etherscan.io/gastracker"
78-
target="_blank"
115+
<a
116+
href="https://etherscan.io/gastracker"
117+
target="_blank"
79118
rel="noopener noreferrer"
80119
className="text-xs text-blue-500 hover:underline flex items-center gap-1"
81120
>
@@ -117,8 +156,18 @@ export const GasEstimator: React.FC<GasEstimatorProps> = ({
117156
<div className="flex justify-between items-center">
118157
<span className="text-sm text-muted-foreground font-medium">Estimated Cost</span>
119158
<div className="text-right">
120-
<div className="text-lg font-bold text-gray-900 dark:text-white">
121-
${totalCostUsd || '0.00'}
159+
<div className="text-lg font-bold">
160+
≈ ${totalCostUsd || "0.00"}
161+
</div>
162+
163+
<div className="text-xs text-muted-foreground">
164+
Approximate USD value
165+
</div>
166+
167+
<div className="text-xs text-muted-foreground">
168+
{totalCostEth
169+
? parseFloat(totalCostEth).toFixed(6)
170+
: "0"} ETH
122171
</div>
123172
<div className="text-xs text-muted-foreground">
124173
{totalCostEth ? parseFloat(totalCostEth).toFixed(6) : '0'} ETH
@@ -136,6 +185,23 @@ export const GasEstimator: React.FC<GasEstimatorProps> = ({
136185
</div>
137186
<div className="flex justify-between items-center text-xs">
138187
<span className="text-muted-foreground">Gas Limit</span>
188+
<div className="flex justify-between items-center text-xs">
189+
<span className="text-muted-foreground">
190+
Confidence
191+
</span>
192+
193+
<Badge
194+
variant={
195+
confidence.label === "High"
196+
? "default"
197+
: confidence.label === "Medium"
198+
? "secondary"
199+
: "destructive"
200+
}
201+
>
202+
{confidence.label} ({confidence.variance})
203+
</Badge>
204+
</div>
139205
<span className="font-mono">{estimatedGas?.toString() || '0'}</span>
140206
</div>
141207
</div>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type {
2+
TransactionStatus,
3+
TransactionType,
4+
} from "@/store/transactionStore";
5+
6+
export const TRANSACTION_TYPES: TransactionType[] = [
7+
"purchase",
8+
"transfer",
9+
"management",
10+
"other",
11+
];
12+
13+
export const TRANSACTION_STATUSES: TransactionStatus[] = [
14+
"pending",
15+
"processing",
16+
"confirmed",
17+
"failed",
18+
"cancelled",
19+
];
20+
21+
export const isTransactionType = (
22+
value: string
23+
): value is TransactionType =>
24+
TRANSACTION_TYPES.includes(value as TransactionType);
25+
26+
export const isTransactionStatus = (
27+
value: string
28+
): value is TransactionStatus =>
29+
TRANSACTION_STATUSES.includes(value as TransactionStatus);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type {
2+
Transaction,
3+
TransactionStatus,
4+
TransactionType,
5+
} from "@/store/transactionStore";
6+
7+
export type SortBy = "timestamp" | "value" | "gasUsed";
8+
9+
export type SortOrder = "asc" | "desc";
10+
11+
export interface DateRange {
12+
from?: Date;
13+
to?: Date;
14+
}
15+
16+
export interface FilterTransactionsParams {
17+
transactions: Transaction[];
18+
typeFilter: TransactionType | "all";
19+
statusFilter: TransactionStatus | "all";
20+
propertyFilter: string;
21+
searchTerm: string;
22+
amountRange: [number, number];
23+
gasPriceRange: [number, number];
24+
dateRange: DateRange;
25+
sortBy: SortBy;
26+
sortOrder: SortOrder;
27+
getTransactionsByType: (
28+
type: TransactionType
29+
) => Transaction[];
30+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<AlertDialog>
2+
3+
<AlertDialogTrigger />
4+
5+
<AlertDialogContent>
6+
7+
<AlertDialogHeader />
8+
9+
<AlertDialogFooter />
10+
11+
</AlertDialogContent>
12+
13+
</AlertDialog>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export interface ConfirmDialogOptions {
2+
3+
title: string;
4+
5+
description?: string;
6+
7+
confirmText?: string;
8+
9+
cancelText?: string;
10+
11+
variant?: "default" | "destructive";
12+
13+
}

src/config/chains.ts

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,74 @@
1-
import {mainnet, polygon, bsc} from "wagmi/chains";
2-
import {getRpcUrl} from "./env";
1+
import { mainnet, polygon, bsc } from "wagmi/chains";
2+
import { getRpcUrl } from "./env";
3+
4+
/**
5+
* Supported EVM chain IDs.
6+
*/
7+
export const CHAIN_IDS = {
8+
ETHEREUM: 1,
9+
POLYGON: 137,
10+
BSC: 56,
11+
} as const;
12+
13+
export type ChainId = (typeof CHAIN_IDS)[keyof typeof CHAIN_IDS];
314

415
export const SUPPORTED_CHAINS = [mainnet, polygon, bsc];
516

617
/**
7-
* Get RPC URL for a chain, falling back to defaults if env var is not set
18+
* Get RPC URL for a chain, falling back to defaults if env var is not set.
819
*/
9-
const getChainRpcUrl = (chainId: number): string => {
20+
const getChainRpcUrl = (chainId: ChainId): string => {
1021
switch (chainId) {
11-
case 1:
22+
case CHAIN_IDS.ETHEREUM:
1223
return getRpcUrl("ethereum") ?? "https://mainnet.infura.io/v3/";
13-
case 137:
24+
25+
case CHAIN_IDS.POLYGON:
1426
return getRpcUrl("polygon") ?? "https://polygon-rpc.com";
15-
case 56:
27+
28+
case CHAIN_IDS.BSC:
1629
return getRpcUrl("bsc") ?? "https://bsc-dataseed1.binance.org";
30+
1731
default:
1832
return "";
1933
}
2034
};
2135

2236
export const CHAIN_CONFIG = {
23-
1: {
37+
[CHAIN_IDS.ETHEREUM]: {
38+
id: CHAIN_IDS.ETHEREUM,
2439
name: "Ethereum",
2540
symbol: "ETH",
2641
decimals: 18,
27-
rpcUrl: getChainRpcUrl(1),
42+
rpcUrl: getChainRpcUrl(CHAIN_IDS.ETHEREUM),
2843
blockExplorer: "https://etherscan.io",
2944
color: "#627EEA",
3045
},
31-
137: {
46+
47+
[CHAIN_IDS.POLYGON]: {
48+
id: CHAIN_IDS.POLYGON,
3249
name: "Polygon",
3350
symbol: "MATIC",
3451
decimals: 18,
35-
rpcUrl: getChainRpcUrl(137),
52+
rpcUrl: getChainRpcUrl(CHAIN_IDS.POLYGON),
3653
blockExplorer: "https://polygonscan.com",
3754
color: "#8247E5",
3855
},
39-
56: {
56+
57+
[CHAIN_IDS.BSC]: {
58+
id: CHAIN_IDS.BSC,
4059
name: "Binance Smart Chain",
4160
symbol: "BNB",
4261
decimals: 18,
43-
rpcUrl: getChainRpcUrl(56),
62+
rpcUrl: getChainRpcUrl(CHAIN_IDS.BSC),
4463
blockExplorer: "https://bscscan.com",
4564
color: "#F3BA2F",
4665
},
4766
} as const;
4867

49-
export type ChainId = keyof typeof CHAIN_CONFIG;
50-
51-
export const DEFAULT_CHAIN_ID: ChainId = 1;
68+
export const DEFAULT_CHAIN_ID: ChainId = CHAIN_IDS.ETHEREUM;
5269

5370
export const isChainId = (value: number): value is ChainId =>
5471
value in CHAIN_CONFIG;
5572

5673
export const toChainId = (value: number): ChainId | null =>
57-
isChainId(value) ? value : null;
74+
isChainId(value) ? value : null;

0 commit comments

Comments
 (0)