Token Swap Component

This commit is contained in:
Georges KABBOUCHI 2021-08-16 01:17:42 +03:00
parent 953c9bf11f
commit 00bec1a41f
10 changed files with 647 additions and 4 deletions

26
assets/icons/1inch.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,85 @@
<template>
<div class="flex flex-col flex-shrink-0 w-full">
<div class="relative rounded-sm">
<input
autocomplete="off"
class="w-full pl-4 pr-6 py-2 rounded-[6px] border border-grey-dark border-opacity-[0.15]"
:class="{ 'text-right': right, 'bg-primary-gray/[.15] text-primary-gray': $attrs.disabled }"
type="text"
inputmode="decimal"
:value="value"
v-bind="$attrs"
v-on="inputListeners"
/>
<div
v-if="!$attrs.disabled"
class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none select-none"
>
<span
class="font-semibold text-ocean-blue-pure text-11 dark:text-light"
>
%
</span>
</div>
</div>
<div class="h-0">
<transition
enter-active-class="duration-75 ease-out"
enter-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="duration-75 ease-in"
leave-class="opacity-100"
leave-to-class="opacity-0"
>
<p
v-if="touched && showError && error"
class="mt-1 text-red-600 text-11"
>
{{ error }}
</p>
</transition>
</div>
</div>
</template>
<script>
import { defineComponent, watch, ref } from '@nuxtjs/composition-api'
import { useInputListeners } from '@/composables/useInputListeners'
import { usePattern } from '@/composables/usePattern'
export default defineComponent({
inheritAttrs: false,
props: {
value: { type: String, default: '0' },
showError: { type: Boolean, default: false },
error: { type: String, default: null },
max: {
type: String,
default: '100',
},
right: { type: Boolean, default: false },
},
setup(props, context) {
const { amountPattern } = usePattern()
const amountFilter = (value) => amountPattern.test(value)
const params = { max: props.max }
const { inputListeners } = useInputListeners(props, context, amountFilter, params)
const touched = ref(false)
const stopTouchedWatcher = watch(
() => props.value,
() => {
touched.value = true
stopTouchedWatcher()
}
)
return { inputListeners, touched }
},
})
</script>

View File

@ -11,9 +11,9 @@
@click="toggle"
>
<div
class="box-content flex w-10 h-5 transition-colors duration-75 rounded-full p-2px"
class="box-content flex w-10 h-5 transition-colors duration-75 rounded-full p-0.5"
:class="{
'bg-green-pure dark:bg-opacity-75': checked,
'bg-primary-blue-dark': checked,
'bg-grey-light group-hover:bg-grey-pure group-hover:bg-opacity-20 focus:bg-grey-pure focus:bg-opacity-20 dark:group-hover:bg-opacity-38 dark:bg-opacity-20 ': !checked
}"
/>

View File

