Skip to content

Commit 13d860f

Browse files
authored
ui(swush): ui makeover with swush branding (#11)
* feat: swush ui * refactor: improve layout and spacing in Swap components This commit refines the layout and spacing of the SwapContainer, SwapAction, and SwapHeader components to enhance the overall user interface. The adjustments include reducing padding and margin values, which streamlines the visual hierarchy and improves the alignment of elements. Specifically, the padding in the SwapContainer has been decreased from `pt-8` to `pt-4`, and the spacing in the SwapField has been adjusted to maintain consistency across the UI. These changes not only contribute to a cleaner and more cohesive design but also ensure that the components remain responsive and visually appealing across different screen sizes. By focusing on modularity and maintainability, this refactor aligns with our commitment to producing high-quality, production-ready code while preserving existing functionality. * ui(swush): enhance visual design of SwapConfirmSheet component * ui(swush): enhance SwapField component with balance display and button size adjustment * ui: add icons * ui: override favicon * ui: update layout spacing and icon disappear in responsive mode * ui: add top margins for desktop and laptop screens * ui: fix top margins for desktop and laptop screens * ui: cleanup * ui: revert top margins for desktop and laptop screens * refactor: update input handling and balance display in Swap components * fix: reset route when input is cleared or set to zero in SwapContainer * fix: enhance input handling in SwapContainer and useSwapRoute This commit improves the input handling logic in the SwapContainer component by canceling any pending debounced calls when the input is cleared or set to zero. Additionally, it updates the useSwapRoute hook to include safety checks that prevent API calls with invalid amounts, ensuring that only valid inputs trigger route fetching. These changes enhance the robustness of the swap functionality and prevent unnecessary API calls, ultimately improving user experience and performance. * feat: add restart script * ui: adjust layout spacing in SwapContainer for improved responsiveness
1 parent 1c867ba commit 13d860f

File tree

21 files changed

+423
-135
lines changed

21 files changed

+423
-135
lines changed

apps/web/public/favicon.ico

205 KB
Binary file not shown.

apps/web/public/swush-logo.png

1.87 MB
Loading

apps/web/src/app/favicon.ico

-25.3 KB
Binary file not shown.

apps/web/src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const inter = Inter({
99

1010
export const metadata: Metadata = {
1111
title: "Swush",
12-
description: "DEX Aggregator on Polkadot Asset Hub",
12+
description: "DEX Aggregator on Polkadot Asset Hub"
1313
};
1414

1515
export default function RootLayout({

apps/web/src/app/page.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,36 @@ import { Suspense } from 'react'
22
import { SwapContainer } from '@/components/swap/SwapContainer'
33
import { LoadState } from '@/components/swap/ui/LoadState'
44
import { ErrorBoundary } from '@/components/ErrorBoundary'
5+
import Image from 'next/image'
56

67
export default function SwapPage() {
78
return (
89
<ErrorBoundary>
9-
<Suspense fallback={<LoadState />}>
10-
<SwapContainer />
11-
</Suspense>
10+
<div className="min-h-screen relative overflow-hidden">
11+
{/* Simplified Swush-inspired background with better contrast */}
12+
<div className="absolute inset-0 bg-gradient-to-br from-forest-900 via-forest-900 to-slate-900">
13+
</div>
14+
15+
{/* Swush Logo - Top Left Corner - Hidden on mobile */}
16+
<div className="absolute top-6 left-6 z-20 hidden md:block">
17+
<div className="relative">
18+
<Image
19+
src="/swush-logo.png"
20+
alt="Swush"
21+
width={100}
22+
height={100}
23+
className="drop-shadow-lg opacity-90 hover:opacity-100 transition-opacity duration-300"
24+
priority
25+
/>
26+
{/* Subtle glow effect and increase the effect time to 300ms*/}
27+
<div className="absolute inset-0 bg-gradient-to-br from-flame-400/15 to-flame-500/10 rounded-full filter blur-lg -z-10"></div>
28+
</div>
29+
</div>
30+
31+
<Suspense fallback={<LoadState />}>
32+
<SwapContainer />
33+
</Suspense>
34+
</div>
1235
</ErrorBoundary>
1336
)
1437
}

apps/web/src/app/swush-logo.png

1.87 MB
Loading

apps/web/src/components/swap/SwapContainer.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { calculateMinimumReceived } from '@/components/swap'
2828

2929
export function SwapContainer() {
3030
// UI state
31-
const [inputAmount, setInputAmount] = useState('0')
31+
const [inputAmount, setInputAmount] = useState('')
3232
const [slippageTolerance, setSlippageTolerance] = useState(10)
3333
const [transactionDeadline, setTransactionDeadline] = useState(20)
3434
const [insufficientBalance, setInsufficientBalance] = useState(false)
@@ -58,6 +58,7 @@ export function SwapContainer() {
5858
inputBalance,
5959
outputBalance,
6060
isBalanceLoading,
61+
balancesLoaded,
6162
resetBalances,
6263
refreshBalances
6364
} = useTokenBalances({
@@ -135,7 +136,7 @@ export function SwapContainer() {
135136
routeState,
136137
onSuccess: () => {
137138
// Reset all swap-related states
138-
setInputAmount('0');
139+
setInputAmount('');
139140
resetRoute(); // This will reset the output amount and route state
140141

141142
// Slight delay before closing the progress modal to show success state
@@ -148,7 +149,7 @@ export function SwapContainer() {
148149
},
149150
onError: (error) => {
150151
// Reset all swap-related states
151-
setInputAmount('0');
152+
setInputAmount('');
152153
resetRoute();
153154
setIsSwapping(false);
154155
resetConfirmationState();
@@ -181,7 +182,7 @@ export function SwapContainer() {
181182
// Reset route state
182183
resetRoute();
183184
// Reset input amount
184-
setInputAmount('0');
185+
setInputAmount('');
185186
}, [handleDisconnect, showConfirmation, resetConfirmationState, resetBalances, resetRoute]);
186187

187188
// Effect to reset states when tokens change
@@ -190,7 +191,7 @@ export function SwapContainer() {
190191
setInsufficientBalance(false);
191192

192193
// If we have both tokens and an input amount, fetch new route
193-
if (inputToken && outputToken && parseFloat(inputAmount) > 0) {
194+
if (inputToken && outputToken && inputAmount && parseFloat(inputAmount) > 0) {
194195
debouncedFetchRoute(inputAmount);
195196
}
196197
}, [inputToken, outputToken, inputAmount, debouncedFetchRoute]);
@@ -202,11 +203,15 @@ export function SwapContainer() {
202203

203204
if (value && parseFloat(value) > 0) {
204205
debouncedFetchRoute(value);
206+
} else {
207+
// Cancel any pending debounced calls and reset route when input is cleared or zero
208+
debouncedFetchRoute.cancel();
209+
resetRoute();
205210
}
206211

207212
setInsufficientBalance(value !== '' && parseFloat(value) > parseFloat(inputBalance));
208213
}
209-
}, [debouncedFetchRoute, inputBalance]);
214+
}, [debouncedFetchRoute, inputBalance, resetRoute]);
210215

211216
const percentageOptions = useMemo(() => [
212217
{ label: '25%', value: 0.25 },
@@ -240,15 +245,15 @@ export function SwapContainer() {
240245
/>
241246

242247
{/* Main Content */}
243-
<div className="min-h-screen w-full bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-slate-900 via-cyan-900 to-slate-900 flex flex-col items-center justify-start pt-8">
244-
<div className="w-full max-w-md space-y-8">
248+
<div className="min-h-screen w-full flex flex-col items-center justify-start px-6 py-8 md:px-4 md:py-4 relative z-10">
249+
<div className="w-full max-w-md space-y-6 md:space-y-4">
245250
<SwapHeader
246251
slippageTolerance={slippageTolerance}
247252
setSlippageTolerance={setSlippageTolerance}
248253
/>
249254

250255
<div className="space-y-8">
251-
<div className="space-y-6">
256+
<div className="">
252257
<SwapField
253258
type="input"
254259
token={inputToken}
@@ -265,6 +270,8 @@ export function SwapContainer() {
265270
percentageOptions={percentageOptions}
266271
onPercentageSelect={(value) => handleInputChange((parseFloat(inputBalance) * value).toString())}
267272
isLoading={isConnected && isBalanceLoading}
273+
balancesLoaded={balancesLoaded}
274+
isConnected={isConnected}
268275
/>
269276

270277
<ArrowSymbolDown />
@@ -282,6 +289,8 @@ export function SwapContainer() {
282289
setOpenDialog={setOpenOutputDialog}
283290
availableTokens={tokens}
284291
isLoading={routeState.isLoading || (isConnected && isBalanceLoading)}
292+
balancesLoaded={balancesLoaded}
293+
isConnected={isConnected}
285294
error={routeState.error}
286295
/>
287296
</div>
@@ -293,6 +302,7 @@ export function SwapContainer() {
293302
maxTransactionFee={estimatedFees || simulationResult?.estimatedFee || '0'}
294303
feeBreakdown={feeBreakdown || simulationResult?.feeBreakdown}
295304
route={routeDex || ''}
305+
isLoading={routeState.isLoading}
296306
/>
297307

298308
<SubmitButtonAction
@@ -302,7 +312,7 @@ export function SwapContainer() {
302312
setWalletAddress={setWalletAddress}
303313
onSwap={() => handleSwapExecution(isConnected)}
304314
insufficientBalance={insufficientBalance}
305-
disabled={!inputAmount || parseFloat(inputAmount) <= 0 || insufficientBalance}
315+
disabled={!inputAmount || inputAmount === '' || parseFloat(inputAmount) <= 0 || insufficientBalance}
306316
/>
307317
</div>
308318
</div>
@@ -320,11 +330,13 @@ export function SwapContainer() {
320330
<Toaster
321331
position="top-right"
322332
toastOptions={{
323-
className: "!bg-slate-900 !border !border-slate-800 !text-white",
333+
className: "!bg-forest-900/90 !border !border-forest-600/50 !text-forest-100",
324334
style: {
325-
background: 'rgb(15 23 42 / 0.9)',
326-
border: '1px solid rgb(51 65 85 / 0.5)',
327-
backdropFilter: 'blur(8px)',
335+
background: 'rgba(15, 41, 34, 0.9)',
336+
border: '1px solid rgba(44, 95, 93, 0.5)',
337+
backdropFilter: 'blur(12px)',
338+
borderRadius: '12px',
339+
boxShadow: '0 10px 40px rgba(255, 107, 53, 0.1)',
328340
},
329341
}}
330342
/>

apps/web/src/components/swap/hooks/useSwapRoute.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface RouteState {
2020
}
2121

2222
export function useSwapRoute({ inputToken, outputToken }: UseSwapRouteProps) {
23-
const [outputAmount, setOutputAmount] = useState('0');
23+
const [outputAmount, setOutputAmount] = useState('');
2424
const [routeDex, setRouteDex] = useState<string | null>(null);
2525
const [routeState, setRouteState] = useState<RouteState>({
2626
isLoading: false,
@@ -37,7 +37,7 @@ export function useSwapRoute({ inputToken, outputToken }: UseSwapRouteProps) {
3737

3838
// Reset states when tokens change
3939
useEffect(() => {
40-
setOutputAmount('0');
40+
setOutputAmount('');
4141
setRouteDex(null);
4242
setRouteState({
4343
isLoading: false,
@@ -50,7 +50,7 @@ export function useSwapRoute({ inputToken, outputToken }: UseSwapRouteProps) {
5050

5151
const fetchRouteAndUpdateOutput = useCallback(async (currentInputAmount: string) => {
5252
if (!inputToken || !outputToken || !currentInputAmount || parseFloat(currentInputAmount) <= 0) {
53-
setOutputAmount('0');
53+
setOutputAmount('');
5454
setRouteDex(null);
5555
setRouteState(prev => ({ ...prev, isLoading: false, error: null }));
5656
setEstimatedFees('0');
@@ -62,7 +62,7 @@ export function useSwapRoute({ inputToken, outputToken }: UseSwapRouteProps) {
6262
latestInputAmountRef.current = currentInputAmount;
6363

6464
setRouteState(prev => ({ ...prev, isLoading: true, error: null }));
65-
setOutputAmount('0');
65+
setOutputAmount('');
6666
setRouteDex('');
6767

6868
try {
@@ -72,8 +72,8 @@ export function useSwapRoute({ inputToken, outputToken }: UseSwapRouteProps) {
7272
amountIn: currentInputAmount
7373
});
7474

75-
// Only update if this is still the latest input amount
76-
if (latestInputAmountRef.current === currentInputAmount) {
75+
// Only update if this is still the latest input amount and ref hasn't been cleared
76+
if (latestInputAmountRef.current === currentInputAmount && latestInputAmountRef.current !== '') {
7777
setRouteDex(getNetworkDisplayName(route.dex));
7878
setRouteState({
7979
isLoading: false,
@@ -97,15 +97,15 @@ export function useSwapRoute({ inputToken, outputToken }: UseSwapRouteProps) {
9797
errorMessage = `No route available from ${inputToken.symbol} to ${outputToken.symbol}`;
9898
}
9999

100-
// Only update if this is still the latest input amount
101-
if (latestInputAmountRef.current === currentInputAmount) {
100+
// Only update if this is still the latest input amount and ref hasn't been cleared
101+
if (latestInputAmountRef.current === currentInputAmount && latestInputAmountRef.current !== '') {
102102
setRouteState({
103103
isLoading: false,
104104
error: errorMessage,
105105
data: null
106106
});
107107
setRouteDex('');
108-
setOutputAmount('0');
108+
setOutputAmount('');
109109
setEstimatedFees('0');
110110
setFeeBreakdown(undefined);
111111
}
@@ -115,10 +115,11 @@ export function useSwapRoute({ inputToken, outputToken }: UseSwapRouteProps) {
115115
const debouncedFetchRoute = useMemo(
116116
() =>
117117
debounce((amount: string) => {
118-
if (parseFloat(amount) > 0) {
118+
// Additional safety check to prevent API calls with invalid amounts
119+
if (amount && parseFloat(amount) > 0 && !isNaN(parseFloat(amount))) {
119120
fetchRouteAndUpdateOutput(amount);
120121
} else {
121-
setOutputAmount('0');
122+
setOutputAmount('');
122123
setRouteDex('');
123124
setRouteState(prev => ({ ...prev, isLoading: false, error: null }));
124125
setEstimatedFees('0');
@@ -130,7 +131,10 @@ export function useSwapRoute({ inputToken, outputToken }: UseSwapRouteProps) {
130131

131132
// Add resetRoute function
132133
const resetRoute = useCallback(() => {
133-
setOutputAmount('0');
134+
// Clear the latest input amount ref to prevent stale API responses
135+
latestInputAmountRef.current = '';
136+
137+
setOutputAmount('');
134138
setRouteDex('');
135139
setRouteState({
136140
isLoading: false,

apps/web/src/components/swap/hooks/useTokenBalances.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@ export function useTokenBalances({
1616
inputToken,
1717
outputToken
1818
}: UseTokenBalancesProps) {
19-
const [inputBalance, setInputBalance] = useState('0');
20-
const [outputBalance, setOutputBalance] = useState('0');
19+
const [inputBalance, setInputBalance] = useState('');
20+
const [outputBalance, setOutputBalance] = useState('');
2121
const [isBalanceLoading, setIsBalanceLoading] = useState(false);
22+
const [balancesLoaded, setBalancesLoaded] = useState(false);
2223

2324
// Function to fetch balances directly from the API
2425
const fetchBalances = useCallback(async () => {
2526
// Early return and reset if not connected or no wallet address
2627
if (!isConnected || !walletAddress) {
27-
setInputBalance('0');
28-
setOutputBalance('0');
28+
setInputBalance('');
29+
setOutputBalance('');
2930
setIsBalanceLoading(false);
31+
setBalancesLoaded(false);
3032
return;
3133
}
3234

@@ -46,6 +48,10 @@ export function useTokenBalances({
4648

4749
// Only update balances if still connected
4850
if (isConnected && walletAddress) {
51+
// Initialize balances to '0' for tokens that are being fetched
52+
if (inputToken) setInputBalance('0');
53+
if (outputToken) setOutputBalance('0');
54+
4955
// Process results and update states
5056
response.forEach(result => {
5157
if (result.status === 'success' && result.data) {
@@ -61,6 +67,9 @@ export function useTokenBalances({
6167
}
6268
}
6369
});
70+
71+
// Mark balances as loaded
72+
setBalancesLoaded(true);
6473
}
6574
} catch (error) {
6675
console.error('Failed to fetch token balances:', error);
@@ -91,9 +100,10 @@ export function useTokenBalances({
91100
// Only refresh after swap if still connected
92101
refreshBalances(true, txHash);
93102
} else {
94-
// Just reset the balances to 0 without refreshing
95-
setInputBalance('0');
96-
setOutputBalance('0');
103+
// Just reset the balances to empty without refreshing
104+
setInputBalance('');
105+
setOutputBalance('');
106+
setBalancesLoaded(false);
97107
}
98108
}, [refreshBalances, isConnected]);
99109

@@ -112,9 +122,10 @@ export function useTokenBalances({
112122
if (isConnected && walletAddress) {
113123
fetchIfMounted();
114124
} else {
115-
// Reset balances to 0 when disconnected
116-
setInputBalance('0');
117-
setOutputBalance('0');
125+
// Reset balances to empty when disconnected
126+
setInputBalance('');
127+
setOutputBalance('');
128+
setBalancesLoaded(false);
118129
}
119130

120131
// Set up regular refresh interval if connected
@@ -135,6 +146,7 @@ export function useTokenBalances({
135146
inputBalance,
136147
outputBalance,
137148
isBalanceLoading,
149+
balancesLoaded,
138150
resetBalances,
139151
refreshBalances
140152
};

apps/web/src/components/swap/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ export interface SwapFieldProps {
7272
percentageOptions?: Array<{ label: string; value: number }>;
7373
onPercentageSelect?: (value: number) => void;
7474
isLoading?: boolean;
75+
balancesLoaded?: boolean;
76+
isConnected?: boolean;
7577
error?: string | null;
7678
}
7779

0 commit comments

Comments
 (0)