221 lines
5.7 KiB
Vue
221 lines
5.7 KiB
Vue
<template>
|
|
<div class="local-swap-widget">
|
|
<template v-if="!canSwap">
|
|
<p class="swap-warning">Connect your wallet to swap ETH for $KRK on the local Uniswap pool.</p>
|
|
</template>
|
|
<template v-else>
|
|
<div class="swap-tabs">
|
|
<button
|
|
class="swap-tab"
|
|
:class="{ active: mode === 'buy' }"
|
|
data-testid="swap-mode-buy"
|
|
@click="setMode('buy')"
|
|
>Buy</button>
|
|
<button
|
|
class="swap-tab"
|
|
:class="{ active: mode === 'sell' }"
|
|
data-testid="swap-mode-sell"
|
|
@click="setMode('sell')"
|
|
>Sell</button>
|
|
</div>
|
|
|
|
<template v-if="mode === 'buy'">
|
|
<div class="swap-field">
|
|
<label for="local-swap-amount" class="swap-label">ETH to spend</label>
|
|
<input id="local-swap-amount" data-testid="swap-amount-input" :value="swapAmount" @input="swapAmount = ($event.target as HTMLInputElement).value" type="number" min="0" step="0.01" class="swap-input" :disabled="swapping" />
|
|
</div>
|
|
<button class="swap-button" data-testid="swap-buy-button" :disabled="swapping" @click="buyKrk">
|
|
{{ swapping ? 'Submitting…' : 'Buy KRK' }}
|
|
</button>
|
|
<p class="swap-hint">Wraps ETH → WETH, approves the swap router, then trades into $KRK.</p>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<div class="swap-field">
|
|
<div class="swap-label-row">
|
|
<label for="local-sell-amount" class="swap-label">KRK to sell</label>
|
|
<span class="swap-balance">Balance: {{ formattedKrkBalance }} KRK</span>
|
|
</div>
|
|
<div class="swap-input-row">
|
|
<input id="local-sell-amount" data-testid="swap-sell-amount-input" :value="sellAmount" @input="sellAmount = ($event.target as HTMLInputElement).value" type="number" min="0" step="0.01" class="swap-input" :disabled="selling" />
|
|
<button class="max-button" :disabled="selling" @click="setMax">Max</button>
|
|
</div>
|
|
</div>
|
|
<button class="swap-button sell-button" data-testid="swap-sell-button" :disabled="selling" @click="sellKrk">
|
|
{{ selling ? (sellPhase === 'approving' ? 'Approving…' : 'Selling…') : 'Sell KRK' }}
|
|
</button>
|
|
<p class="swap-hint">Approves the swap router, then trades $KRK → WETH.</p>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted, computed } from 'vue';
|
|
import { useSwapKrk } from '@/composables/useSwapKrk';
|
|
import { formatUnits } from 'viem';
|
|
|
|
const { swapAmount, swapping, canSwap, buyKrk, sellAmount, selling, sellPhase, krkBalance, loadKrkBalance, sellKrk } = useSwapKrk();
|
|
|
|
const mode = ref<'buy' | 'sell'>('buy');
|
|
|
|
const formattedKrkBalance = computed(() => {
|
|
const s = formatUnits(krkBalance.value, 18);
|
|
const [whole, frac = ''] = s.split('.');
|
|
const truncFrac = frac.slice(0, 4).replace(/0+$/, '');
|
|
const wholeFormatted = BigInt(whole).toLocaleString();
|
|
return truncFrac ? `${wholeFormatted}.${truncFrac}` : wholeFormatted;
|
|
});
|
|
|
|
function setMode(m: 'buy' | 'sell') {
|
|
mode.value = m;
|
|
if (m === 'sell') loadKrkBalance();
|
|
}
|
|
|
|
async function setMax() {
|
|
await loadKrkBalance();
|
|
sellAmount.value = formatUnits(krkBalance.value, 18);
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (canSwap.value) loadKrkBalance();
|
|
});
|
|
</script>
|
|
|
|
<style lang="sass" scoped>
|
|
.local-swap-widget
|
|
display: flex
|
|
flex-direction: column
|
|
gap: 16px
|
|
|
|
.swap-warning
|
|
margin: 0
|
|
color: #FFB347
|
|
font-size: 14px
|
|
|
|
.swap-tabs
|
|
display: flex
|
|
gap: 8px
|
|
|
|
.swap-tab
|
|
flex: 1
|
|
padding: 8px 0
|
|
border-radius: 8px
|
|
border: 1px solid #3a3a3a
|
|
background: #111111
|
|
color: #a3a3a3
|
|
font-size: 14px
|
|
font-weight: 600
|
|
cursor: pointer
|
|
transition: all 0.2s
|
|
|
|
&.active
|
|
background: #1e3a5f
|
|
border-color: #60a5fa
|
|
color: #ffffff
|
|
|
|
&:hover:not(.active)
|
|
border-color: #60a5fa
|
|
color: #ffffff
|
|
|
|
.swap-label
|
|
display: block
|
|
font-size: 13px
|
|
color: #a3a3a3
|
|
margin-bottom: 6px
|
|
|
|
.swap-label-row
|
|
display: flex
|
|
justify-content: space-between
|
|
align-items: baseline
|
|
margin-bottom: 6px
|
|
|
|
.swap-balance
|
|
font-size: 12px
|
|
color: #a3a3a3
|
|
|
|
.swap-field
|
|
display: flex
|
|
flex-direction: column
|
|
|
|
.swap-input-row
|
|
display: flex
|
|
gap: 8px
|
|
|
|
.swap-input
|
|
background: #111111
|
|
border: 1px solid #3a3a3a
|
|
border-radius: 8px
|
|
color: #ffffff
|
|
font-size: 16px
|
|
padding: 12px 16px
|
|
outline: none
|
|
transition: border-color 0.2s
|
|
flex: 1
|
|
min-width: 0
|
|
|
|
&:focus
|
|
border-color: #60a5fa
|
|
|
|
&:disabled
|
|
opacity: 0.5
|
|
cursor: not-allowed
|
|
|
|
.max-button
|
|
padding: 0 16px
|
|
border-radius: 8px
|
|
border: 1px solid #3a3a3a
|
|
background: #1a1a1a
|
|
color: #60a5fa
|
|
font-size: 13px
|
|
font-weight: 600
|
|
cursor: pointer
|
|
transition: all 0.2s
|
|
white-space: nowrap
|
|
|
|
&:hover:not(:disabled)
|
|
border-color: #60a5fa
|
|
background: #1e3a5f
|
|
|
|
&:disabled
|
|
opacity: 0.5
|
|
cursor: not-allowed
|
|
|
|
.swap-button
|
|
display: flex
|
|
align-items: center
|
|
justify-content: center
|
|
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%)
|
|
color: #ffffff
|
|
font-size: 16px
|
|
font-weight: 600
|
|
padding: 16px 32px
|
|
border-radius: 12px
|
|
border: none
|
|
cursor: pointer
|
|
transition: all 0.3s ease
|
|
box-shadow: 0 4px 12px rgba(96, 165, 250, 0.3)
|
|
|
|
&:hover:not(:disabled)
|
|
transform: translateY(-2px)
|
|
box-shadow: 0 6px 16px rgba(96, 165, 250, 0.4)
|
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)
|
|
|
|
&:disabled
|
|
opacity: 0.6
|
|
cursor: not-allowed
|
|
transform: none
|
|
|
|
&.sell-button
|
|
background: linear-gradient(135deg, #f472b6 0%, #ec4899 100%)
|
|
box-shadow: 0 4px 12px rgba(244, 114, 182, 0.3)
|
|
|
|
&:hover:not(:disabled)
|
|
box-shadow: 0 6px 16px rgba(244, 114, 182, 0.4)
|
|
background: linear-gradient(135deg, #ec4899 0%, #db2777 100%)
|
|
|
|
.swap-hint
|
|
margin: 0
|
|
font-size: 12px
|
|
color: #a3a3a3
|
|
</style>
|