@ -5,7 +5,13 @@
<Spinner class="w-6 h-6" />
</div>
<div v-else class="flex flex-col flex-grow">
<div v-if="items.length" :class="{ ' divide-y divide-grey-light divide-opacity-50': divided }">
<div
v-if="items.length"
:class="[
itemsWrapperClasses,
{ ' divide-y divide-grey-light divide-opacity-50': divided }
]"
>
<div v-for="(item, i) in items" :key="i">
<slot name="default" :item="item" :index="i"></slot>
</div>
@ -27,6 +33,10 @@ export default defineComponent({
type: Array,
default: () => [],
},
itemsWrapperClasses: {
type: String,
default: ''
},
divided: {
type: Boolean,
default: false,

View File

@ -14,7 +14,7 @@
<div
v-close-popover="closeOnClick"
:border-radius="borderRadius"
class="flex overflow-hidden border border-opacity-50 shadow-lg border-grey-light dark:bg-dark-400"
class="flex overflow-hidden border border-opacity-50 shadow-lg border-grey-light"
:class="{ 'py-2': !noPadding }"
>
<slot />

View File

@ -0,0 +1,398 @@
<template>
<div>
<h3 class="text-primary-gray text-lg font-semibold mb-4">Swap</h3>
<div class="relative">
<div
class="shadow-sm relative border border-grey-dark/[0.15] rounded-lg p-4 mb-2"
>
<div class="flex justify-between items-center mb-3">
<div>
<input
type="text"
v-model.trim="token0.amount"
inputmode="decimal"
autocomplete="off"
autocorrect="off"
pattern="^[0-9]*[.,]?[0-9]*$"
placeholder="0.0"
minlength="1"
maxlength="79"
spellcheck="false"
:size="
token0.amount.length > 0
? Math.min(token0.amount.length, 20)
: 2
"
class="border-0 focus:outline-none focus:ring-0 text-lg font-semibold px-0 max-w-full"
/>
<span class="text-primary-gray text-lg font-semibold">{{
token0.symbol
}}</span>
</div>
<Dropdown>
<template #trigger="{ toggle }">
<button
class="flex items-center hover:bg-primary-blue-dark/10 p-1.5 rounded"
@click="toggle"
>
<svg
width="9"
height="6"
viewBox="0 0 9 6"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.5 4.5L3.89896 5.10104C4.2309 5.43299 4.76909 5.43299 5.10104 5.10104L4.5 4.5ZM8.60104 1.60104C8.93299 1.2691 8.93299 0.730905 8.60104 0.398959C8.2691 0.0670136 7.7309 0.0670135 7.39896 0.398959L8.60104 1.60104ZM1.60104 0.398959C1.26909 0.0670133 0.730905 0.0670132 0.398959 0.398959C0.0670138 0.730905 0.0670137 1.26909 0.398959 1.60104L1.60104 0.398959ZM5.10104 5.10104L8.60104 1.60104L7.39896 0.398959L3.89896 3.89896L5.10104 5.10104ZM5.10104 3.89896L1.60104 0.398959L0.398959 1.60104L3.89896 5.10104L5.10104 3.89896Z"
fill="#3F75FF"
/>
</svg>
<icon-currency
:currency="token0.symbol.toLowerCase()"
class="ml-2.5 w-9 h-9"
/>
</button>
</template>
<template #menu="{ close }">
<DropdownMenu
class="z-10 bg-white w-32 rounded border border-[#CFDCFF] drop-shadow-sm pt-0 pb-0"
align="right"
>
<List
:items="allTokens.filter(t => t.symbol != token1.symbol)"
class="p-1.5"
items-wrapper-classes="space-y-2 overflow-y-scroll max-h-[200px]"
>
<template v-slot:default="{ item }">
<div
@click="
selectToken0(item);
close();
"
class="cursor-pointer rounded-sm flex items-center p-1.5 bg-[#F6F7F9] border border-transparent hover:border-primary-blue-dark"
>
<icon-currency
:currency="item.symbol.toLowerCase()"
class="mr-1.5 w-6 h-6"
/>
<span
class="text-primary-blue-dark text-sm font-semibold"
>
{{ item.symbol }}
</span>
</div>
</template>
</List>
</DropdownMenu>
</template>
</Dropdown>
</div>
<div
class="flex justify-between items-center text-primary-gray font-medium"
>
<div>~ $ 9,321.69</div>
<div>Balance: 85 {{ token0.symbol }}</div>
</div>
<div
class="absolute -bottom-1 border border-grey-dark/[0.15] border-b-0 inset-x-0 mx-auto rounded-t-full bg-white w-10 h-5 shadow-sm"
></div>
</div>
<div
class="shadow-sm relative border border-grey-dark/[0.15] rounded-lg p-4"
>
<div class="flex justify-between items-center mb-3">
<div>
<input
type="text"
v-model.trim="token1.amount"
readonly
inputmode="decimal"
autocomplete="off"
autocorrect="off"
pattern="^[0-9]*[.,]?[0-9]*$"
placeholder="0.0"
minlength="1"
maxlength="79"
spellcheck="false"
:size="
token1.amount.length > 0
? Math.min(token1.amount.length, 20)
: 2
"
class="border-0 focus:outline-none focus:ring-0 text-lg font-semibold px-0 max-w-full"
/>
<span class="text-primary-gray text-lg font-semibold">{{
token1.symbol
}}</span>
</div>
<Dropdown>
<template #trigger="{ toggle }">
<button
class="flex items-center hover:bg-primary-blue-dark/10 p-1.5 rounded"
@click="toggle"
>
<svg
width="9"
height="6"
viewBox="0 0 9 6"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.5 4.5L3.89896 5.10104C4.2309 5.43299 4.76909 5.43299 5.10104 5.10104L4.5 4.5ZM8.60104 1.60104C8.93299 1.2691 8.93299 0.730905 8.60104 0.398959C8.2691 0.0670136 7.7309 0.0670135 7.39896 0.398959L8.60104 1.60104ZM1.60104 0.398959C1.26909 0.0670133 0.730905 0.0670132 0.398959 0.398959C0.0670138 0.730905 0.0670137 1.26909 0.398959 1.60104L1.60104 0.398959ZM5.10104 5.10104L8.60104 1.60104L7.39896 0.398959L3.89896 3.89896L5.10104 5.10104ZM5.10104 3.89896L1.60104 0.398959L0.398959 1.60104L3.89896 5.10104L5.10104 3.89896Z"
fill="#3F75FF"
/>
</svg>
<icon-currency
:currency="token1.symbol.toLowerCase()"
class="ml-2.5 w-9 h-9"
/>
</button>
</template>
<template #menu="{ close }">
<DropdownMenu
class="z-10 bg-white w-32 rounded border border-[#CFDCFF] drop-shadow-sm pt-0 pb-0"
align="right"
>
<List
:items="allTokens.filter(t => t.symbol != token0.symbol)"
class="p-1.5"
items-wrapper-classes="space-y-2 overflow-y-scroll max-h-[200px]"
>
<template v-slot:default="{ item }">
<div
@click="
selectToken1(item);
close();
"
class="cursor-pointer rounded-sm flex items-center p-1.5 bg-[#F6F7F9] border border-transparent hover:border-primary-blue-dark"
>
<icon-currency
:currency="item.symbol.toLowerCase()"
class="mr-1.5 w-6 h-6"
/>
<span
class="text-primary-blue-dark text-sm font-semibold"
>
{{ item.symbol }}
</span>
</div>
</template>
</List>
</DropdownMenu>
</template>
</Dropdown>
</div>
<div
class="flex justify-between items-center text-primary-gray font-medium"
>
<div>~ $ 9,321.69 <span class="text-green-pure">(0.188%)</span></div>
<div>Balance: 85 {{ token1.symbol }}</div>
</div>
<div
class="shadow-sm absolute -top-1 border border-grey-dark/[0.15] border-t-0 inset-x-0 mx-auto rounded-b-full bg-white w-10 h-5"
></div>
</div>
<div class="absolute inset-y-0 my-auto h-2 w-full bg-white"></div>
<div
@click="switchTokens"
class="cursor-pointer absolute inset-0 m-auto h-[30px] w-[30px] bg-white border border-[#D1D7E1] rounded-full flex items-center justify-center drop-shadow-sm"
>
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.25333 6.0701C2.26024 6.14317 2.27865 6.21659 2.30753 6.2869C2.42378 6.5674 2.69665 6.74979 3.0004 6.74979H4.5004L4.5004 14.9998H6.0004L6.0004 6.74979H7.5004C7.8034 6.74979 8.07702 6.5674 8.19327 6.2869C8.23152 6.1939 8.2504 6.09654 8.2504 5.99979C8.2504 5.80479 8.17392 5.61277 8.03067 5.46952L5.78067 3.21952C5.48742 2.92627 5.01263 2.92627 4.72013 3.21952L2.47013 5.46952C2.30925 5.63039 2.23259 5.8509 2.25333 6.0701ZM9.75333 11.9295C9.73259 12.1487 9.80925 12.3692 9.97012 12.5301L12.2201 14.7801C12.5126 15.0733 12.9874 15.0733 13.2807 14.7801L15.5307 12.5301C15.6739 12.3868 15.7504 12.1948 15.7504 11.9998C15.7504 11.903 15.7315 11.8057 15.6933 11.7127C15.577 11.4322 15.3034 11.2498 15.0004 11.2498H13.5004L13.5004 2.99979H12.0004L12.0004 11.2498H10.5004C10.1974 11.2498 9.92378 11.4322 9.80753 11.7127C9.77865 11.783 9.76024 11.8564 9.75333 11.9295Z"
fill="#3F75FF"
/>
</svg>
</div>
</div>
<div class="text-right mt-[18px] font-medium mb-1">
1 DAI = 0.0004272 ETH
</div>
<div class="mt-6 flex items-center justify-between">
<div class="text-[#A5AEC5] text-sm font-medium">
Slippage tolerance
</div>
<div>
<toggle-button
:checked="customSlippage"
@change="customSlippage = $event"
label="Custom"
/>
</div>
</div>
<div class="mt-4 flex items-center justify-between space-x-4">
<div class="w-1/2">
<div
v-if="customSlippage"
class="text-sm font-medium flex items-center justify-around bg-primary-gray/[.15] text-primary-gray w-full px-4 py-2 rounded-[6px] border border-grey-dark border-opacity-[0.15]"
>
<span>0.1%</span>
<span>0.5%</span>
<span>1%</span>
<span>3%</span>
</div>
<div
v-else
class="text-sm font-medium flex items-center border-primary-blue-dark text-primary-gray w-full p-1 rounded-[6px] border "
>
<button
class="w-1/4 py-2"
@click="slippage = '0.1'"
:class="{
'rounded bg-primary-blue-dark text-white': slippage === '0.1'
}"
>
0.1%
</button>
<button
class="w-1/4 py-2"
@click="slippage = '0.5'"
:class="{
'rounded bg-primary-blue-dark text-white': slippage === '0.5'
}"
>
0.5%
</button>
<button
class="w-1/4 py-2"
@click="slippage = '1'"
:class="{
'rounded bg-primary-blue-dark text-white': slippage === '1'
}"
>
1%
</button>
<button
class="w-1/4 py-2"
@click="slippage = '3'"
:class="{
'rounded bg-primary-blue-dark text-white': slippage === '3'
}"
>
3%
</button>
</div>
</div>
<div class="w-1/2">
<input-percent
ref="customPercentInput"
right
max="10"
v-model="customSlippageValue"
placeholder="Custom"
:disabled="!customSlippage"
/>
</div>
</div>
<div class="mt-7">
<ButtonCTA class="px-8 h-16 w-full">Swap</ButtonCTA>
</div>
</div>
</template>
<script>
import { computed, defineComponent, reactive, ref } from '@nuxtjs/composition-api'
import { useNetwork } from '~/composables/useNetwork'
import tokens from '~/constant/tokens'
import Button from '../Button.vue'
import Dropdown from '../common/input/Dropdown.vue'
import InputPercent from '../common/input/InputPercent.vue'
import ToggleButton from '../common/input/ToggleButton.vue'
import List from '../common/list/List.vue'
import Menu from '../common/menu/Menu.vue'
import IconCurrency from '../IconCurrency.vue'
import DropdownMenu from '../protocols/DropdownMenu.vue'
import ButtonCTA from '~/components/common/input/ButtonCTA.vue'
export default defineComponent({
components: { List, Menu, IconCurrency, Button, Dropdown, DropdownMenu, ToggleButton, InputPercent, ButtonCTA },
setup() {
const { activeNetworkId } = useNetwork()
const allTokens = computed(() => tokens[activeNetworkId.value].allTokens)
const token0 = reactive({
address: '',
symbol: 'ETH',
amount: '4'
})
const token1 = reactive({
address: '',
symbol: 'DAI',
amount: '9322.75'
})
const slippage = ref('3')
const customSlippage = ref(false)
const customSlippageValue = ref("0.0")
const switchTokens = () => {
const token0Raw = JSON.parse(JSON.stringify(token0))
const token1Raw = JSON.parse(JSON.stringify(token1))
token0.symbol = token1Raw.symbol
token0.address = token1Raw.address
token0.amount = token1Raw.amount
token1.symbol = token0Raw.symbol
token1.address = token0Raw.address
token1.amount = token0Raw.amount
}
const selectToken0 = (token) => {
token0.symbol = token.symbol
token0.address = token.address
token0.amount = "0"
}
const selectToken1 = (token) => {
token1.symbol = token.symbol
token1.address = token.address
token1.amount = "0"
}
return {
customSlippage,
token0,
selectToken0,
token1,
selectToken1,
switchTokens,
allTokens,
customSlippageValue,
slippage,
}
},
})
</script>

View File

@ -0,0 +1,7 @@
export const use1InchSwap = () => {
const swap = ({ buyAddress, sellAddress, sellAmount, unitAmount }) => {};
return {
swap
};
};

View File

@ -35,6 +35,7 @@ import { useNetwork } from "~/composables/useNetwork";
import AaveIcon from "~/assets/icons/aave.svg?inline";
import CompoundIcon from "~/assets/icons/compound.svg?inline";
import MakerIcon from "~/assets/icons/makerdao.svg?inline";
import OneInchIcon from "~/assets/icons/1inch.svg?inline";
const appsPerNetwork = {
mainnet: [
@ -58,6 +59,13 @@ const appsPerNetwork = {
name: "MakerDAO",
url: "/mainnet/maker",
description: "Collateralized Debt"
},
{
id: "1inch",
icon: OneInchIcon,
name: "1inch",
url: "/mainnet/1inch",
description: "DEX aggregator with the best prices on the market"
}
],
polygon: [
@ -67,6 +75,13 @@ const appsPerNetwork = {
name: "Aave v2",
url: "/polygon/aave-v2",
description: "Lend and borrow straight from your Gnosis Safe"
},
{
id: "1inch",
icon: OneInchIcon,
name: "1inch",
url: "/polygon/1inch",
description: "DEX aggregator with the best prices on the market"
}
]
};

51
pages/mainnet/1inch.vue Normal file
View File

@ -0,0 +1,51 @@
<template>
<div>
<div>
<nuxt-link
to="/"
class="text-[#C0C5D7] text-lg font-semibold flex items-center"
>
<BackIcon class="w-4 h-4 mr-3" />
Apps
</nuxt-link>
</div>
<div class="mt-10 flex items-center">
<div class="flex items-center">
<div
style="background: radial-gradient(42.15% 42.15% at 48.94% 48.94%, #D6DAE0 75.67%, #F0F3F9 100%), #C4C4C4;"
class="w-16 h-16 rounded-full flex items-center justify-center border border-[#CCDCF3]"
>
<div
class="w-12 h-12 rounded-full flex items-center justify-center bg-[#1874FF]"
>
<OneInchIcon Icon class="w-10 h-10 text-white" />
</div>
</div>
<h1 class="ml-4 text-primary-black text-2xl font-semibold">1Inch</h1>
</div>
</div>
<div class="mx-auto w-[512px] shadow rounded-[10px] px-8 py-12">
<swap-card />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import BackIcon from "~/assets/icons/back.svg?inline";
import OneInchIcon from "~/assets/icons/1inch.svg?inline";
import SwapCard from "~/components/swap/SwapCard.vue";
export default defineComponent({
components: {
BackIcon,
OneInchIcon,
SwapCard
},
setup() {
return {};
}
});
</script>

51
pages/polygon/1inch.vue Normal file
View File

@ -0,0 +1,51 @@
<template>
<div>
<div>
<nuxt-link
to="/"
class="text-[#C0C5D7] text-lg font-semibold flex items-center"
>
<BackIcon class="w-4 h-4 mr-3" />
Apps
</nuxt-link>
</div>
<div class="mt-10 flex items-center">
<div class="flex items-center">
<div
style="background: radial-gradient(42.15% 42.15% at 48.94% 48.94%, #D6DAE0 75.67%, #F0F3F9 100%), #C4C4C4;"
class="w-16 h-16 rounded-full flex items-center justify-center border border-[#CCDCF3]"
>
<div
class="w-12 h-12 rounded-full flex items-center justify-center bg-[#1874FF]"
>
<OneInchIcon Icon class="w-10 h-10 text-white" />
</div>
</div>
<h1 class="ml-4 text-primary-black text-2xl font-semibold">1Inch</h1>
</div>
</div>
<div class="sm:mx-auto sm:w-[512px] sm:shadow sm:rounded-[10px] sm:px-8 sm:py-12">
<swap-card />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import BackIcon from "~/assets/icons/back.svg?inline";
import OneInchIcon from "~/assets/icons/1inch.svg?inline";
import SwapCard from "~/components/swap/SwapCard.vue";
export default defineComponent({
components: {
BackIcon,
OneInchIcon,
SwapCard
},
setup() {
return {};
}
});
</script>