Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e5516972a | ||
|
|
a3e3f0506c | ||
|
|
976c48ac4b | ||
|
|
7bcf5d90c2 | ||
|
|
358ae7410f | ||
|
|
a906fa136d | ||
|
|
7ec73e8c6f | ||
|
|
84afed78fb | ||
|
|
2b6be86b2e | ||
|
|
fdc03068f2 | ||
|
|
c58e91e073 | ||
|
|
13c0fcc681 | ||
|
|
5c1d560d0c | ||
|
|
a24ee193d4 | ||
|
|
9b325ac917 | ||
|
|
c3d770f713 | ||
|
|
3ef1694217 | ||
|
|
2b707e81c2 | ||
|
|
f6a9ab854c | ||
|
|
474af2fbfc | ||
|
|
4af059f6b7 | ||
|
|
23163be99d | ||
|
|
c1542707c2 | ||
|
|
dd351acb2e | ||
|
|
2370051243 | ||
|
|
ed5d3b5726 | ||
|
|
2e246f7560 | ||
|
|
81c8b04c7a | ||
|
|
cf61f5ecfd | ||
|
|
73ba77ca4f | ||
|
|
c7b2f1643c | ||
|
|
fa2def812c | ||
|
|
8a1dab4764 | ||
|
|
75c02ed1b9 | ||
|
|
8eb10f493f | ||
|
|
1ec4780e14 | ||
|
|
0186afe971 | ||
|
|
bca93282ac | ||
|
|
fe06c46c3f | ||
|
|
4908dff58b | ||
|
|
9fa2b34a5d | ||
|
|
e93cac6086 | ||
|
|
3227e3209c | ||
|
|
54cff28ba0 | ||
|
|
d5f9c8f6fa | ||
|
|
0f17a67e00 | ||
|
|
c90f468d3c | ||
|
|
c4dae86b5f | ||
|
|
92f6cb4d35 | ||
|
|
9cda680494 | ||
|
|
1d98afbdfb | ||
|
|
be7c1c6132 | ||
|
|
5b49fdcffd | ||
|
|
b487949482 | ||
|
|
e29aa6f440 | ||
|
|
fc62f7b031 | ||
|
|
617388d8e1 | ||
|
|
9ddc843c01 | ||
|
|
659db5ef04 | ||
|
|
9205909f9f | ||
|
|
b61dfb2de0 | ||
|
|
53d52f3a14 | ||
|
|
e6117d77d7 | ||
|
|
6e59f77153 | ||
|
|
c8a5c74c71 | ||
|
|
6516ae0509 | ||
|
|
5408791d9f | ||
|
|
fc8fac4b48 | ||
|
|
149f0e4767 | ||
|
|
1287a0e9a7 | ||
|
|
bdf4e37018 | ||
|
|
61032e3d92 | ||
|
|
3cf9cd0cac | ||
|
|
8a45b3a9f8 | ||
|
|
e6b5bc7748 | ||
|
|
65c1dc0f06 | ||
|
|
15099c602f | ||
|
|
d796379119 | ||
|
|
475b0fbf01 | ||
|
|
135a01b7fd | ||
|
|
fd61376b36 | ||
|
|
42fcae0465 | ||
|
|
e483fd537c | ||
|
|
1f003f19bc | ||
|
|
4b4b5af550 | ||
|
|
659cc34d68 | ||
|
|
814e6a4eac | ||
|
|
f15361599f | ||
|
|
0a51a80e0c | ||
|
|
8ad42cc0f2 | ||
|
|
ae60edd324 | ||
|
|
fbe32e78ab | ||
|
|
b956c8ec2b | ||
|
|
1d429f039a |
No files matched your search
@@ -102,3 +102,19 @@ cd P2Pix-Front-End
|
|||||||
# Run docker-compose up command
|
# Run docker-compose up command
|
||||||
docker-compose up
|
docker-compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Backend Communication
|
||||||
|
|
||||||
|
Backend Repo: `https://gitea.kosmos.org/hueso/helpix`
|
||||||
|
|
||||||
|
Backend Endpoint: `https://api.p2pix.co/release/1279331`
|
||||||
|
|
||||||
|
curl -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer {api-key}" \
|
||||||
|
-d '{"query": "{ depositAddeds { id seller token amount } }"}' \
|
||||||
|
https://api.studio.thegraph.com/query/113713/p-2-pix/sepolia
|
||||||
|
|
||||||
|
https://api.studio.thegraph.com/query/113713/p-2-pix/1
|
||||||
|
|
||||||
|
curl --request POST --url 'https://api.hm.bb.com.br/testes-portal-desenvolvedor/v1/boletos-pix/pagar?gw-app-key=95cad3f03fd9013a9d15005056825665' --header 'content-type: application/json' --data '{"pix":"00020101021226070503***63041654" }'
|
||||||
@@ -5,12 +5,10 @@
|
|||||||
"start": "vite --host=0.0.0.0 --port 3000",
|
"start": "vite --host=0.0.0.0 --port 3000",
|
||||||
"build": "run-p type-check build-only",
|
"build": "run-p type-check build-only",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "vitest",
|
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"coverage": "vitest run --coverage",
|
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --noEmit",
|
"type-check": "vue-tsc --skipLibCheck --noEmit",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore --fix",
|
||||||
"lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
"lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -18,12 +16,14 @@
|
|||||||
"@headlessui/vue": "^1.7.3",
|
"@headlessui/vue": "^1.7.3",
|
||||||
"@heroicons/vue": "^2.0.12",
|
"@heroicons/vue": "^2.0.12",
|
||||||
"@vueuse/core": "^9.12.0",
|
"@vueuse/core": "^9.12.0",
|
||||||
|
"@web3-onboard/injected-wallets": "^2.11.2",
|
||||||
|
"@web3-onboard/vue": "^2.9.0",
|
||||||
"alchemy-sdk": "^2.3.0",
|
"alchemy-sdk": "^2.3.0",
|
||||||
"axios": "^1.2.1",
|
"axios": "^1.2.1",
|
||||||
"crc": "^3.8.0",
|
|
||||||
"marked": "^4.2.12",
|
"marked": "^4.2.12",
|
||||||
"pinia": "^2.0.23",
|
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
|
"viem": "^2.31.3",
|
||||||
|
"vite-svg-loader": "^5.1.0",
|
||||||
"vue": "^3.2.41",
|
"vue": "^3.2.41",
|
||||||
"vue-markdown": "^2.2.4",
|
"vue-markdown": "^2.2.4",
|
||||||
"vue-router": "^4.1.5"
|
"vue-router": "^4.1.5"
|
||||||
@@ -31,33 +31,27 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.20.2",
|
"@babel/preset-env": "^7.20.2",
|
||||||
"@babel/preset-typescript": "^7.18.6",
|
"@babel/preset-typescript": "^7.18.6",
|
||||||
"@pinia/testing": "^0.0.14",
|
|
||||||
"@rushstack/eslint-patch": "^1.1.4",
|
"@rushstack/eslint-patch": "^1.1.4",
|
||||||
"@types/crc": "^3.8.0",
|
"@types/crc": "^3.8.0",
|
||||||
"@types/jest": "^27.0.0",
|
|
||||||
"@types/marked": "^4.0.8",
|
"@types/marked": "^4.0.8",
|
||||||
"@types/node": "^16.11.68",
|
"@types/node": "^16.11.68",
|
||||||
"@types/qrcode": "^1.5.0",
|
"@types/qrcode": "^1.5.0",
|
||||||
"@types/vue-markdown": "^2.2.1",
|
"@types/vue-markdown": "^2.2.1",
|
||||||
"@vitejs/plugin-vue": "^3.1.2",
|
"@vitejs/plugin-vue": "^3.1.2",
|
||||||
"@vitejs/plugin-vue-jsx": "^2.0.1",
|
"@vitejs/plugin-vue-jsx": "^2.0.1",
|
||||||
"@vitest/coverage-c8": "^0.28.2",
|
|
||||||
"@vue/eslint-config-prettier": "^7.0.0",
|
"@vue/eslint-config-prettier": "^7.0.0",
|
||||||
"@vue/eslint-config-typescript": "^11.0.0",
|
"@vue/eslint-config-typescript": "^11.0.0",
|
||||||
"@vue/test-utils": "^2.2.7",
|
|
||||||
"@vue/tsconfig": "^0.1.3",
|
"@vue/tsconfig": "^0.1.3",
|
||||||
|
"@wagmi/cli": "^2.3.1",
|
||||||
"autoprefixer": "^10.4.12",
|
"autoprefixer": "^10.4.12",
|
||||||
"eslint": "^8.22.0",
|
"eslint": "^8.22.0",
|
||||||
"eslint-plugin-vue": "^9.3.0",
|
"eslint-plugin-vue": "^9.3.0",
|
||||||
"ethers": "^5.7.2",
|
|
||||||
"jsdom": "^21.1.0",
|
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"postcss": "^8.4.18",
|
"postcss": "^8.4.18",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"tailwindcss": "^3.2.1",
|
"tailwindcss": "^3.2.1",
|
||||||
"typescript": "~4.7.4",
|
"typescript": "~5.8.2",
|
||||||
"vite": "^3.1.8",
|
"vite": "^3.1.8",
|
||||||
"vitest": "^0.28.1",
|
"vue-tsc": "^2.2.8"
|
||||||
"vue-tsc": "^1.0.8"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,57 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
import TopBar from "@/components/TopBar/TopBar.vue";
|
import TopBar from "@/components/TopBar/TopBar.vue";
|
||||||
import SpinnerComponent from "@/components/SpinnerComponent.vue";
|
import SpinnerComponent from "@/components/ui/SpinnerComponent.vue";
|
||||||
|
import ToasterComponent from "@/components/ui/ToasterComponent.vue";
|
||||||
|
import { init, useOnboard } from "@web3-onboard/vue";
|
||||||
|
import injectedModule from "@web3-onboard/injected-wallets";
|
||||||
|
import { Networks, DEFAULT_NETWORK } from "@/config/networks";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const injected = injectedModule();
|
||||||
|
const targetNetwork = ref(DEFAULT_NETWORK);
|
||||||
|
|
||||||
|
const web3Onboard = init({
|
||||||
|
wallets: [injected],
|
||||||
|
chains: Object.values(Networks).map((network) => ({
|
||||||
|
id: network.id,
|
||||||
|
token: network.nativeCurrency.symbol,
|
||||||
|
label: network.name,
|
||||||
|
rpcUrl: network.rpcUrls.default.http[0],
|
||||||
|
})),
|
||||||
|
connect: {
|
||||||
|
autoConnectLastWallet: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { connectedWallet } = useOnboard();
|
||||||
|
if (!connectedWallet) {
|
||||||
|
web3Onboard.connectWallet();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div class="p-3 sm:p-4 md:p-8">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
<RouterView v-slot="{ Component }">
|
<RouterView v-slot="{ Component }">
|
||||||
<template v-if="Component">
|
<template v-if="Component">
|
||||||
|
<Transition name="page" mode="out-in" appear>
|
||||||
|
<div :key="route.fullPath">
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<component :is="Component"></component>
|
<template #default>
|
||||||
|
<component :is="Component" />
|
||||||
|
</template>
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<div class="flex w-full h-full justify-center items-center">
|
<div class="flex w-full h-full justify-center items-center">
|
||||||
<SpinnerComponent :width="'16'" :height="'16'"></SpinnerComponent>
|
<SpinnerComponent :width="'16'" :height="'16'" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
|
<ToasterComponent :targetNetwork="targetNetwork" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
After Width: | Height: | Size: 47 KiB |
@@ -1,34 +0,0 @@
|
|||||||
<svg width="1440" height="1024" viewBox="0 0 1440 1024" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<g clip-path="url(#clip0_1163_1577)">
|
|
||||||
<rect width="1440" height="1024" transform="translate(1440 1024) rotate(-180)" fill="#111827"/>
|
|
||||||
<rect width="1440" height="1024" transform="translate(1440 1024) rotate(-180)" fill="url(#pattern0)" style="mix-blend-mode:overlay"/>
|
|
||||||
<g filter="url(#filter0_f_1163_1577)">
|
|
||||||
<path d="M307.642 94.7582C329.492 66.5307 387.168 22.494 460.177 -4.02267L471.036 -18.6085C520.484 -21.466 630.246 -28.7616 673.711 -35.0842L744.468 -45.3768C769.738 -49.0528 811.434 -55.118 1077.82 52.5739C1130.28 96.0046 1111.85 79.4162 1169.75 157.222C1227.64 235.027 1185.33 208.907 1190.2 304.059C1195.08 399.211 1178.96 376.025 1134.39 458.619C1089.82 541.213 1114.55 496.67 1027.12 544.553C939.684 592.437 946.753 578.403 834.129 598.157C721.504 617.912 727.718 597.74 655.013 598.681C582.309 599.623 602.473 587.055 541.549 580.021C480.625 572.987 475.518 564.095 412.883 544.785C350.249 525.475 367.241 522.522 283.773 473.967C200.305 425.413 275.945 417.782 226.421 367.662C176.897 317.542 217.738 305.339 208.989 242.544C200.24 179.748 227.97 198.355 242.001 174.155C256.033 149.955 285.793 122.986 307.642 94.7582Z" fill="#1E40AF"/>
|
|
||||||
<path d="M190.775 -149.087C224.733 -176.041 310.524 -217.148 415.43 -240.372L432.377 -254.317C501.523 -255.158 655.128 -257.966 716.388 -262.462L816.114 -269.782C851.73 -272.396 910.497 -276.709 1267.45 -159.904C1334.94 -114.994 1311.4 -132.083 1382.11 -53.069C1452.82 25.9449 1397.26 -1.49464 1392.03 92.5012C1386.8 186.497 1367.3 162.996 1294.87 242.637C1222.44 322.279 1262.47 279.355 1134.8 323.066C1007.13 366.777 1018.73 353.225 859.573 368.2C700.413 383.175 711.603 363.537 610.349 361.56C509.095 359.584 538.732 348.001 454.872 338.632C371.012 329.264 365.031 320.295 280.342 298.756C195.653 277.218 219.662 274.985 109.686 223.786C-0.289922 172.586 105.892 168.085 43.3306 116.698C-19.2306 65.311 39.1219 54.9127 34.8812 -7.34079C30.6406 -69.5943 66.8651 -50.1438 89.4394 -73.4396C112.014 -96.7355 156.816 -122.133 190.775 -149.087Z" fill="#312E81"/>
|
|
||||||
</g>
|
|
||||||
<g style="mix-blend-mode:color-burn" opacity="0.35" filter="url(#filter1_f_1163_1577)">
|
|
||||||
<path d="M1331.87 698.139C1355.95 716.641 1390.22 762.434 1405.5 817.478L1418 826.729C1413.83 862.5 1405.5 941.998 1405.5 973.822L1405.5 1025.63C1405.5 1044.13 1405.5 1074.66 1264.73 1255.06C1215.65 1288.36 1234.17 1276.8 1151.29 1310.56C1068.41 1344.33 1099.43 1316.58 1007.28 1310.56C915.14 1304.55 939.681 1295.3 866.521 1255.06C793.361 1214.81 832.719 1237.02 798.917 1169.48C765.115 1101.95 777.617 1108.43 774.376 1025.63C771.135 942.831 789.656 949.307 798.917 897.038C808.178 844.769 817.439 860.496 832.719 817.478C847.999 774.461 857.26 771.685 884.579 728.668C911.899 685.65 912.362 698.139 970.704 643.095C1029.05 588.051 1025.81 643.095 1080.91 612.566C1136.01 582.038 1142.03 612.566 1203.61 612.566C1265.2 612.566 1243.43 630.606 1264.73 643.095C1286.03 655.584 1307.8 679.637 1331.87 698.139Z" fill="#93C5FD"/>
|
|
||||||
<path d="M89.9274 452.392C84.5615 433.093 173.645 405.29 218.857 393.802C261.551 378.49 356.883 348.502 396.666 351.042C446.395 354.217 484.084 367.188 567.285 375.581C650.486 383.974 701.343 406.986 866.253 451.998C1031.16 497.01 941.631 477.94 1020.73 563.955C1099.82 649.97 1062.64 612.744 1020.73 677.843C978.817 742.941 990.104 746.666 966.604 746.666C947.104 746.666 886.901 761.818 866.253 764.843C845.604 767.867 889.105 784.219 857.604 796.843C812.105 808.719 821.104 797.843 798.104 807.343C787.465 811.737 755.766 774.203 770.045 828.393C784.325 882.583 746.839 894.934 596.079 928.873C445.319 962.811 554.99 880.704 486.361 828.356C417.732 776.008 440.652 785.204 377.552 746.666C314.453 708.128 334.131 694.563 253.132 714.864C172.132 735.165 204.248 721.441 111.698 701.123C19.148 680.804 88.2222 669.852 79.4716 629.472C70.7211 589.091 109.341 574.169 106.698 522.953C104.055 471.737 96.6348 476.516 89.9274 452.392Z" fill="#34D399"/>
|
|
||||||
<path d="M109.89 387.022C111.529 360.564 260.184 360.352 334.307 363.553L364.295 362.69C421.384 353.376 545.59 339.062 585.703 356.315C635.845 377.881 629.175 372.07 693.573 419.737C757.972 467.403 769.441 414.051 852.407 411.662L1060.32 405.675C1162.78 402.725 1166.75 419.118 1258.98 512.502C1351.2 605.886 1181.32 577.764 1065.37 581.103C949.42 584.441 1032.39 582.052 947.759 631.008C863.133 679.964 885.26 649.315 684.132 665.11C483.003 680.905 490.959 627.154 420.639 599.666C350.319 572.179 389.889 574.041 340.308 571.967C290.728 569.893 243.787 555.238 153.96 527.812C64.1335 500.386 107.841 420.095 109.89 387.022Z" fill="#FB923C"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="0.177778" height="0.25">
|
|
||||||
<use xlink:href="#image0_1163_1577" transform="scale(0.000173611 0.000244141)"/>
|
|
||||||
</pattern>
|
|
||||||
<filter id="filter0_f_1163_1577" x="-275.793" y="-560.664" width="1983.5" height="1457.53" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="145" result="effect1_foregroundBlur_1163_1577"/>
|
|
||||||
</filter>
|
|
||||||
<filter id="filter1_f_1163_1577" x="-227.136" y="58.2273" width="1935.14" height="1558.77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
|
||||||
<feGaussianBlur stdDeviation="145" result="effect1_foregroundBlur_1163_1577"/>
|
|
||||||
</filter>
|
|
||||||
<clipPath id="clip0_1163_1577">
|
|
||||||
<rect width="1440" height="1024" fill="white" transform="translate(1440 1024) rotate(-180)"/>
|
|
||||||
</clipPath>
|
|
||||||
<image id="image0_1163_1577" width="1024" height="1024" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAYAAAB/HSuDAAAAAXNSR0IArs4c6QAAIABJREFUeF7svXe0JdmV5rX3iWuyKqsrU42nq2kYPAKEKVQvzn0lKNxA410zeA8Dg/dm8MzgvWfwHmEH03iSqcw48UqUAAmEpzElPCNltupJeU2czbq/F1/pvEuWNLMWa7Uh3h8V9fLdG+aYfU7sz2wvpbxvZhYR/8j56O5/1vz7Hzr//tvPv/9n52NKaXM+9n3/752PwzD8vPlz+/PRzP6J839yzm/Mf//Tz8fdbvd3nI/jOP4l8/n+2Pnzf+v8+b9r/vtfN//9P5z//lvMx396vs7vPf/995iP//X877/p/LnD+ThN019+PnZd9858/n/gfPzwww8fnY+Hw+EvmD//h8zHX3f+nM+/cxiG4en5mFJazdf7gfl6f9N8nf/8fLy+vv7wfCyl/Nbzef6T9jyllN9g/vf/bn5O7jci/t35fP/N/Pvb8+fenM+X53//7Hw+2sPdea6I+Mfm5/zq+Vhr/Rvmz31nPtJ/Zsbfc86/zXzef3T+/Y+e7+fvPh/7vv8F89+5Ts6Zfn/y5MmD83G73dIPEfFvzvfxm83nf23+nf6IiJ8z//5ftM/l7t88/75er//q8/F4PP7i+e/X5+M0Tb/X/Dxfma//mfNxGIa/Yj7fHzdf75+cj7/ffPy/589znohIPPRXv8p9ffzxx7/m/P0/fr7OLzkfU0q0b9d1/+X5eHV19eX5er/V/Pe/dz7/P34+1lr/zPn8P3/++58w//78fNztdn/K+VhK4fPuzniqtTK/drvdfzCfj0MphXFjZj88H/+h+fhyPv7L83PRD6UU3c+/MJ+f9o+IOv/+b8zX+23n7/9O89//mvPxwYMHt+fjfr//0vx5zZ+P5s/9kfP3Hs7H/2k+Mh5Pp9Nvfj52Xff6+ZhS+gPm7/1a830SP77yla/w/dvb2799/j79bmb0Z86ZfiilcH/u/uvN5/n959+Z/xHB+c2M9kgp/dnnY62V8WtmzLOcM/ddSvn7z8fVasXzHo/HP28+n8bL7zh/njhTSmGe5JwZJ8Mw/D7n4263+6Xn4ziOjEcz03z9Xef7+nPnf+eYUvqn5n8n3uWcf/f5fOr3P2a+HnHP3f/w87Hve+5nHEfik5n9ffPx15j/zjwrpfyJ8/npt5TSj86/q70YJxHx58/XJ87pZxiGv36+7k/M/8a8c3fmlcaPmf2B89//7fmoePYHX5zvF82//+R8P3E+1lr/nfNxt9v9p6/6/G63I/6XUv6g83Gz2Tw5Hw+Hg8Y/60rXdcTBd955539oz1NK+Qfn6/xL83X5Pef8a5+Pzbijffq+Z56UUhiHKaUPzsfj8cg8XK1W//x8/m/N56Hfnj179hvP90F/1lr/lrm9NB9/y4v7Io6bGe1vZn/jfD3G69XVFfG3lPJnzNfRvODDwzD8yPw94knXdcTxWivxIOdMe+lnGAbuy92Zd2ZGXN9sNsTD/X7/X83Xpz0j4u+cP0ecN7PPz98nnkYE4zrn/Iedj+M4/g7zvxP3I+I3Oh9Xq9W/dT5O06T17X+fz6d5RHwYx5H+jwj2Bzln7ufZs2f06/F4ZN3cbDasv+6u9v3Lzr/3fc/9PnnyhHV3u91qv6Dx+Cef/72U8q/Nz8n4jgjase975uOXvvQlxtXpdKJfFUfcnefr+57zllL+t/nv/9d8/KH5/lg3DofDH3E+Pn/+nPN+5jOfYT7qPsdxJO5GxK9+Pu73+3fn+/5f5/P9hXM7MM/Hcfxn5s+zT4oIxqWZMe92u91/dD5q/7Hb7TjfOI7MdzP7xnx94mQphXUs51zmzxGvaq3sh2qtx/Ox6zrFM/ZP+/2e8223W/YRZkb/mBnzLuf8O5+PNzc3jPfT6cT+T/sd9XPf96yXpZS/dv4++yQzY76cvzqf75fNv987lFLo//MW7fyflBLjo9bKeppzZr29/CmlKP4zLkopjBt3Z52LCOKWu7NefPTRR6wLb7311r84n+tz8/Hb83XYZ2mf+Pbbb79or1lKIe6YGetVzpnr6qeUojjC85dStH8lbpvZvz9/7/dsvzcMA/dlZszz3W5H/NF97Pd79o3uzjpsZrov7ZPZZ7g78S4i2HfVWtkXpJRYz8yM/Uutlf7tuo52mqaJcZ5SYp0ys19/PrLPNTPWRzNjvXN34lrf94zbUgrre0Ro/DLvSyn/6vw91nkzo91TSsS/aZrYL2j9PB6PxA1353pd1zFf3P1vns/PfjUi/o/zcb1eE2enaaIf+r7netovppSITxHB53LO6pf/eL4f4qOZcf+bzebvOR+Px+OfOv87+5W+73nuUor2Z7/K/Hfts7hOzvm3mz+necD+JedM/ByGgfeOiPjX5+dmH2RmxJfT6cR4Wa1W7A/Ol54/x3qr/en8t3M80H5off43d9d+n/kYEXx/t9uxj2v20X/RfF/b+X7Z/+WciZfDMHA/Zsa+092JRzlnrlNKYV9lZsQvM+O5cs6sF/oppTBuzYz+yjnv5u9/fD7WWokv19fXN+331E673Y64PgzDF+b70L6afUZE8J6q902dYxiGP+f8/ykl5lFEEO9yzsR17RNzzn/S/Dv3Z2asYykl3q9qrbST3lsigvliZnoP0fsH770Rwfp9uf8ppfwm8/ken49d12ldZf3Tfej+nz59yvvTarViPPV9/8/N90mcMjPGX86Z+y2l/J/z9fW+/c+efx/H8S+e//3r8/dY93POjG9fEgC2JADuBsqSAFgSAOcAtiQAlgTAeW1YEgB3C/WSAFgSAEsCYEkAsH9eEgBLAqB9ATOzJQFw1yBLAuBnYgJgGAYyFmZ2icSREU4pkQmapgnENqVEZsXdyeibGcj9ec88ZxpAYDebDRmz/X4PEptSIgMfEUIyyFDv93syrkKWc85kpEspMAMa5I3M7nq9BqGdponMS9/3IKLjOILgr9drIcxknD/3uc8pY8PJlKk83+L591ormdjj8UiGa7vdglBN0wQCk1IisxgRZGy6riMj+PLlSzKZ7733HsikMt8ppX94/j4ZJHcnw2ZmIOdmBlK92WzIBL399ttk5oXw1Vr/2/m66hdlkMl8mZkYAGR8hXSrndwdJkatlY2rED13n+b7ETIjJEoZOdox5wyyogxU13VCTsjAmZky0GS0cs5kwoVg9n0PMvLkyRMyg9vtlgx1zhlk4cMPP/xVz8fj8cg4UP+9//77IFnr9ZrxVmvlPlNKtPfV1dUXz8evfvWrIM+3t7cwV8wMJM/dvzZ/j7/vdjsyueM4kmGNiGfzfWjc8uVSihAJENecM88lBD8i/uf5OkJy//vz78MwwGhJKZEp7/ueDGMphf5zdzL6tVYxA5SBn093dxjHEcQwIn7u/Adl9IVcg2DUWkFgmowo93Nusvl6YmKQyTczzR8QMXcHcer7fpjvkwxgzpn5KyTF3UFEIwJkOOfMPFG/HQ4HMYZANM2MzLK7gyznnEHgx3H80+a/k8mvtTLO3F1MHSGIQs64LzMDQTEzkAh3V8aU5621gph/85vf5Pcf/dEfZbwLacg5wxAqpfAc7k4mXkwbZaLNjHHo7jCFIkILuZgXxBN3J7P7+uuvk7H/9re/TUb56uqK+xeyvNvtmG8ax6vViv7KOfMiPQyDGEtkxK+vr2FeNIwO2ifnzPeGYeA5IwKEvWEYgVgImZvb6hNk2d2JLxEB4hgRxDd3Bxk0M8anmXGdWqsYFiBv7k67p5RIjPZ9D4JQSvlX5vOBNKeUhCyDPAqRFLIspMfMaF93J2MdEWI0CfGnPRpkWXFLGXkheMzTjz76iL//8A//sBhczEfF4/Opzr83yDLxabPZsH4dDgeQU3cH6RZiND/DGen8Ded2Gc/HaZr4fbVaiXEhphH9qrgtZFnIWa2VeJ9SEpOF6wuh2Ww2IDyHw0HzBySi73sQ7VKKGC6MRyGgDQIl5AHkKaUEcyMi/tL5OkL6NI5o5wZZpv2a9QIGQ0qJuNnME5gkEcE8MDMhl6yT7g4irvkUEcT/w+HAeGvWR5DTZn0HQck5g6iJGdAgy6xDEQFinXOm/Usp2m8oXhLf3f2t+fP8XUiMu2u88fwpJZCupp1Z992deBsRPz5fj3VkHEf6bb1eg4Qej0cQITEmzAxk7xKBKqXAlBCzKyLE3IHR0/c965mQ75wzSF7DUPx15vvSOGU+l1KEJAuB4n7M7H+cjyRwT6cTyNEXvvAFMRv4s5gmQgRLKSDS7s46GhHsC7Q/MjOQNndnv6PxFRHMu9VqRbtN00Q7mBkIm7szPyKCdc/d2b9FBMwwMSfcHYS6mUdiimmf+LvM32O+uLuYl8TTrutgCpgZyLeZMV4iAqTY3Ynrp9OJ/Y3aYxxHPa8YeKyTOed7TKdxHFlPpmnieVNKINZC8s1MzC3aJ+fMdfVTSmEfambM65wz87aUov0I++VmXguZVvym/91d+zCdGoRXzAp3F3NF84M4HREw3c6nOP9nv9+zz9lutzDAUkogi+7O+JmmCaaJu4NAqr/MjLii/UDzfOxvIuJvOx93ux37hpubG/aJtVb2ZWbG/uPhw4d/5fmofXkphf2vmYkBw3iJCPYP7g6j5nxr81H9LQQUZq32Y+4Oo0fzUfuxlNJ7538/Ho98791334VhKgZz13XM+6urK9bb5vkYzznn/2X+vN5/aFch4kKc3Z37qbWyr7u+vmY/o3l9PB7FoNU6TJzIOXMchgEG1W634zlLKcSnhoFBf6k/zl+Zv8/zNffNeqL9mPZHEaF4yT6tGY9iwBD3bm5umHe1Vvo9IlgXdrvdK5HllBJxZpomvv/GG2+wP7i9vRXTFgZHrVWMG/YT2n/1fa+4wyOUUphvm82G/bjek774xS9259/feustMLine truncated
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path id="chevronDown" fill-rule="evenodd" clip-rule="evenodd" d="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 508 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="white"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 491 B |
@@ -1,3 +0,0 @@
|
|||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="black"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 484 B |
@@ -1,3 +1,3 @@
|
|||||||
<svg width="16" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3536 7.35355C13.1583 7.54882 12.8417 7.54882 12.6464 7.35355L7 1.70711L1.35355 7.35355C1.15829 7.54881 0.841709 7.54881 0.646446 7.35355C0.451184 7.15829 0.451184 6.84171 0.646446 6.64645L6.64645 0.646446C6.84171 0.451184 7.15829 0.451184 7.35355 0.646446L13.3536 6.64645C13.5488 6.84171 13.5488 7.15829 13.3536 7.35355Z" fill="#111827"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3536 7.35355C13.1583 7.54882 12.8417 7.54882 12.6464 7.35355L7 1.70711L1.35355 7.35355C1.15829 7.54881 0.841709 7.54881 0.646446 7.35355C0.451184 7.15829 0.451184 6.84171 0.646446 6.64645L6.64645 0.646446C6.84171 0.451184 7.15829 0.451184 7.35355 0.646446L13.3536 6.64645C13.5488 6.84171 13.5488 7.15829 13.3536 7.35355Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 493 B After Width: | Height: | Size: 498 B |
@@ -1,3 +1,7 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.00016 0.333496C3.31816 0.333496 0.333496 3.32283 0.333496 7.0115C0.333496 9.9615 2.2435 12.4648 4.89283 13.3475C5.22616 13.4088 5.3475 13.2028 5.3475 13.0255C5.3475 12.8675 5.34216 12.4468 5.33883 11.8902C3.48416 12.2935 3.09283 10.9948 3.09283 10.9948C2.79016 10.2228 2.35283 10.0175 2.35283 10.0175C1.7475 9.60416 2.39883 9.61216 2.39883 9.61216C3.0675 9.65883 3.4195 10.3002 3.4195 10.3002C4.01416 11.3202 4.98016 11.0255 5.3595 10.8548C5.42083 10.4235 5.59283 10.1295 5.7835 9.96283C4.3035 9.79416 2.74683 9.22083 2.74683 6.66216C2.74683 5.9335 3.00683 5.33683 3.43283 4.87016C3.36416 4.7015 3.1355 4.02216 3.49816 3.1035C3.49816 3.1035 4.05816 2.9235 5.3315 3.7875C5.87534 3.63917 6.43645 3.56362 7.00016 3.56283C7.56683 3.5655 8.13683 3.6395 8.6695 3.7875C9.94216 2.9235 10.5008 3.10283 10.5008 3.10283C10.8648 4.02216 10.6355 4.7015 10.5675 4.87016C10.9942 5.33683 11.2528 5.9335 11.2528 6.66216C11.2528 9.2275 9.6935 9.79216 8.20883 9.9575C8.44816 10.1635 8.66083 10.5708 8.66083 11.1942C8.66083 12.0862 8.65283 12.8068 8.65283 13.0255C8.65283 13.2042 8.77283 13.4122 9.1115 13.3468C10.439 12.9016 11.5931 12.0504 12.4105 10.9135C13.2279 9.77669 13.6674 8.41171 13.6668 7.0115C13.6668 3.32283 10.6815 0.333496 7.00016 0.333496Z" fill="#1F2937"/>
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1.41819,0,0,1.41819,0.0724699,0.290651)">
|
||||||
|
<path d="M7,0.333C3.318,0.333 0.333,3.323 0.333,7.012C0.333,9.961 2.244,12.465 4.893,13.348C5.226,13.409 5.348,13.203 5.348,13.025C5.348,12.868 5.342,12.447 5.339,11.89C3.484,12.294 3.093,10.995 3.093,10.995C2.79,10.223 2.353,10.018 2.353,10.018C1.748,9.604 2.399,9.612 2.399,9.612C3.068,9.659 3.42,10.3 3.42,10.3C4.014,11.32 4.98,11.025 5.36,10.855C5.421,10.424 5.593,10.13 5.784,9.963C4.304,9.794 2.747,9.221 2.747,6.662C2.747,5.934 3.007,5.337 3.433,4.87C3.364,4.702 3.136,4.022 3.498,3.104C3.498,3.104 4.058,2.924 5.332,3.788C5.875,3.639 6.436,3.564 7,3.563C7.567,3.566 8.137,3.64 8.67,3.788C9.942,2.924 10.501,3.103 10.501,3.103C10.865,4.022 10.636,4.702 10.568,4.87C10.994,5.337 11.253,5.934 11.253,6.662C11.253,9.227 9.694,9.792 8.209,9.958C8.448,10.164 8.661,10.571 8.661,11.194C8.661,12.086 8.653,12.807 8.653,13.025C8.653,13.204 8.773,13.412 9.112,13.347C10.439,12.902 11.593,12.05 12.411,10.914C13.228,9.777 13.667,8.412 13.667,7.012C13.667,3.323 10.682,0.333 7,0.333Z" style="fill:rgb(31,41,55);"/>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(0.426716,0,0,0.426716,-0.646464,-0.646464)">
|
||||||
|
<path d="M41,4L9,4C6.24,4 4,6.24 4,9L4,41C4,43.76 6.24,46 9,46L41,46C43.76,46 46,43.76 46,41L46,9C46,6.24 43.76,4 41,4ZM17,20L17,39L11,39L11,20L17,20ZM11,14.47C11,13.07 12.2,12 14,12C15.8,12 16.93,13.07 17,14.47C17,15.87 15.88,17 14,17C12.2,17 11,15.87 11,14.47ZM39,39L33,39L33,29C33,27 32,25 29.5,24.96L29.42,24.96C27,24.96 26,27.02 26,29L26,39L20,39L20,20L26,20L26,22.56C26,22.56 27.93,20 31.81,20C35.78,20 39,22.73 39,28.26L39,39Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1001 B |
@@ -6,11 +6,9 @@
|
|||||||
#app {
|
#app {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-image: url( './bg.svg' );
|
background: radial-gradient(ellipse at 50% -50%, rgba(49, 46, 129, 1) 60%, rgba(24, 30, 42, 1) 80%);
|
||||||
background-size: cover;
|
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,3 +24,13 @@ a,
|
|||||||
background-color: hsla(160, 100%, 37%, 0.2);
|
background-color: hsla(160, 100%, 37%, 0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
@apply flex w-full md:max-w-lg flex-col justify-center items-center px-4 sm:px-8 py-4 sm:py-6 gap-4 rounded-lg border border-gray-500 backdrop-blur-md drop-shadow-lg shadow-lg mt-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
appearance: textfield;
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
width="375"
|
||||||
|
height="416"
|
||||||
|
viewBox="0 0 375 416"
|
||||||
|
sodipodi:docname="rootstock-testnet.svg"
|
||||||
|
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="1.7788462"
|
||||||
|
inkscape:cx="187.48108"
|
||||||
|
inkscape:cy="215.3081"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="993"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="g1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
id="g1"
|
||||||
|
style="fill:#b3b3b3">
|
||||||
|
<image
|
||||||
|
width="375"
|
||||||
|
height="416"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAXcAAAGgCAYAAABR+ydpAAAAAXNSR0IArs4c6QAAIABJREFUeF7s nXmYVMXVxt9ze2YAxRVZAtO3B8E1atxiTGLc97jvRo1rRJnuBlRi1ORzsmhUDEt3D4rRaNS4JUZj NMYtUaNGE3fjhgvTtwdcEEUFhJnue76negYEBKaXu9W9p54nf+Sx6pz3/KrmcPveqlMEaUJAYwLM IExtSaDR3grMm4AxCkRxML4GA4PBWA/AQACNy4VZAmERGPMBzAPT+wB3AjwLoJkAXsPH1kxqQ1Fj NCI94gQo4vFL+JoR4MnNA9BkfAfMuwH0bQA7AljfhTAWA/wiYPwbjCdQLD1K53R+7IIfMSkEXCEg yd0VrGLUSQI8JfE1NNqHwTYOAfHuAPo7ab9CWzbA/wFwLwzjz9Saf73CcdJNCPhCQJK7L9jFaV8E uH3wQNj9jwbRSWDsBsDoa4zH//0VADejSDfRhPx7HvsWd0KgTwKS3PtEJB28JMDZls0BToFYJfV1 vPRdo68iwPcAsSylOh6t0YYMEwKOE5Dk7jhSMVgLAc607AzDvhCMgwDouS4Jz8HGZfjY+jO1wa6F g4wRAk4R0POPyKnoxY7vBDiX2B42LgHx/r6LcU7AKyD+GSULf3HOpFgSAtURkOReHS/p7RABvqp5 BIrGZQBO0PZJvS8WapcNYzyNs57rq6v8dyHgNAFJ7k4TFXtrJMBtaMCGiXEgbuvdfx52YjaYZqBE F9KEDrWvXpoQ8ISAJHdPMIsTRYCzzVuDjOvB2CGCROaAcRalrb9GMHYJ2QcCktx9gB41l9wGA4MS 5wD8KwD9ohb/ivHydTAWj6fWuQuizUGid5uAJHe3CUfcPs8YvhG6Gm4GsF/EUSwf/huAfQylOtVe eWlCwBUCktxdwSpGe17DxFVpgDsBMoXIVwgsAvMZlC7cKmyEgBsEJLm7QVVsgrPm0QBuALCW4Fgt AQbRr/FR/meyL15WidMEJLk7TVTsgTOJc0E8KbRbHB2fY74F89Y5ldpe63LctBiMLAFJ7pGdeucD L5ffzZlq7/qPnbceeosPoKl4BI2Zsyj0kUqAnhCQ5O4J5vA76Uns8QxAyfBH61aE9BgWL/4+Tfxg oVsexG50CEhyj85cuxopZ8wpIIx31UkkjPOj6OYD6ZzOLyIRrgTpGgFJ7q6hjY5hzprqtOnF0YnY 9UjvQ9Pgw2nMc92uexIHoSUgyT20U+tNYJwzzwRjhjfeouSFrqdU/rQoRSyxOktAkruzPCNljTPm 3iDcD6AhUoF7FSzhQkpav/bKnfgJFwFJ7uGaT8+i4UxcXUT9XwAbeOY0eo5sMA6TejTRm3gnIpbk 7gTFiNkoX1LdSP8G6BsRC92PcOeDeUdKF97xw7n41JeAJHd958435Zw1rwJwlm8Couf4Wcwb+F05 5BS9ia8nYknu9dCL4FjOxg8BSG4Y8n7uL6OUdYH3bsWjrgQkues6cz7o7q3w+CqAIT64j7rLEtjY hdIdT0cdhMRfGQFJ7pVxkl7lKo/mTQBOFBi+EXgd8wZuK69nfOOvlWNJ7lpNl39iOWfuBcbD/ikQ zz0E+KeUKlwiNIRAXwQkufdFSP47yveeDjJfArCl4PCdwCIYxc2pdU7BdyUiINAEJLkHenqCIY5z iVYw54KhRlSA8AdKWvJ6TJbCGglIcpcFskYC3D54IOwBao+1fEQNzlphGMb21NrxYnAkiZKgEZDk HrQZCZgezpo/ASBH4AM2LwDupZR1cPBkiaKgEJDkHpSZCKAOnjR0bfTr1wHCRgGUF3VJ8vQe9RXQ R/yS3GWBrJYAZ8w0CNMEUVAJ0G2Uyh8fVHWiy18Cktz95R9Y79wGA4PMmQBGBVakCCvCKG4sO2dk IayKgCR3WRerJMCZxPdBfK/gCToBuoRS+Z8GXaXo856AJHfvmWvhkTPm3SAcqoXYaIt8D/Msk9pQ jDYGiX5lApLcZU18hQBfNWoIit2dABoFjwYEiL5PyfzfNFAqEj0kIMndQ9i6uJJDS7rM1FKdfAul Cifoplr0uktAkru7fLW0zlnzMQC7aik+iqIJn2NtYwid2rE4iuFLzKsmIMldVsYKBDgzbDCo6T0A MUGjEQGmgyidv08jxSLVZQKS3F0GrJt5zpkngXGjbrpFL02nVL5VOAiBpQQkuctaWOnJ3bwRhJME i3YE3qaUtYl2qkWwawQkubuGVk/DnDVVKdlmPdVHXLVRNOVAU8TXwHLhS3KXtbCMAE9paUGDPUuQ aEqA+QeULtyqqXqR7TABSe4OA9XZHGfjxwJ0m84xRFz7NEpZ4yPOQMLvJSDJXZbCl0/uWfNyAD8W JNoS+BelLNnCqu30OStckruzPLW2xtn43wA6QOsgoi1+PqWsDaKNQKJfSkCSu6yF5Z/cOwAkBInG BIo0nCbk1TkFaREnIMk94gtgaficGd0P1LUIgCFINCZAvBslC49rHIFId4iAJHeHQOpuptDWPLp5 kPGW7nFEXj/hh5S0boo8BwEASe6yCMoEONO8G8h4VHBoT+ACSlmXaR+FBFA3AUnudSMMhwHOmceA cXs4oolwFIyplLYmRJiAhN5LQJK7LIUygYWT42et1UhXCQ69CXQV+cZ+Ewon6x2FqHeCgCR3JyiG wAZnEhNBfEUIQol4CHwXpQpHRByChA/IO3dZBT0EOGteDKBNeGhP4AFKWftrH4UEUDcBeXKvG2E4 DHAu/kswyUXLmk/n3IWlfw75yew9NQ9D5DtAQJK7AxDDYIJziUvAfGEYYolyDK990P3Y13/13u5R ZiCx9xCQ5C4roUyg7cD12y4+YF31akaa3gTktYze8+eYeknujqHU2xBnzZ8A+LXeUYh6APdQyjpU SAgBSe6yBsoEOJNIgjgrOPQm0FXEH/pNsE7UOwpR7wQBSe5OUAyBjQWTm3+wdqPxhxCEEvEQOEep QiriECR8eecua2ApAc6Z+4DxoBDRnADhYkpav9A8CpHvAAF5cncAYhhMvPcL8+vDNsD/whBLlGNY tMT+0drndV4bZQYSew8BSe6yEnreubcPHgh7wOeCQ3MChH0paT2keRQi3wECktwdgBgWE5w13wcw NCzxRDKOGEbRWOvdSMYuQa9AQJK7LIhlBDibeBTg3QSJtgQWY6g1kI5BSdsIRLhjBCS5O4ZSf0Oc iedA1Kp/JJGN4HlKWTtENnoJXJ7cZQ2smgBnEqeB+DrhoysBvo5ShTN0VS+6nSUgT+7O8tTaGmeb twaMl7UOIsriCWMoaV0TZQQS+5cEJLnLavjynXsbDAwy5wFYX7BoSICwFSWtVzVULpJdICDJ3QWo OpvkrPkXAIfoHENEtX+IpDWMCBzR+CXslQhIcpclsQIBzprq6HpGsOhGgG6lVP4HuqkWve4RkOTu HlstLXMmPgpEb2spPtqiT6KUdXO0EUj0yxOQ5C7r4SsEOGu+AmArQaMNgSK67aF0TufH2igWoa4T 0D65s/oIuOGI4YAxEkAzDBoK5g0BWhfAWgCaeihyEYzFIPochPlg/hDAHCCWBzfMovTbS1ynrYkD zprqLlW5uEOT+QLwIKWs/fSR665SzozuByqOBEoJAMNBNASM9cG8Dgj9AWroVdAFYBHAn4HoY4Df h43ZgD0LH8+eQ22w3VXqrnWtkjvP2KERxQ+3hU07g7A9GNsA2Lw3iddDSp3o6wDoFRC/AOZn0NT4 NI1599N6jOo6lqeO3Ayx0hu66o+cbsIplLR+H7m41SPbjI3XQ1f3ziD6Fpi2A3hrAC0AYnXyWATg DRBeBuN5GPw0Goa8SGOe667TrmfDA5/cOTdiU7DxfbCxL4i/B2Btj+iohP8CgIfAfD+GFZ6K0rFu zsb/DdDOHrEWN7UTWIBFA4bT+W9Gougb34EY3o9/B0QHANgHwHYOJPJK6S8E079A9oMg+z5Kzp5Z 6UA/+gUyufck9Jj68n8MgC38APNVnzQXxH+Gbd+KVOfjYd9yxtnE6QBL6dhgLL7Vq2BcS2nrR0GX WY8+ZhCyzbvCMI4H0xEAD67HnoNjXwdwB6h0SxATfWCSO09uHoAmQyXzM8H4joMT4IapWQBfh1LD tTR+1gduOPDbJs8Yvha6GjoBbOC3FvG/BgJEO1Ay/3wYGfHUkUMRK54B0OkA1De14DbCU7D5tyjy 7XRO5xdBEOp7cuerRg1BqSsFm84CYaMgQKlCwxIQbkWJf0PjCqG76IIz5mUgnF8FD+nqLYHHKWWF roonT4tvhRidC8bxAPp5i7ROb4yPYPDViDVl6ex31KYN35pvyZ3bW4bBtn9SflIHBvhGwBnHDPBd ALdRqlNtIwxF42x8OECqNrhef2ChoF9BEISDKWndW0FPLbr01DaiNoAOD8FFQurp/RoYxmXU2qHu SfC8eZ7cyzf+cP/zwTTBw4+jXoFVW6dugd1wEY171/LKqZt+OGteBeAsN32I7ZoIvICktUMYvv1w +/A47IZLAajvbEZNNII7aCGIp4AWX06tcxd4KdOz5F7+KJJLHA/wpPLe03C3RQBdhoE0iU7tWKxz qJwZ0QyKvQWo/cHSgkOAD6VU4Z7g6KleCV/f0h8LeCLA6he8OpMS5jYHoIlI5m/16h9kT5I7Tzc3 RhHXgLBXmGfvq7HRTLine truncated
|
||||||
|
id="image1"
|
||||||
|
style="fill:#b3b3b3;fill-opacity:1" />
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;fill-opacity:1;stroke-width:0.562162"
|
||||||
|
d="m 180.45405,414.20796 c -3.24648,-0.30209 -5.98702,-0.78137 -6.09008,-1.06509 -0.10307,-0.28371 -1.05172,-0.51584 -2.10811,-0.51584 -1.0564,0 -1.92072,-0.25298 -1.92072,-0.56217 0,-0.30918 -0.49102,-0.56216 -1.09115,-0.56216 -0.60013,0 -1.24749,-0.25297 -1.43858,-0.56216 -0.19109,-0.30919 -0.73698,-0.56216 -1.21309,-0.56216 -0.86347,0 -4.71858,-1.62979 -5.25178,-2.22026 -0.15459,-0.17119 -1.04,-0.69032 -1.96757,-1.15361 -0.92756,-0.46329 -2.02077,-1.15856 -2.42934,-1.54505 -0.40857,-0.38649 -0.93006,-0.7027 -1.15887,-0.7027 -0.2288,0 -0.9064,-0.45036 -1.50579,-1.0008 -0.59938,-0.55043 -2.10167,-1.75206 -3.33843,-2.67027 -2.81359,-2.0889 -2.91728,-2.19207 -5.63322,-5.60461 -1.23038,-1.54594 -2.38259,-2.9373 -2.56047,-3.09189 -0.17788,-0.1546 -0.80161,-1.10324 -1.38607,-2.10811 -0.58446,-1.00486 -1.26108,-1.82703 -1.50361,-1.82703 -0.24252,0 -0.44095,-0.37946 -0.44095,-0.84324 0,-0.46378 -0.25298,-0.84324 -0.56217,-0.84324 -0.30919,0 -0.56216,-0.32826 -0.56216,-0.72946 0,-0.40121 -0.8854,-2.45841 -1.96757,-4.57156 -1.08216,-2.11315 -1.96756,-4.31462 -1.96756,-4.89216 0,-0.57753 -0.22814,-1.05007 -0.50697,-1.05007 -0.27883,0 -0.68647,-1.58108 -0.90586,-3.51351 -0.21939,-1.93243 -0.60971,-3.77556 -0.86737,-4.09584 -0.25766,-0.32028 -0.58471,-4.11487 -0.72676,-8.43243 -0.27925,-8.48778 -0.753,-12.60228 -1.54792,-13.44362 -0.27752,-0.29373 -0.50458,-1.06753 -0.50458,-1.71956 0,-0.65202 -0.88541,-2.65586 -1.96757,-4.45297 -1.08216,-1.79711 -1.96757,-3.39307 -1.96757,-3.54658 0,-0.58109 -4.32266,-4.51095 -6.85994,-6.23658 -1.45405,-0.98891 -3.41986,-1.95325 -4.36846,-2.14296 -0.9486,-0.18972 -1.86087,-0.56525 -2.02729,-0.8345 -0.1664,-0.26925 -1.41107,-0.48955 -2.76593,-0.48955 -1.35486,0 -2.61972,-0.25298 -2.81081,-0.56217 -0.19109,-0.30919 -1.32947,-0.56216 -2.52973,-0.56216 -1.20026,0 -2.33864,0.25297 -2.52973,0.56216 -0.19109,0.30919 -1.597372,0.56217 -3.12507,0.56217 -1.527699,0 -2.777632,0.25297 -2.777632,0.56216 0,0.30919 -0.56919,0.57629 -1.264865,0.59357 -1.578366,0.0392 -5.727263,1.69778 -7.032115,2.81119 -0.933847,0.79684 -2.717503,1.69734 -6.740858,3.40317 -1.004865,0.42606 -1.827027,0.96585 -1.827027,1.19957 0,0.23371 -0.632433,0.42493 -1.405406,0.42493 -0.772973,0 -1.405405,0.19509 -1.405405,0.43354 0,0.48369 -5.660945,1.79529 -9.556757,2.21424 -1.391351,0.14962 -2.770054,0.50049 -3.063784,0.7797 -0.293729,0.27921 -3.089081,0.50766 -6.211892,0.50766 -3.12281,0 -5.918162,-0.22845 -6.211891,-0.50766 -0.29373,-0.27921 -1.672433,-0.64473 -3.063784,-0.81225 -4.44802,-0.53556 -9.556757,-1.69027 -9.556757,-2.16009 0,-0.25033 -0.491016,-0.45514 -1.091147,-0.45514 -0.600131,0 -1.247493,-0.25297 -1.438583,-0.56216 -0.191089,-0.30919 -0.838451,-0.56216 -1.438582,-0.56216 -0.600131,0 -1.091147,-0.23288 -1.091147,-0.5175 0,-0.28462 -0.724734,-0.67667 -1.610519,-0.87122 -0.885786,-0.19456 -2.140073,-0.84709 -2.787305,-1.45007 -0.647233,-0.603 -1.440337,-1.09635 -1.762455,-1.09635 -0.322117,0 -0.585667,-0.21237 -0.585667,-0.47192 0,-0.25956 -0.56919,-0.73745 -1.264865,-1.06198 C 22.08292,327.58353 11.243243,316.97 11.243243,315.39421 c 0,-0.2767 -0.316216,-0.84507 -0.702702,-1.26306 -1.3845193,-1.49734 -4.0555867,-6.51003 -5.6289285,-10.56358 -0.3000203,-0.77298 -0.76895,-1.84468 -1.0420656,-2.38156 -0.2731156,-0.53689 -0.4965739,-1.46446 -0.4965739,-2.06127 0,-0.5968 -0.2301341,-1.16942 -0.5114091,-1.27249 -0.2812749,-0.10306 -0.7889857,-2.97009 -1.1282463,-6.37117 -0.3392605,-3.40108 -0.86804536,-6.26811 -1.17507742,-6.37117 -0.74875885,-0.25134 -0.71696408,-3.88223 0.0361941,-4.13328 0.3269389,-0.10898 0.86215012,-2.97601 1.18935832,-6.37117 0.3272082,-3.39517 0.8186317,-6.25735 1.0920522,-6.36041 0.2734205,-0.10307 0.4971282,-0.67569 0.4971282,-1.2725 0,-0.5968 0.2317062,-1.52437 0.5149026,-2.06126 0.2831965,-0.53689 0.8817713,-1.86156 1.3301664,-2.94372 2.0206548,-4.87667 2.3903001,-5.62162 2.7894582,-5.62162 0.2337128,0 0.4249322,-0.24806 0.4249322,-0.55125 0,-2.18603 13.1652956,-17.43794 15.0522526,-17.43794 0.327027,0 0.678918,-0.18973 0.781982,-0.42162 0.269511,-0.6064 4.833396,-3.51352 5.515875,-3.51352 0.315818,0 0.574215,-0.25297 0.574215,-0.56216 0,-0.30919 0.254213,-0.56216 0.564918,-0.56216 0.310706,0 1.875888,-0.63244 3.478184,-1.40541 1.602295,-0.77297 3.371866,-1.4054 3.932378,-1.4054 0.560513,0 1.019114,-0.25298 1.019114,-0.56217 0,-0.30918 0.611352,-0.56216 1.358559,-0.56216 0.747207,0 1.442883,-0.22315 1.545946,-0.49589 0.179311,-0.47452 11.287869,-1.91815 14.803603,-1.92383 3.312547,-0.005 14.063842,1.39797 14.588109,1.90412 0.293729,0.28358 0.99284,0.5156 1.553574,0.5156 0.560734,0 1.458788,0.23171 1.995676,0.5149 0.536887,0.2832 1.988047,0.94893 3.224804,1.4794 5.015301,2.15116 8.372404,3.9387 9.715713,5.17327 0.420526,0.38649 0.990946,0.7027 1.267603,0.7027 1.55602,0 12.207422,10.87828 13.623322,13.91352 0.32453,0.69567 0.80242,1.26486 1.06198,1.26486 0.25955,0 0.47192,0.37946 0.47192,0.84324 0,0.46379 0.20083,0.84325 0.44629,0.84325 0.50146,0 4.61317,8.42979 4.61317,9.45788 0,0.3Line truncated
|
||||||
|
id="path2" />
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;fill-opacity:1;stroke-width:0.562162"
|
||||||
|
d="m 181.85946,113.07866 c -0.30919,-0.29842 -1.82703,-0.66518 -3.37297,-0.81502 -3.43346,-0.33279 -8.0885,-1.37307 -9.40859,-2.10257 -0.53689,-0.2967 -1.35905,-0.53945 -1.82703,-0.53945 -0.46798,0 -0.85087,-0.25297 -0.85087,-0.56216 0,-0.30919 -0.4427,-0.57021 -0.98378,-0.58004 -0.54108,-0.01 -1.47484,-0.3893 -2.07502,-0.84324 -0.60017,-0.45395 -1.54882,-0.82537 -2.10811,-0.82537 -0.55928,0 -1.01687,-0.25297 -1.01687,-0.56216 0,-0.30919 -0.37946,-0.56216 -0.84325,-0.56216 -0.46378,0 -0.84324,-0.22229 -0.84324,-0.49396 0,-0.27168 -0.75892,-0.80832 -1.68649,-1.19253 -0.92756,-0.38421 -1.68648,-0.9038 -1.68648,-1.15464 0,-0.25084 -0.47595,-0.60714 -1.05767,-0.79177 -1.04027,-0.33017 -10.74774,-9.594144 -10.74774,-10.25672 0,-0.186665 -0.63243,-1.168555 -1.4054,-2.181976 -0.77298,-1.013421 -1.40541,-2.112431 -1.40541,-2.442251 0,-0.329821 -0.25297,-0.59967 -0.56216,-0.59967 -0.30919,0 -0.56216,-0.379459 -0.56216,-0.843243 0,-0.463784 -0.20429,-0.843244 -0.45398,-0.843244 -0.24969,0 -0.91772,-1.075135 -1.48452,-2.389189 -0.56679,-1.314054 -1.50088,-3.385351 -2.07575,-4.602888 -0.57487,-1.217536 -1.04521,-2.672131 -1.04521,-3.232432 0,-0.560302 -0.21168,-1.018734 -0.47039,-1.018734 -0.90563,0 -1.92543,-8.783587 -1.92543,-16.583784 0,-7.800195 1.0198,-16.583783 1.92543,-16.583783 0.25871,0 0.47039,-0.632433 0.47039,-1.405406 0,-0.772973 0.25297,-1.405405 0.56216,-1.405405 0.30919,0 0.56216,-0.332115 0.56216,-0.738033 0,-0.405918 0.63243,-2.049002 1.40541,-3.651298 0.77297,-1.602295 1.4054,-3.167478 1.4054,-3.478183 0,-0.310705 0.25297,-0.564919 0.56216,-0.564919 0.30919,0 0.56217,-0.379459 0.56217,-0.843243 0,-0.463784 0.22228,-0.843243 0.49396,-0.843243 0.27167,0 0.79796,-0.733931 1.16952,-1.630958 0.37156,-0.897027 1.28766,-2.205988 2.03577,-2.908803 0.74811,-0.702816 1.36021,-1.472715 1.36021,-1.710889 0,-0.579165 5.40291,-5.88675 7.87027,-7.73141 1.08216,-0.809052 2.09405,-1.609838 2.24864,-1.779524 0.1546,-0.169687 1.48225,-1.056532 2.95035,-1.9707657 1.4681,-0.9142348 2.66972,-1.8519747 2.67027,-2.0838666 5.5e-4,-0.2318919 0.35996,-0.4216216 0.79869,-0.4216216 0.43873,0 1.64035,-0.4222619 2.67027,-0.9383599 3.41216,-1.7098496 3.8182,-1.8724509 4.67577,-1.8724509 0.46798,0 0.85087,-0.252973 0.85087,-0.5621622 0,-0.3091892 0.70833,-0.5621621 1.57405,-0.5621621 0.86573,0 1.81438,-0.2323009 2.10811,-0.5162242 0.29373,-0.2839232 3.18628,-0.7778909 6.42789,-1.0977061 3.2416,-0.319815 5.98214,-0.84643309 6.09009,-1.17026233 0.25569,-0.76708351 3.63845,-0.76316439 4.40672,0.005105 0.32664,0.32663724 3.22318,0.85503913 6.43676,1.17422643 3.21357,0.3191874 5.92719,0.8108577 6.03025,1.0926007 0.10307,0.281743 0.92523,0.51226 1.82703,0.51226 0.9018,0 1.63964,0.2529729 1.63964,0.5621621 0,0.3091892 0.63243,0.5621622 1.40541,0.5621622 0.77297,0 1.4054,0.252973 1.4054,0.5621622 0,0.3091891 0.4576,0.5621621 1.01688,0.5621621 0.55928,0 1.50793,0.3714138 2.10811,0.8253648 0.60017,0.4539505 1.53393,0.8334099 2.07501,0.8432433 0.54108,0.00983 0.98379,0.2708514 0.98379,0.5800406 0,0.3091892 0.26364,0.5621621 0.58589,0.5621621 0.32223,0 0.89142,0.2538382 1.26486,0.5640848 0.37343,0.3102466 1.99753,1.4462463 3.60909,2.5244413 2.66795,1.784963 11.40502,9.989899 11.40502,10.710396 0,0.166422 0.31622,0.680148 0.7027,1.141612 0.38649,0.461464 0.95568,1.146462 1.26487,1.522219 0.30919,0.375756 0.91884,1.374599 1.35478,2.219651 0.43594,0.845051 1.22876,2.004601 1.76182,2.576779 0.53307,0.572178 1.14514,1.841312 1.36016,2.820299 0.21502,0.978988 0.62383,1.779978 0.90845,1.779978 0.28462,0 0.51749,0.632432 0.51749,1.405405 0,0.772973 0.25297,1.405406 0.56216,1.405406 0.30919,0 0.56217,0.632432 0.56217,1.405405 0,0.772973 0.20804,1.405406 0.46231,1.405406 1.0378,0 2.0087,8.481909 1.99483,17.427027 -0.0164,10.608871 -0.72744,15.174358 -3.08489,19.808587 -0.27312,0.536888 -0.49658,1.35905 -0.49658,1.827027 0,0.467978 -0.24151,0.850872 -0.53669,0.850872 -0.29517,0 -0.70279,0.885405 -0.90581,1.967568 -0.20301,1.082162 -0.59393,1.967567 -0.86871,1.967567 -0.27478,0 -0.4996,0.37946 -0.4996,0.843243 0,0.463784 -0.20612,0.843244 -0.45804,0.843244 -0.25193,0 -0.8828,0.822162 -1.40194,1.827027 -0.51915,1.004865 -1.26167,2.309233 -1.65006,2.898598 -1.03291,1.56737 -10.98752,11.577076 -11.51336,11.577076 -0.24615,0 -1.0774,0.58355 -1.84721,1.29677 -0.76981,0.71322 -2.22182,1.66038 -3.22669,2.1048 -1.00486,0.44441 -1.82702,0.99924 -1.82702,1.23296 0,0.23371 -0.25422,0.42493 -0.56492,0.42493 -0.31071,0 -1.87589,0.63243 -3.47819,1.40541 -1.60229,0.77297 -3.24538,1.4054 -3.65129,1.4054 -0.40592,0 -0.73804,0.22923 -0.73804,0.50941 0,0.28018 -1.3281,0.68796 -2.95135,0.90619 -1.62324,0.21823 -3.20432,0.59542 -3.51351,0.83821 -1.28934,1.01241 -17.09178,2.06939 -17.98919,1.20323 z"
|
||||||
|
id="path3" />
|
||||||
|
<path
|
||||||
|
style="fill:#666666;stroke-width:0.562162"
|
||||||
|
d="m 180.45405,413.99003 c -6.22403,-1.10996 -8.7453,-1.77793 -12.76882,-3.38288 -5.7302,-2.28574 -11.13907,-5.4179 -15.90145,-9.20818 -8.66889,-6.8994 -16.7896,-20.20407 -18.8811,-30.93411 -0.45201,-2.31892 -1.06539,-8.1573 -1.36306,-12.97418 -0.59065,-9.55763 -1.58544,-13.70964 -4.50899,-18.81948 -5.0696,-8.86073 -17.09257,-14.52382 -26.85868,-12.65104 -5.87418,1.12646 -8.888105,2.23304 -15.847628,5.81856 -7.069554,3.6422 -11.402048,5.22317 -15.902668,5.80306 -1.61137,0.20761 -3.540514,0.60403 -4.286986,0.88094 -1.851734,0.68689 -11.887876,0.64911 -14.506578,-0.0546 -1.168964,-0.31413 -4.056797,-0.9368 -6.417407,-1.3837 C 32.426281,335.04275 17.400009,324.52345 10.962666,314.50885 8.4439099,310.59041 4.8041036,303.0256 3.602451,299.21171 2.534914,295.82349 0.92277292,285.99962 0.95001844,283.04864 0.97805015,280.0125 2.4941036,270.75763 3.5407895,267.23305 8.0380406,252.08917 21.318097,237.58338 36.259459,231.4945 c 6.299621,-2.5672 8.137954,-3.03016 15.227297,-3.83477 6.253205,-0.70972 14.008271,-0.11281 20.188918,1.55393 4.229106,1.14047 12.437523,4.91252 16.864864,7.75 6.321339,4.05132 14.666852,13.72424 18.477562,21.4165 4.13659,8.35009 5.11221,12.53661 6.07623,26.07389 0.89327,12.54384 3.18272,18.62922 9.0236,23.98489 5.79912,5.31738 10.11216,6.93402 18.49922,6.93402 8.87757,0 14.58387,-2.09628 24.85704,-9.13154 4.96445,-3.39975 9.43764,-9.73655 11.37208,-16.10986 1.19117,-3.92448 1.01574,-11.0092 -0.37771,-15.25387 -2.2181,-6.75665 -7.65246,-13.04332 -15.38748,-17.80079 -12.4599,-7.66356 -20.02818,-16.08572 -25.30389,-28.15877 -2.71515,-6.21341 -2.91624,-7.21809 -3.66326,-18.30189 -0.85213,-12.64365 -2.59343,-19.23699 -6.44609,-24.40783 -2.99835,-4.02424 -4.9325,-5.81837 -8.24493,-7.64809 -4.64133,-2.56377 -7.1492,-3.14887 -13.53407,-3.15755 -5.307698,-0.007 -6.571225,0.1935 -10.760547,1.70937 -2.610681,0.94466 -5.975795,2.50869 -7.478033,3.47563 -2.651505,1.70668 -5.8401,3.0384 -13.954849,5.82827 -3.598383,1.23713 -4.781785,1.34553 -14.616216,1.33887 -11.608725,-0.008 -12.8086,-0.22283 -21.534948,-3.85793 -6.67657,-2.78123 -10.756926,-5.57156 -16.898695,-11.5561 -4.89564,-4.77031 -11.3374441,-12.93714 -11.3374441,-14.37345 0,-0.22358 -0.732432,-2.02259 -1.6276264,-3.99778 C 4.7852868,151.99444 3.7461949,149.24 3.3713883,147.84864 2.4720363,144.51008 0.98484203,135.15452 0.95857764,132.67027 0.92981349,129.94956 2.6316553,119.43779 3.5979626,116.36756 c 1.2951409,-4.11502 4.6635656,-11.05132 7.1808944,-14.78699 4.55578,-6.760688 14.244166,-15.635706 19.85898,-18.19176 0.927568,-0.42226 2.065946,-1.003634 2.52973,-1.291941 1.771166,-1.101031 8.288847,-3.325512 11.416313,-3.896381 1.795729,-0.327781 3.502902,-0.743021 3.793717,-0.922755 0.290816,-0.179734 4.077984,-0.330058 8.41593,-0.334053 6.737053,-0.0062 8.76607,0.215086 13.91439,1.517544 9.905056,2.505852 17.44306,6.823574 25.328694,14.508122 4.762759,4.641309 8.705499,10.005004 11.008679,14.976184 0.70187,1.5149 1.52426,3.08094 1.82755,3.48009 1.69511,2.23091 3.33159,10.69168 4.38097,22.65005 0.43411,4.94703 1.02483,9.90839 1.31269,11.02525 1.80454,7.0012 7.60249,14.22787 13.90386,17.33003 4.36018,2.1465 10.11128,3.11296 15.5805,2.61825 3.91289,-0.35394 10.64775,-2.55763 12.25111,-4.00866 0.50098,-0.45338 1.23137,-0.82433 1.62308,-0.82433 0.39171,0 1.38368,-0.47407 2.20438,-1.05349 5.41891,-3.82578 18.51411,-7.41793 27.14153,-7.4452 9.55647,-0.0302 16.61857,1.98821 29.5547,8.44702 9.96061,4.97318 14.62803,5.91306 22.75405,4.58201 7.10966,-1.16458 14.41868,-6.49562 18.21646,-13.28668 2.38432,-4.26358 3.00278,-6.98455 3.69575,-16.25987 1.11836,-14.96903 3.94942,-24.32008 10.26112,-33.89263 2.75945,-4.18508 10.26982,-11.755795 14.78892,-14.90775 4.40093,-3.06954 11.75671,-6.451152 16.18236,-7.439386 2.16432,-0.483287 4.73874,-1.128067 5.72094,-1.432843 2.46207,-0.763989 16.13182,-0.689945 19.5713,0.106011 6.45189,1.493074 9.23971,2.416036 13.65604,4.52111 6.72727,3.206596 10.75923,6.172279 16.36873,12.039923 6.67555,6.982745 10.40698,13.263815 13.50647,22.735295 0.92418,2.82411 2.50821,12.76488 2.50821,15.74054 0,2.69735 -1.54232,12.65333 -2.3949,15.45946 -1.18795,3.90997 -4.55461,11.02377 -7.02272,14.83908 -5.12548,7.92316 -17.01415,18.01529 -24.19163,20.53591 -0.95924,0.33687 -2.04776,0.80736 -2.41894,1.04554 -0.37117,0.23817 -2.64487,1.05828 -5.05264,1.82247 -4.0202,1.27594 -5.25237,1.38943 -15.08541,1.38943 -9.83086,0 -11.02824,-0.11023 -14.62303,-1.34612 -8.33237,-2.86469 -11.7202,-4.22348 -13.13119,-5.26665 -2.29613,-1.69757 -5.0914,-3.07224 -9.05448,-4.45283 -4.54508,-1.58334 -14.50344,-1.81431 -18.48236,-0.42866 -7.20223,2.50816 -12.57869,7.28915 -15.77066,14.02399 -2.62313,5.53461 -3.26936,8.82163 -3.99588,20.32464 -0.35137,5.56314 -0.97275,10.39108 -1.62352,12.61425 -2.202,7.52244 -6.96849,16.01503 -12.34435,21.99428 -3.08677,3.43323 -10.2969,9.06014 -15.64689,12.21111 -4.92527,2.90083 -10.29949,8.47654 -13.33386,13.83379 l -2.21373,3.90841 0.003,7.87027 0.003,7.87027 2.11683,3.93514 c 2.90954,5.40876 4.29161,7.1042 8.65494,10.617Line truncated
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
style="fill:#999999;stroke-width:0.562162"
|
||||||
|
d="m 181.56218,112.80934 c -0.1635,-0.17358 -2.0608,-0.59047 -4.21622,-0.92642 -10.04121,-1.56505 -18.71078,-5.95429 -27.29462,-13.818776 -10.02676,-9.186477 -16.28485,-20.965084 -17.45508,-32.852874 -0.99809,-10.139159 0.25779,-23.676788 2.5294,-27.265325 0.39144,-0.618378 1.30906,-2.617602 2.03915,-4.442719 2.83182,-7.079115 10.83219,-16.556533 18.74217,-22.202395 5.42215,-3.8701346 4.74121,-3.4891096 10.49302,-5.8714466 5.32464,-2.2054109 5.92351,-2.3648698 13.49189,-3.592407 6.81719,-1.10570114 8.60429,-1.10611466 15.45946,-0.00358 6.45256,1.037785 10.58969,2.210356 14.56495,4.1280847 1.53527,0.7406425 2.99613,1.3466229 3.24633,1.3466229 0.85617,0 8.60683,5.787337 12.84856,9.593889 3.57184,3.205389 11.21137,13.06757 11.2247,14.490417 0.002,0.203361 0.40222,1.128666 0.88959,2.056234 3.84884,7.325131 5.07332,14.888375 4.55655,28.144355 -0.25433,6.52378 -0.57697,8.735848 -1.84489,12.648649 -0.84516,2.608173 -1.87549,5.141507 -2.2896,5.629638 -0.41413,0.488131 -0.75295,1.229409 -0.75295,1.647292 0,1.225682 -4.46938,7.830205 -8.13508,12.02141 -3.38488,3.870144 -10.28753,9.764311 -13.86428,11.838691 -1.04089,0.60368 -3.28388,1.67891 -4.98442,2.3894 -1.70055,0.71049 -3.59784,1.54552 -4.21622,1.85563 -3.07547,1.5423 -10.56989,2.91875 -17.42703,3.20073 -4.01946,0.16528 -7.44188,0.15849 -7.60538,-0.0151 z"
|
||||||
|
id="path4" />
|
||||||
|
<path
|
||||||
|
style="fill:#666666;stroke-width:0.562162"
|
||||||
|
d="m 176.22224,412.93597 c -14.27631,-3.31448 -27.3642,-12.3061 -34.92525,-23.9943 -6.17372,-9.54359 -8.14478,-15.99326 -9.47275,-30.99658 -1.02089,-11.5339 -2.1083,-15.67794 -5.41225,-20.62565 -4.63901,-6.94696 -13.77624,-11.82756 -22.14302,-11.82756 -5.228643,0 -11.162073,1.77986 -20.350928,6.1047 -9.122306,4.2935 -10.163976,4.66906 -16.739665,6.03525 -8.752618,1.81847 -20.689239,0.77528 -29.698317,-2.59545 -4.182604,-1.56491 -11.444871,-6.00341 -16.262927,-9.93947 -5.985462,-4.88975 -9.59184,-9.60177 -13.6809846,-17.8753 -5.9214348,-11.98078 -7.36746076,-22.64405 -4.761285,-35.11063 1.4170213,-6.77829 2.6046715,-9.86343 6.0987717,-15.84269 7.5446749,-12.91078 20.7421299,-23.1781 34.6939309,-26.9911 6.495658,-1.77525 20.47665,-1.78015 26.935005,-0.009 4.98703,1.36732 14.624108,5.79127 18.650999,8.56184 6.287199,4.32571 14.35862,14.12776 18.24499,22.15697 2.97921,6.15505 4.25865,11.66321 5.29158,22.781 0.95297,10.25711 1.99576,15.17513 4.11103,19.38848 1.96522,3.91449 7.14288,8.78989 11.73731,11.05211 3.55735,1.75158 4.2843,1.89758 10.36201,2.08108 7.92121,0.23915 11.37518,-0.41845 17.33455,-3.30034 9.6533,-4.66822 15.31578,-9.81539 19.0009,-17.27171 l 2.12622,-4.30212 v -7.36642 c 0,-7.36297 -9.7e-4,-7.36839 -2.08226,-11.57958 -2.76167,-5.58787 -7.05727,-9.90078 -15.68542,-15.74866 -10.85825,-7.35937 -17.15186,-14.18235 -22.03721,-23.89083 -3.41797,-6.79239 -4.18005,-9.88096 -5.18178,-21.00079 -0.51352,-5.70028 -1.40997,-12.22648 -1.99213,-14.50268 -2.21718,-8.6691 -7.24969,-15.26576 -14.28345,-18.72289 -3.74537,-1.84087 -4.24652,-1.93645 -11.14588,-2.12564 -6.703985,-0.18385 -7.534793,-0.088 -11.537758,1.33056 -2.372465,0.84076 -6.10751,2.51184 -8.3001,3.7135 -2.192595,1.20166 -6.871465,3.21395 -10.397492,4.47176 l -6.410953,2.28692 -10.683594,-0.003 c -12.043232,-0.003 -13.933205,-0.30792 -21.105535,-3.40008 -9.744277,-4.20099 -15.579406,-8.76272 -23.621249,-18.4664 C 9.4391111,161.23789 8.1112645,159.06339 6.2033497,154.4486 1.6199165,143.36237 0.48469696,132.49449 2.754034,121.42702 6.2409887,104.42128 20.046648,87.66215 35.862502,81.235593 c 7.3796,-2.9986 12.197624,-3.879341 21.196956,-3.87483 7.414553,0.0037 9.10516,0.188099 14.005533,1.527482 16.109982,4.403224 29.576349,15.998483 37.183489,32.016945 2.33258,4.91176 3.18232,9.10033 4.73272,23.32869 0.77072,7.07306 1.57639,11.85022 2.33117,13.82261 1.6117,4.2116 6.38278,10.2872 10.03663,12.78087 4.24972,2.90031 7.81611,3.98719 14.0968,4.29607 7.17379,0.3528 10.57855,-0.57434 21.05149,-5.73247 15.50896,-7.63847 28.18539,-9.27043 41.16495,-5.29958 2.09441,0.64074 8.14041,3.15256 13.43555,5.58182 5.29514,2.42925 10.79266,4.71021 12.21672,5.06879 1.48221,0.37322 5.05715,0.55814 8.36166,0.43252 5.01313,-0.19058 6.25314,-0.44667 9.42653,-1.94674 5.04326,-2.38399 8.97516,-5.6629 11.72041,-9.77395 3.06149,-4.58462 3.85153,-7.48151 4.89508,-17.94918 1.74016,-17.45527 4.93995,-27.04452 12.14919,-36.409125 2.77726,-3.607582 9.49702,-10.042349 13.01413,-12.462183 4.19904,-2.889014 11.48797,-6.146221 17.24958,-7.708319 4.62118,-1.252906 6.4795,-1.448546 13.81896,-1.454838 10.00342,-0.0086 14.78652,1.008991 23.1989,4.935384 6.85139,3.197815 11.28136,6.411166 16.52942,11.989887 8.84982,9.407414 13.15322,18.154884 15.15861,30.812734 l 1.18064,7.45209 -1.18393,7.47285 c -2.24428,14.16571 -7.97048,24.40936 -19.16944,34.29238 -4.99867,4.41129 -10.00303,7.47472 -16.36696,10.01906 -5.25051,2.09918 -6.61702,2.40613 -12.36757,2.77797 -8.31257,0.5375 -17.50599,0.10355 -21.08108,-0.99508 -5.50519,-1.69175 -11.5705,-4.20674 -14.72219,-6.10459 -1.75959,-1.05958 -4.54675,-2.43066 -6.19368,-3.04686 -4.06672,-1.52155 -11.87994,-2.32516 -16.67002,-1.71454 -10.33861,1.31792 -18.98919,9.62295 -21.9788,21.10085 -0.46274,1.77658 -1.23569,7.92509 -1.71767,13.66336 -0.92011,10.95445 -1.97167,15.38628 -5.13647,21.64781 -5.29555,10.47717 -11.49434,17.02946 -23.52711,24.86883 -6.84116,4.45701 -11.22354,9.03158 -14.82935,15.47963 -1.72816,3.09036 -1.75444,3.25352 -1.75444,10.89545 v 7.7581 l 2.38919,4.52092 c 3.87383,7.33023 9.55449,12.15437 20.51096,17.41837 l 4.92688,2.3671 h 8.99459 c 8.79686,0 9.06875,-0.0361 12.36757,-1.64296 4.42572,-2.15575 9.73568,-7.55071 12.04126,-12.23401 1.87742,-3.81359 2.35922,-6.23753 3.68562,-18.54238 1.49665,-13.88433 2.70001,-18.00054 8.10734,-27.732 1.88311,-3.38901 4.103,-6.10681 9.00804,-11.02855 6.23752,-6.25875 6.80228,-6.67485 13.53612,-9.97314 8.19,-4.01154 13.83656,-5.59508 21.64324,-6.06973 16.15952,-0.9825 30.09441,4.36787 41.93321,16.10047 5.21649,5.16969 8.20568,9.255 11.36357,15.53057 2.86456,5.69262 4.06002,9.67806 5.19046,17.30392 1.05443,7.11315 0.80678,11.03163 -1.21182,19.1743 -4.07479,16.43699 -15.48282,30.20125 -31.20513,37.65026 -15.2814,7.24011 -32.28907,6.51076 -50.04289,-2.14602 -4.12966,-2.01362 -9.40069,-4.18529 -11.7134,-4.82593 -15.89832,-4.40396 -30.93823,4.63609 -34.53755,20.75953 -0.44864,2.00973 -1.22301,7.95459 -1.72083,13.21081 -1.07546,11.35532 -1.81775,14.59129 -4.81345,20.98395 -6.57831,14.03776 -19.65376,25.3Line truncated
|
||||||
|
id="path5" />
|
||||||
|
<path
|
||||||
|
style="fill:#999999;stroke-width:0.562162"
|
||||||
|
d="m 175.95675,111.37992 c -10.99475,-2.4062 -19.5687,-7.28188 -27.93576,-15.886043 -4.54217,-4.670887 -6.59161,-7.312268 -8.96287,-11.551679 -4.76714,-8.522828 -6.38785,-15.247462 -6.38785,-26.504321 0,-10.107303 1.03301,-15.975432 3.88128,-22.048044 3.19391,-6.809532 6.14903,-11.127545 11.25563,-16.446696 10.16277,-10.5857799 19.59958,-15.2165005 35.20757,-17.2766401 4.34916,-0.5740577 5.98163,-0.5321512 11.21046,0.2877807 9.28788,1.4564284 17.94097,4.52445 22.73943,8.0624424 10.14221,7.478042 16.67077,14.732556 20.58689,22.876092 3.83142,7.967391 4.30153,10.302845 4.59599,22.832765 0.2327,9.902058 0.13742,11.516425 -0.92904,15.74054 -4.57937,18.138285 -20.91198,34.480333 -39.36116,39.383903 -6.28332,1.67003 -19.45914,1.9396 -25.90057,0.5299 z"
|
||||||
|
id="path6" />
|
||||||
|
<path
|
||||||
|
style="fill:#999999;stroke-width:0.562162"
|
||||||
|
d="m 176.51891,412.99193 c -18.014,-4.24378 -31.74984,-15.32437 -39.6549,-31.98928 -2.72624,-5.7473 -4.02486,-11.61565 -5.00659,-22.62428 -0.46874,-5.25622 -1.24485,-11.05566 -1.72469,-12.88765 -2.4271,-9.26643 -9.98203,-16.60913 -19.69123,-19.13806 -7.084,-1.84514 -13.016383,-0.78153 -24.14961,4.32977 -10.192826,4.67956 -12.230957,5.44159 -17.989189,6.72593 -9.382453,2.0927 -21.351551,1.17161 -30.822641,-2.37198 C 30.118977,332.28225 19.571198,324.6698 14.264732,318.28163 9.7058942,312.79348 4.2793903,301.48601 2.7519433,294.29188 c -2.08046687,-9.7988 -1.1538709,-21.85184 2.3671206,-30.7912 3.163004,-8.03047 10.3760521,-17.54477 17.8574991,-23.55474 12.396451,-9.95828 26.681773,-13.87478 42.555106,-11.667 7.572211,1.0532 18.566647,5.76775 25.356936,10.87337 5.815905,4.37298 12.825085,13.22132 16.519665,20.85433 3.0022,6.20254 4.17011,11.21153 5.28233,22.65531 1.04612,10.7636 1.86762,14.64281 4.0584,19.16421 1.98717,4.10118 7.20896,9.12687 11.86197,11.41654 3.74376,1.84224 3.78961,1.84918 12.20048,1.84747 7.8435,-0.002 8.69112,-0.10633 11.94472,-1.476 10.92484,-4.59902 18.58151,-11.00805 22.41058,-18.75884 l 2.19541,-4.44393 v -7.33543 c 0,-7.17305 -0.0415,-7.42033 -1.87497,-11.17032 -2.61838,-5.35538 -7.7954,-10.61122 -15.97007,-16.21323 -10.97394,-7.52032 -17.06914,-14.14372 -21.95985,-23.86285 -3.40786,-6.7723 -4.2398,-10.0683 -5.14589,-20.3871 -0.46284,-5.27092 -1.22284,-11.42717 -1.6889,-13.68056 -1.86747,-9.0292 -7.37139,-16.58353 -14.71888,-20.20221 -3.63574,-1.79063 -4.18354,-1.89385 -11.04957,-2.08213 -6.698666,-0.1837 -7.536119,-0.0874 -11.512326,1.32421 -2.358478,0.83728 -6.323374,2.61502 -8.810874,3.95055 -2.487495,1.33553 -7.17894,3.34803 -10.425427,4.47223 l -5.902702,2.04401 -10.44097,0.01 c -5.742535,0.005 -11.434427,-0.17419 -12.64865,-0.39915 C 39.772586,185.87135 30.31374,181.46615 24.978535,177.4556 21.088086,174.5311 14.265586,167.55892 10.790813,162.95662 5.6611499,156.16243 1.5674794,142.61878 1.5515758,132.38919 1.5310317,119.17464 7.1018281,105.23305 16.379544,95.280423 c 7.916371,-8.492254 16.144723,-13.564837 26.62586,-16.41421 4.965169,-1.349817 6.606302,-1.526699 14.054054,-1.514751 7.41045,0.01189 9.11375,0.199517 14.054054,1.548147 15.963448,4.357787 29.343418,15.773565 36.860788,31.449601 2.60623,5.43479 3.40957,9.28416 5.01027,24.00754 1.13476,10.43765 1.29598,11.17735 3.36949,15.45946 4.86771,10.05261 13.48718,15.45946 24.64506,15.45946 5.8759,0 9.79055,-1.18531 19.46332,-5.89324 9.02871,-4.39446 14.37826,-6.06535 22.55732,-7.04558 9.80689,-1.17533 17.90288,0.68764 32.56942,7.49455 11.43901,5.30897 12.65703,5.63467 19.94839,5.33426 5.68031,-0.23404 6.44452,-0.4059 10.31806,-2.3203 6.76303,-3.34248 12.63096,-10.03964 14.37123,-16.40212 0.33827,-1.23676 1.00748,-6.16973 1.48713,-10.96216 1.28513,-12.84042 2.879,-19.71099 6.2065,-26.75374 4.75376,-10.061457 14.73847,-20.44785 24.1985,-25.171999 9.82331,-4.905565 14.70285,-6.053815 25.7837,-6.067415 10.05543,-0.01234 15.08347,1.04753 23.24626,4.900131 6.9187,3.265423 11.30059,6.46105 16.66762,12.155355 5.63486,5.978478 9.24298,11.514008 11.76033,18.042548 8.58449,22.26322 2.5079,46.15789 -15.69583,61.71983 -5.64299,4.82406 -10.39679,7.74025 -16.58378,10.17319 -5.19466,2.04273 -6.67818,2.37217 -12.36757,2.74637 -8.28969,0.54522 -17.49599,0.1138 -21.08108,-0.9879 -5.60872,-1.72357 -11.64854,-4.24236 -15.3265,-6.39164 -4.83842,-2.82741 -7.99401,-3.85101 -13.62079,-4.41824 -6.50447,-0.65572 -11.70717,0.0477 -16.00628,2.16417 -6.7127,3.30465 -12.49629,10.8072 -14.55731,18.88393 -0.4734,1.85514 -1.258,7.97645 -1.74355,13.60293 -0.96229,11.15049 -1.96397,15.3348 -5.22193,21.81329 -5.31831,10.57554 -10.96502,16.4776 -24.31283,25.41228 -5.22637,3.49841 -11.05373,9.60147 -13.71987,14.36896 l -2.02067,3.61329 v 7.82986 7.82986 l 2.55257,4.48368 c 4.27078,7.50178 9.85506,12.25727 20.25132,17.24575 l 5.00138,2.39983 7.66548,0.21051 c 5.86771,0.16115 8.39252,-8.4e-4 10.76555,-0.6907 6.20318,-1.80331 13.56349,-8.9854 16.06778,-15.6787 0.66615,-1.78045 1.47339,-6.4977 2.02909,-11.85744 1.75433,-16.92035 3.02528,-21.91842 7.80999,-30.71313 2.40967,-4.42918 4.05669,-6.50398 9.65144,-12.15824 6.53778,-6.60733 6.96277,-6.92374 13.77297,-10.25423 8.53865,-4.17578 13.97019,-5.64138 22.48648,-6.06755 16.00921,-0.80112 29.31643,4.41189 41.08997,16.09672 10.36692,10.2888 15.65635,21.68311 17.10052,36.83735 0.66921,7.02217 -2.07387,19.05964 -6.22777,27.32937 -6.74057,13.41937 -20.98171,24.94045 -35.35765,28.60433 -14.22242,3.62475 -26.10025,2.06617 -42.26883,-5.54642 -12.81611,-6.03416 -18.62818,-6.95771 -27.04782,-4.29795 -7.30396,2.30732 -12.43433,6.72357 -15.92438,13.70778 -2.18148,4.36553 -3.10358,8.83379 -4.1616,20.16617 -1.05237,11.27183 -1.76206,14.35742 -4.78674,20.81191 -6.6811,14.25711 -19.4581,25.32512 -34.83137,30.17248 -4.15237,1.30929 -5.95061,1.53154 -14.05406,1.73703 -6.40822,0.1625 -10.25209,0.01 -12.38432,-0.49276 z"
|
||||||
|
id="path7" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM8.16801 2.56435C8.09081 2.43177 7.89923 2.4319 7.82221 2.56458L4.82431 7.72899C4.76983 7.82284 4.80013 7.94301 4.89259 7.99981L7.89038 9.84139C7.95451 9.88079 8.03533 9.88085 8.09952 9.84154L11.1069 7.99986C11.1996 7.94308 11.23 7.82262 11.1752 7.72866L8.16801 2.56435ZM8.08754 10.7831C8.02182 10.8253 7.93759 10.8253 7.87181 10.7833L5.51555 9.27665C5.33379 9.16043 5.1222 9.37463 5.24065 9.55495L7.8123 13.4701C7.89136 13.5905 8.06789 13.5903 8.14678 13.4699L10.7098 9.55566C10.828 9.37517 10.6161 9.16127 10.4345 9.27775L8.08754 10.7831Z" fill="#3B82F6"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.82232 2.56456C7.89934 2.43187 8.09092 2.43174 8.16812 2.56432L11.1754 7.72864C11.2301 7.8226 11.1997 7.94305 11.107 7.99984L8.09963 9.84152C8.03544 9.88082 7.95463 9.88077 7.89049 9.84137L4.8927 7.99979C4.80024 7.94299 4.76994 7.82282 4.82442 7.72897L7.82232 2.56456ZM7.87193 10.7833C7.9377 10.8253 8.02193 10.8253 8.08765 10.7831L10.4346 9.27773C10.6162 9.16125 10.8281 9.37515 10.7099 9.55563L8.1469 13.4698C8.06801 13.5903 7.89147 13.5904 7.81241 13.4701L5.24076 9.55492C5.12232 9.3746 5.3339 9.1604 5.51566 9.27662L7.87193 10.7833Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" fill="#efb914" fill-rule="nonzero" r="16"/><path d="M21.002 9.855A7.947 7.947 0 0124 15.278l-2.847-.708a5.357 5.357 0 00-3.86-3.667c-2.866-.713-5.76.991-6.465 3.806s1.05 5.675 3.917 6.388a5.373 5.373 0 005.134-1.43l2.847.707a7.974 7.974 0 01-5.2 3.385L16.716 27l-2.596-.645.644-2.575a8.28 8.28 0 01-1.298-.323l-.643 2.575-2.596-.646.81-3.241c-2.378-1.875-3.575-4.996-2.804-8.081s3.297-5.281 6.28-5.823L15.323 5l2.596.645-.644 2.575a8.28 8.28 0 011.298.323l.643-2.575 2.596.646z" fill="#fff"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 644 B |
@@ -0,0 +1,32 @@
|
|||||||
|
.dropdown-enter-active,
|
||||||
|
.dropdown-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-enter-from,
|
||||||
|
.dropdown-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-enter-active,
|
||||||
|
.page-leave-active {
|
||||||
|
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-enter-from,
|
||||||
|
.page-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-enter-active,
|
||||||
|
.resize-leave-active {
|
||||||
|
max-height: 100px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-enter-from,
|
||||||
|
.resize-leave-to {
|
||||||
|
max-height: 0px;
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
<svg width="14" height="12" viewBox="0 0 14 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<path d="M4.52683 11.5021C9.55816 11.5021 12.3102 7.33344 12.3102 3.71877C12.3102 3.6001 12.3102 3.4821 12.3022 3.36544C12.8376 2.97779 13.2997 2.49784 13.6668 1.9481C13.1675 2.16944 12.6379 2.31461 12.0955 2.37877C12.6666 2.03681 13.094 1.499 13.2982 0.865436C12.7613 1.18403 12.174 1.40859 11.5615 1.52944C11.1491 1.09061 10.6035 0.799992 10.0092 0.702573C9.41498 0.605153 8.80517 0.706369 8.27424 0.990549C7.74331 1.27473 7.32088 1.72602 7.07236 2.27454C6.82383 2.82307 6.76307 3.43823 6.8995 4.02477C5.81189 3.97026 4.7479 3.68765 3.77659 3.19528C2.80529 2.70291 1.94838 2.01179 1.2615 1.16677C0.911708 1.7689 0.804555 2.48172 0.961853 3.16008C1.11915 3.83844 1.52907 4.43135 2.10816 4.8181C1.673 4.80551 1.24725 4.68844 0.866829 4.47677V4.51144C0.867089 5.14297 1.08576 5.75497 1.48576 6.24367C1.88576 6.73238 2.44247 7.06769 3.0615 7.19277C2.6587 7.30258 2.23609 7.31854 1.82616 7.23944C2.00094 7.78309 2.34128 8.2585 2.79958 8.59918C3.25788 8.93986 3.81121 9.12875 4.38216 9.13944C3.81491 9.58515 3.16535 9.91466 2.47065 10.1091C1.77594 10.3036 1.04971 10.3592 0.333496 10.2728C1.58457 11.0757 3.04029 11.5015 4.52683 11.4994" fill="#1F2937"/>
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1.44084,0,0,1.44084,-0.0860978,1.23349)">
|
||||||
|
<path d="M4.527,11.502C9.558,11.502 12.31,7.333 12.31,3.719C12.31,3.6 12.31,3.482 12.302,3.365C12.838,2.978 13.3,2.498 13.667,1.948C13.168,2.169 12.638,2.315 12.096,2.379C12.667,2.037 13.094,1.499 13.298,0.865C12.761,1.184 12.174,1.409 11.562,1.529C11.149,1.091 10.604,0.8 10.009,0.703C9.415,0.605 8.805,0.706 8.274,0.991C7.743,1.275 7.321,1.726 7.072,2.275C6.824,2.823 6.763,3.438 6.9,4.025C5.812,3.97 4.748,3.688 3.777,3.195C2.805,2.703 1.948,2.012 1.262,1.167C0.912,1.769 0.805,2.482 0.962,3.16C1.119,3.838 1.529,4.431 2.108,4.818C1.673,4.806 1.247,4.688 0.867,4.477L0.867,4.511C0.867,5.143 1.086,5.755 1.486,6.244C1.886,6.732 2.442,7.068 3.062,7.193C2.659,7.303 2.236,7.319 1.826,7.239C2.001,7.783 2.341,8.259 2.8,8.599C3.258,8.94 3.811,9.129 4.382,9.139C3.815,9.585 3.165,9.915 2.471,10.109C1.776,10.304 1.05,10.359 0.333,10.273C1.585,11.076 3.04,11.502 4.527,11.499" style="fill:rgb(31,41,55);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,104 +0,0 @@
|
|||||||
import { expectTypeOf, it, expect } from "vitest";
|
|
||||||
import {
|
|
||||||
getTokenAddress,
|
|
||||||
getP2PixAddress,
|
|
||||||
getProviderUrl,
|
|
||||||
isPossibleNetwork,
|
|
||||||
possibleChains,
|
|
||||||
network2Chain,
|
|
||||||
} from "../addresses";
|
|
||||||
|
|
||||||
import { setActivePinia, createPinia } from "pinia";
|
|
||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
|
||||||
import { useEtherStore } from "@/store/ether";
|
|
||||||
|
|
||||||
describe("addresses.ts types", () => {
|
|
||||||
it("My addresses.ts types work properly", () => {
|
|
||||||
expectTypeOf(getTokenAddress).toBeFunction();
|
|
||||||
expectTypeOf(getP2PixAddress).toBeFunction();
|
|
||||||
expectTypeOf(getProviderUrl).toBeFunction();
|
|
||||||
expectTypeOf(isPossibleNetwork).toBeFunction();
|
|
||||||
|
|
||||||
expectTypeOf(possibleChains).toBeObject();
|
|
||||||
expectTypeOf(network2Chain).toBeObject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("addresses.ts functions", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
setActivePinia(createPinia());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getTokenAddress Ethereum", () => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
|
||||||
expect(getTokenAddress()).toBe(
|
|
||||||
"0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getTokenAddress Polygon", () => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
etherStore.setNetworkName(NetworkEnum.polygon);
|
|
||||||
expect(getTokenAddress()).toBe(
|
|
||||||
"0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getTokenAddress Default", () => {
|
|
||||||
expect(getTokenAddress()).toBe(
|
|
||||||
"0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getP2PixAddress Ethereum", () => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
|
||||||
expect(getP2PixAddress()).toBe(
|
|
||||||
"0x2414817FF64A114d91eCFA16a834d3fCf69103d4"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getP2PixAddress Polygon", () => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
etherStore.setNetworkName(NetworkEnum.polygon);
|
|
||||||
expect(getP2PixAddress()).toBe(
|
|
||||||
"0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getP2PixAddress Default", () => {
|
|
||||||
expect(getP2PixAddress()).toBe(
|
|
||||||
"0x2414817FF64A114d91eCFA16a834d3fCf69103d4"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getProviderUrl Ethereum", () => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
|
||||||
expect(getProviderUrl()).toBe(import.meta.env.VITE_GOERLI_API_URL);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getProviderUrl Polygon", () => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
etherStore.setNetworkName(NetworkEnum.polygon);
|
|
||||||
expect(getProviderUrl()).toBe(import.meta.env.VITE_MUMBAI_API_URL);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getProviderUrl Default", () => {
|
|
||||||
expect(getProviderUrl()).toBe(import.meta.env.VITE_GOERLI_API_URL);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("isPossibleNetwork Returns", () => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
|
||||||
expect(isPossibleNetwork("0x5")).toBe(true);
|
|
||||||
expect(isPossibleNetwork("5")).toBe(true);
|
|
||||||
expect(isPossibleNetwork("0x13881")).toBe(true);
|
|
||||||
expect(isPossibleNetwork("80001")).toBe(true);
|
|
||||||
|
|
||||||
expect(isPossibleNetwork("")).toBe(false);
|
|
||||||
expect(isPossibleNetwork(" ")).toBe(false);
|
|
||||||
expect(isPossibleNetwork("0x55")).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import { useEtherStore } from "@/store/ether";
|
|
||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
|
||||||
|
|
||||||
const getTokenAddress = (network?: NetworkEnum): string => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
|
|
||||||
const possibleTokenAddresses: { [key: string]: string } = {
|
|
||||||
Ethereum: "0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00",
|
|
||||||
Polygon: "0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29",
|
|
||||||
Rootstock: "0xfE841c74250e57640390f46d914C88d22C51e82e",
|
|
||||||
};
|
|
||||||
|
|
||||||
return possibleTokenAddresses[network ? network : etherStore.networkName];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getP2PixAddress = (network?: NetworkEnum): string => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
|
|
||||||
const possibleP2PixAddresses: { [key: string]: string } = {
|
|
||||||
Ethereum: "0x2414817FF64A114d91eCFA16a834d3fCf69103d4",
|
|
||||||
Polygon: "0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00",
|
|
||||||
Rootstock: "0x98ba35eb14b38D6Aa709338283af3e922476dE34",
|
|
||||||
};
|
|
||||||
|
|
||||||
return possibleP2PixAddresses[network ? network : etherStore.networkName];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getProviderUrl = (): string => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
|
|
||||||
const possibleProvidersUrls: { [key: string]: string } = {
|
|
||||||
Ethereum: import.meta.env.VITE_GOERLI_API_URL,
|
|
||||||
Polygon: import.meta.env.VITE_MUMBAI_API_URL,
|
|
||||||
Rootstock: import.meta.env.VITE_RSK_API_URL,
|
|
||||||
};
|
|
||||||
|
|
||||||
return possibleProvidersUrls[etherStore.networkName];
|
|
||||||
};
|
|
||||||
|
|
||||||
const possibleChains: { [key: string]: NetworkEnum } = {
|
|
||||||
"0x5": NetworkEnum.ethereum,
|
|
||||||
"5": NetworkEnum.ethereum,
|
|
||||||
"0x13881": NetworkEnum.polygon,
|
|
||||||
"80001": NetworkEnum.polygon,
|
|
||||||
"31": NetworkEnum.rootstock,
|
|
||||||
};
|
|
||||||
|
|
||||||
const network2Chain: { [key: string]: string } = {
|
|
||||||
Ethereum: "0x5",
|
|
||||||
Polygon: "0x13881",
|
|
||||||
Localhost: "0x7a69",
|
|
||||||
Rootstock: "0x1f",
|
|
||||||
};
|
|
||||||
|
|
||||||
const isPossibleNetwork = (networkChain: string): boolean => {
|
|
||||||
if (Object.keys(possibleChains).includes(networkChain.toString())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
getTokenAddress,
|
|
||||||
getProviderUrl,
|
|
||||||
possibleChains,
|
|
||||||
network2Chain,
|
|
||||||
isPossibleNetwork,
|
|
||||||
getP2PixAddress,
|
|
||||||
};
|
|
||||||
@@ -1,100 +1,82 @@
|
|||||||
import { useEtherStore } from "@/store/ether";
|
import { getContract } from "./provider";
|
||||||
|
import { ChainContract } from "viem";
|
||||||
|
import {
|
||||||
|
parseEther,
|
||||||
|
type Address,
|
||||||
|
type TransactionReceipt,
|
||||||
|
} from "viem";
|
||||||
|
|
||||||
import { getContract, getProvider } from "./provider";
|
export const addLock = async (
|
||||||
import { getP2PixAddress, getTokenAddress } from "./addresses";
|
sellerAddress: Address,
|
||||||
|
tokenAddress: Address,
|
||||||
import p2pix from "@/utils/smart_contract_files/P2PIX.json";
|
|
||||||
|
|
||||||
import { BigNumber, ethers } from "ethers";
|
|
||||||
import { parseEther } from "ethers/lib/utils";
|
|
||||||
|
|
||||||
const addLock = async (
|
|
||||||
seller: string,
|
|
||||||
token: string,
|
|
||||||
amount: number
|
amount: number
|
||||||
): Promise<string> => {
|
): Promise<bigint> => {
|
||||||
const etherStore = useEtherStore();
|
const { address, abi, wallet, client, account } = await getContract();
|
||||||
|
const parsedAmount = parseEther(amount.toString());
|
||||||
|
|
||||||
const p2pContract = getContract();
|
if (!wallet) {
|
||||||
|
throw new Error("Wallet not connected");
|
||||||
|
}
|
||||||
|
|
||||||
const lock = await p2pContract.lock(
|
const { result, request } = await client.simulateContract({
|
||||||
seller,
|
address,
|
||||||
token,
|
abi,
|
||||||
etherStore.walletAddress, // String "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" (Example)
|
functionName: "lock",
|
||||||
ethers.constants.AddressZero, // String "0x0000000000000000000000000000000000000000"
|
args: [sellerAddress, tokenAddress, parsedAmount, [], []],
|
||||||
0,
|
account,
|
||||||
parseEther(String(amount)), // BigNumber
|
});
|
||||||
[],
|
const hash = await wallet.writeContract(request);
|
||||||
[]
|
const receipt = await client.waitForTransactionReceipt({ hash });
|
||||||
);
|
|
||||||
|
|
||||||
const lock_rec = await lock.wait();
|
if (!receipt.status)
|
||||||
const [t] = lock_rec.events;
|
throw new Error("Transaction failed: " + receipt.transactionHash);
|
||||||
|
|
||||||
return String(t.args.lockID);
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const releaseLock = async (
|
export const withdrawDeposit = async (
|
||||||
pixKey: number,
|
amount: string,
|
||||||
amount: number,
|
token: Address
|
||||||
e2eId: string,
|
): Promise<boolean> => {
|
||||||
lockId: string
|
const { address, abi, wallet, client, account } = await getContract();
|
||||||
): Promise<any> => {
|
|
||||||
const mockBacenSigner = new ethers.Wallet(
|
|
||||||
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
|
|
||||||
);
|
|
||||||
|
|
||||||
const messageToSign = ethers.utils.solidityKeccak256(
|
if (!wallet) {
|
||||||
["uint160", "uint256", "bytes32"],
|
throw new Error("Wallet not connected");
|
||||||
[
|
}
|
||||||
pixKey,
|
|
||||||
parseEther(String(amount)),
|
|
||||||
ethers.utils.formatBytes32String(e2eId),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const messageHashBytes = ethers.utils.arrayify(messageToSign);
|
const { request } = await client.simulateContract({
|
||||||
const flatSig = await mockBacenSigner.signMessage(messageHashBytes);
|
address,
|
||||||
const provider = getProvider();
|
abi,
|
||||||
|
functionName: "withdraw",
|
||||||
|
args: [token, parseEther(amount), []],
|
||||||
|
account
|
||||||
|
});
|
||||||
|
|
||||||
const sig = ethers.utils.splitSignature(flatSig);
|
const hash = await wallet.writeContract(request);
|
||||||
|
const receipt = await client.waitForTransactionReceipt({ hash });
|
||||||
|
|
||||||
const signer = provider.getSigner();
|
return receipt.status === "success";
|
||||||
const p2pContract = new ethers.Contract(getP2PixAddress(), p2pix.abi, signer);
|
|
||||||
|
|
||||||
const release = await p2pContract.release(
|
|
||||||
BigNumber.from(lockId),
|
|
||||||
ethers.constants.AddressZero,
|
|
||||||
ethers.utils.formatBytes32String(e2eId),
|
|
||||||
sig.r,
|
|
||||||
sig.s,
|
|
||||||
sig.v
|
|
||||||
);
|
|
||||||
await release.wait();
|
|
||||||
|
|
||||||
return release;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelDeposit = async (depositId: BigNumber): Promise<any> => {
|
export const releaseLock = async (
|
||||||
const contract = getContract();
|
lockID: bigint,
|
||||||
|
pixTimestamp: `0x${string}`&{lenght:34},
|
||||||
|
signature: `0x${string}`
|
||||||
|
): Promise<TransactionReceipt> => {
|
||||||
|
const { address, abi, wallet, client, account } = await getContract();
|
||||||
|
|
||||||
const cancel = await contract.cancelDeposit(depositId);
|
if (!wallet) {
|
||||||
await cancel.wait();
|
throw new Error("Wallet not connected");
|
||||||
|
}
|
||||||
|
|
||||||
return cancel;
|
const { request } = await client.simulateContract({
|
||||||
|
address,
|
||||||
|
abi,
|
||||||
|
functionName: "release",
|
||||||
|
args: [BigInt(lockID), pixTimestamp, signature],
|
||||||
|
account
|
||||||
|
});
|
||||||
|
|
||||||
|
const hash = await wallet.writeContract(request);
|
||||||
|
return client.waitForTransactionReceipt({ hash });
|
||||||
};
|
};
|
||||||
|
|
||||||
const withdrawDeposit = async (amount: string): Promise<any> => {
|
|
||||||
const contract = getContract();
|
|
||||||
|
|
||||||
const withdraw = await contract.withdraw(
|
|
||||||
getTokenAddress(),
|
|
||||||
parseEther(String(amount)),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
await withdraw.wait();
|
|
||||||
|
|
||||||
return withdraw;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { cancelDeposit, withdrawDeposit, addLock, releaseLock };
|
|
||||||
@@ -1,141 +1,170 @@
|
|||||||
import { useEtherStore } from "@/store/ether";
|
import { useUser } from "@/composables/useUser";
|
||||||
import { Contract, ethers } from "ethers";
|
import { formatEther, toHex, stringToHex } from "viem";
|
||||||
|
import type { PublicClient, Address } from "viem";
|
||||||
import p2pix from "@/utils/smart_contract_files/P2PIX.json";
|
import { Networks } from "@/config/networks";
|
||||||
import { formatEther } from "ethers/lib/utils";
|
|
||||||
import { getContract } from "./provider";
|
import { getContract } from "./provider";
|
||||||
|
import { p2PixAbi } from "./abi"
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
import { getP2PixAddress, getTokenAddress } from "./addresses";
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
|
||||||
import type { UnreleasedLock } from "@/model/UnreleasedLock";
|
import type { UnreleasedLock } from "@/model/UnreleasedLock";
|
||||||
import type { Pix } from "@/model/Pix";
|
import { ChainContract } from "viem";
|
||||||
|
|
||||||
const getNetworksLiquidity = async (): Promise<void> => {
|
const getNetworksLiquidity = async (): Promise<void> => {
|
||||||
const etherStore = useEtherStore();
|
const user = useUser();
|
||||||
|
user.setLoadingNetworkLiquidity(true);
|
||||||
|
|
||||||
const goerliProvider = new ethers.providers.JsonRpcProvider(
|
const depositLists: ValidDeposit[][] = [];
|
||||||
import.meta.env.VITE_GOERLI_API_URL,
|
|
||||||
5
|
|
||||||
); // goerli provider
|
|
||||||
const mumbaiProvider = new ethers.providers.JsonRpcProvider(
|
|
||||||
import.meta.env.VITE_MUMBAI_API_URL,
|
|
||||||
80001
|
|
||||||
); // mumbai provider
|
|
||||||
const rootstockProvider = new ethers.providers.JsonRpcProvider(
|
|
||||||
import.meta.env.VITE_RSK_API_URL,
|
|
||||||
31
|
|
||||||
); // rootstock provider
|
|
||||||
|
|
||||||
const p2pContractGoerli = new ethers.Contract(
|
for (const network of Object.values(Networks)) {
|
||||||
getP2PixAddress(NetworkEnum.ethereum),
|
const deposits = await getValidDeposits(
|
||||||
p2pix.abi,
|
user.network.value.tokens[user.selectedToken.value].address,
|
||||||
goerliProvider
|
network
|
||||||
);
|
|
||||||
const p2pContractMumbai = new ethers.Contract(
|
|
||||||
getP2PixAddress(NetworkEnum.polygon),
|
|
||||||
p2pix.abi,
|
|
||||||
mumbaiProvider
|
|
||||||
);
|
);
|
||||||
|
if (deposits) depositLists.push(deposits);
|
||||||
|
}
|
||||||
|
|
||||||
const p2pContractRootstock = new ethers.Contract(
|
const allDeposits = depositLists.flat();
|
||||||
getP2PixAddress(NetworkEnum.rootstock),
|
user.setDepositsValidList(allDeposits);
|
||||||
p2pix.abi,
|
user.setLoadingNetworkLiquidity(false);
|
||||||
rootstockProvider
|
};
|
||||||
);
|
|
||||||
|
|
||||||
etherStore.setLoadingNetworkLiquidity(true);
|
const getParticipantID = async (
|
||||||
const depositListGoerli = await getValidDeposits(
|
seller: Address,
|
||||||
getTokenAddress(NetworkEnum.ethereum),
|
token: Address
|
||||||
p2pContractGoerli
|
): Promise<string> => {
|
||||||
);
|
const { address, abi, client } = await getContract();
|
||||||
|
|
||||||
const depositListMumbai = await getValidDeposits(
|
const participantIDHex = await client.readContract({
|
||||||
getTokenAddress(NetworkEnum.polygon),
|
address,
|
||||||
p2pContractMumbai
|
abi,
|
||||||
);
|
functionName: "getPixTarget",
|
||||||
|
args: [seller, token],
|
||||||
|
});
|
||||||
|
|
||||||
etherStore.setDepositsValidListGoerli(depositListGoerli);
|
// Remove '0x' prefix and convert hex to UTF-8 string
|
||||||
const depositListRootstock = await getValidDeposits(
|
const hexString =
|
||||||
getTokenAddress(NetworkEnum.rootstock),
|
typeof participantIDHex === "string"
|
||||||
p2pContractRootstock
|
? participantIDHex
|
||||||
|
: toHex(participantIDHex as bigint);
|
||||||
|
if (!hexString) throw new Error("Participant ID not found");
|
||||||
|
const bytes = new Uint8Array(
|
||||||
|
hexString
|
||||||
|
.slice(2)
|
||||||
|
.match(/.{1,2}/g)!
|
||||||
|
.map((byte: string) => parseInt(byte, 16))
|
||||||
);
|
);
|
||||||
etherStore.setDepositsValidListMumbai(depositListMumbai);
|
// Remove null bytes from the end of the string
|
||||||
etherStore.setDepositsValidListRootstock(depositListRootstock);
|
return new TextDecoder().decode(bytes).replace(/\0/g, "");
|
||||||
etherStore.setLoadingNetworkLiquidity(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getValidDeposits = async (
|
const getValidDeposits = async (
|
||||||
token: string,
|
token: Address,
|
||||||
contract?: Contract
|
network: NetworkConfig,
|
||||||
|
contractInfo?: { client: PublicClient; address: Address }
|
||||||
): Promise<ValidDeposit[]> => {
|
): Promise<ValidDeposit[]> => {
|
||||||
let p2pContract: Contract;
|
let client: PublicClient, abi;
|
||||||
|
|
||||||
if (contract) {
|
if (contractInfo) {
|
||||||
p2pContract = contract;
|
({ client } = contractInfo);
|
||||||
|
abi = p2PixAbi;
|
||||||
} else {
|
} else {
|
||||||
p2pContract = getContract(true);
|
({ abi, client } = await getContract(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterDeposits = p2pContract.filters.DepositAdded(null);
|
const body = {
|
||||||
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
|
query: `
|
||||||
|
{
|
||||||
|
depositAddeds(where: { token: "${token}" }) {
|
||||||
|
seller
|
||||||
|
amount
|
||||||
|
blockTimestamp
|
||||||
|
blockNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const depositLogs = await fetch( network.subgraphUrls[0], {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove doubles from sellers list
|
||||||
|
const depositData = await depositLogs.json();
|
||||||
|
const depositAddeds = depositData.data.depositAddeds;
|
||||||
|
const uniqueSellers = depositAddeds.reduce(
|
||||||
|
(acc: Record<Address, boolean>, deposit: any) => {
|
||||||
|
acc[deposit.seller] = true;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<Address, boolean>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!contractInfo) {
|
||||||
|
// Get metamask provider contract
|
||||||
|
({ abi, client } = await getContract(true));
|
||||||
|
}
|
||||||
|
|
||||||
if (!contract) p2pContract = getContract(); // get metamask provider contract
|
|
||||||
const depositList: { [key: string]: ValidDeposit } = {};
|
const depositList: { [key: string]: ValidDeposit } = {};
|
||||||
|
|
||||||
await Promise.all(
|
const sellersList = Object.keys(uniqueSellers) as Address[];
|
||||||
eventsDeposits.map(async (deposit) => {
|
// Use multicall to batch all getBalance requests
|
||||||
// Get liquidity only for the selected token
|
const balanceCalls = sellersList.map((seller) => ({
|
||||||
if (deposit.args?.token != token) return null;
|
address: (network.contracts?.p2pix as ChainContract).address,
|
||||||
|
abi,
|
||||||
|
functionName: "getBalance",
|
||||||
|
args: [seller, token],
|
||||||
|
}));
|
||||||
|
|
||||||
const mappedBalance = await p2pContract.getBalance(
|
const balanceResults = await client.multicall({
|
||||||
deposit.args?.seller,
|
contracts: balanceCalls as any,
|
||||||
token
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const mappedPixTarget = await p2pContract.getPixTarget(
|
// Process results into the depositList
|
||||||
deposit.args?.seller,
|
sellersList.forEach((seller, index) => {
|
||||||
token
|
const mappedBalance = balanceResults[index];
|
||||||
);
|
|
||||||
|
|
||||||
let validDeposit: ValidDeposit | null = null;
|
if (!mappedBalance.error && mappedBalance.result) {
|
||||||
|
const validDeposit: ValidDeposit = {
|
||||||
if (mappedBalance._hex) {
|
token,
|
||||||
validDeposit = {
|
blockNumber: 0,
|
||||||
token: token,
|
remaining: Number(formatEther(mappedBalance.result as bigint)),
|
||||||
blockNumber: deposit.blockNumber,
|
seller,
|
||||||
remaining: Number(formatEther(mappedBalance._hex)),
|
network,
|
||||||
seller: deposit.args?.seller,
|
participantID: "",
|
||||||
pixKey: Number(mappedPixTarget._hex),
|
|
||||||
};
|
};
|
||||||
|
depositList[seller + token] = validDeposit;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
if (validDeposit)
|
|
||||||
depositList[deposit.args?.seller + token] = validDeposit;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return Object.values(depositList);
|
return Object.values(depositList);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUnreleasedLockById = async (
|
const getUnreleasedLockById = async (
|
||||||
lockID: string
|
lockID: bigint
|
||||||
): Promise<UnreleasedLock> => {
|
): Promise<UnreleasedLock> => {
|
||||||
const p2pContract = getContract();
|
const { address, abi, client } = await getContract();
|
||||||
const pixData: Pix = {
|
|
||||||
pixKey: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const lock = await p2pContract.mapLocks(lockID);
|
const [ , , , amount, token, seller ] = await client.readContract({
|
||||||
|
address,
|
||||||
const pixTarget = lock.pixTarget;
|
abi,
|
||||||
const amount = formatEther(lock?.amount);
|
functionName: "mapLocks",
|
||||||
pixData.pixKey = String(Number(pixTarget));
|
args: [lockID],
|
||||||
pixData.value = Number(amount);
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lockID: lockID,
|
lockID,
|
||||||
pix: pixData,
|
amount: Number(formatEther(amount)),
|
||||||
|
tokenAddress: token,
|
||||||
|
sellerAddress: seller,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getValidDeposits, getNetworksLiquidity, getUnreleasedLockById };
|
export {
|
||||||
|
getValidDeposits,
|
||||||
|
getNetworksLiquidity,
|
||||||
|
getUnreleasedLockById,
|
||||||
|
getParticipantID,
|
||||||
|
};
|
||||||
@@ -1,95 +1,63 @@
|
|||||||
import { useEtherStore } from "@/store/ether";
|
import { p2PixAbi } from "./abi";
|
||||||
|
|
||||||
import p2pix from "@/utils/smart_contract_files/P2PIX.json";
|
|
||||||
|
|
||||||
import { updateWalletStatus } from "./wallet";
|
import { updateWalletStatus } from "./wallet";
|
||||||
import {
|
import {
|
||||||
getProviderUrl,
|
createPublicClient,
|
||||||
isPossibleNetwork,
|
createWalletClient,
|
||||||
possibleChains,
|
custom,
|
||||||
network2Chain,
|
http,
|
||||||
getP2PixAddress,
|
PublicClient,
|
||||||
} from "./addresses";
|
WalletClient,
|
||||||
|
} from "viem";
|
||||||
|
import { useUser } from "@/composables/useUser";
|
||||||
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
|
import type { ChainContract } from "viem";
|
||||||
|
|
||||||
import { ethers } from "ethers";
|
let walletClient: WalletClient | null = null;
|
||||||
|
|
||||||
const getProvider = (
|
const getPublicClient = (): PublicClient => {
|
||||||
onlyAlchemyProvider: boolean = false
|
const user = useUser();
|
||||||
): ethers.providers.Web3Provider | ethers.providers.JsonRpcProvider => {
|
const rpcUrl = (user.network.value as NetworkConfig).rpcUrls.default.http[0];
|
||||||
const window_ = window as any;
|
const chain = user.network.value;
|
||||||
const connection = window_.ethereum;
|
|
||||||
|
|
||||||
if (!connection || onlyAlchemyProvider)
|
return createPublicClient({
|
||||||
return new ethers.providers.JsonRpcProvider(getProviderUrl()); // alchemy provider
|
chain,
|
||||||
|
transport: http(rpcUrl),
|
||||||
return new ethers.providers.Web3Provider(connection); // metamask provider
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getContract = (onlyAlchemyProvider: boolean = false) => {
|
const getWalletClient = (): WalletClient | null => {
|
||||||
const provider = getProvider(onlyAlchemyProvider);
|
return walletClient;
|
||||||
const signer = provider.getSigner();
|
|
||||||
return new ethers.Contract(getP2PixAddress(), p2pix.abi, signer);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectProvider = async (): Promise<void> => {
|
const getContract = async (onlyRpcProvider = false) => {
|
||||||
const window_ = window as any;
|
const client = getPublicClient();
|
||||||
const connection = window_.ethereum;
|
const user = useUser();
|
||||||
const provider = getProvider();
|
const address = (user.network.value.contracts?.p2pix as ChainContract).address;
|
||||||
|
const abi = p2PixAbi;
|
||||||
|
const wallet = onlyRpcProvider ? null : getWalletClient();
|
||||||
|
|
||||||
if (!(provider instanceof ethers.providers.Web3Provider)) {
|
if (!client) {
|
||||||
window.alert("Please, connect to metamask extension");
|
throw new Error("Public client not initialized");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [account] = wallet ? await wallet.getAddresses() : [null];
|
||||||
|
|
||||||
|
return { address, abi, client, wallet, account };
|
||||||
|
};
|
||||||
|
|
||||||
|
const connectProvider = async (p: any): Promise<void> => {
|
||||||
|
const user = useUser();
|
||||||
|
const chain = user.network.value;
|
||||||
|
|
||||||
|
const [account] = await p!.request({ method: "eth_requestAccounts" });
|
||||||
|
|
||||||
|
walletClient = createWalletClient({
|
||||||
|
account,
|
||||||
|
chain,
|
||||||
|
transport: custom(p),
|
||||||
|
});
|
||||||
|
|
||||||
await updateWalletStatus();
|
await updateWalletStatus();
|
||||||
|
|
||||||
listenToNetworkChange(connection);
|
|
||||||
listenToWalletChange(connection);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const listenToWalletChange = (connection: any): void => {
|
export { getPublicClient, getWalletClient, getContract, connectProvider };
|
||||||
connection.on("accountsChanged", async () => {
|
|
||||||
console.log("Changed account!");
|
|
||||||
updateWalletStatus();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const listenToNetworkChange = (connection: any) => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
|
|
||||||
connection.on("chainChanged", (networkChain: string) => {
|
|
||||||
console.log("Changed network!");
|
|
||||||
|
|
||||||
if (isPossibleNetwork(networkChain)) {
|
|
||||||
etherStore.setNetworkName(possibleChains[networkChain]);
|
|
||||||
updateWalletStatus();
|
|
||||||
} else {
|
|
||||||
window.alert("Invalid chain!");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestNetworkChange = async (network: string): Promise<boolean> => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
if (!etherStore.walletAddress) return true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const window_ = window as any;
|
|
||||||
await window_.ethereum.request({
|
|
||||||
method: "wallet_switchEthereumChain",
|
|
||||||
params: [{ chainId: network2Chain[network] }], // chainId must be in hexadecimal numbers
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
getProvider,
|
|
||||||
getContract,
|
|
||||||
connectProvider,
|
|
||||||
listenToNetworkChange,
|
|
||||||
requestNetworkChange,
|
|
||||||
};
|
|
||||||
@@ -1,44 +1,91 @@
|
|||||||
import { getContract, getProvider } from "./provider";
|
import { getContract, getPublicClient, getWalletClient } from "./provider";
|
||||||
import { getTokenAddress, getP2PixAddress } from "./addresses";
|
import { parseEther, toHex, ChainContract } from "viem";
|
||||||
import { parseEther } from "ethers/lib/utils";
|
import { mockTokenAbi } from "./abi";
|
||||||
|
import { useUser } from "@/composables/useUser";
|
||||||
|
import { createParticipant } from "@/utils/bbPay";
|
||||||
|
import type { Participant } from "@/utils/bbPay";
|
||||||
|
import type { Address } from "viem";
|
||||||
|
|
||||||
import { ethers } from "ethers";
|
const getP2PixAddress = (): Address => {
|
||||||
|
const user = useUser();
|
||||||
import mockToken from "../utils/smart_contract_files/MockToken.json";
|
return (user.network.value.contracts?.p2pix as ChainContract).address;
|
||||||
|
|
||||||
const approveTokens = async (tokenQty: string): Promise<any> => {
|
|
||||||
const provider = getProvider();
|
|
||||||
const signer = provider.getSigner();
|
|
||||||
|
|
||||||
const tokenContract = new ethers.Contract(
|
|
||||||
getTokenAddress(),
|
|
||||||
mockToken.abi,
|
|
||||||
signer
|
|
||||||
);
|
|
||||||
|
|
||||||
const apprv = await tokenContract.approve(
|
|
||||||
getP2PixAddress(),
|
|
||||||
parseEther(tokenQty)
|
|
||||||
);
|
|
||||||
|
|
||||||
await apprv.wait();
|
|
||||||
return apprv;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addDeposit = async (tokenQty: string, pixKey: string): Promise<any> => {
|
const approveTokens = async (participant: Participant): Promise<any> => {
|
||||||
const p2pContract = getContract();
|
const user = useUser();
|
||||||
|
const publicClient = getPublicClient();
|
||||||
|
const walletClient = getWalletClient();
|
||||||
|
|
||||||
const deposit = await p2pContract.deposit(
|
if (!publicClient || !walletClient) {
|
||||||
getTokenAddress(),
|
throw new Error("Clients not initialized");
|
||||||
parseEther(tokenQty),
|
}
|
||||||
pixKey,
|
|
||||||
|
user.setSeller(participant);
|
||||||
|
const [account] = await walletClient.getAddresses();
|
||||||
|
|
||||||
|
// Get token address
|
||||||
|
const tokenAddress = user.network.value.tokens[user.selectedToken.value].address;
|
||||||
|
|
||||||
|
// Check if the token is already approved
|
||||||
|
const allowance = await publicClient.readContract({
|
||||||
|
address: tokenAddress,
|
||||||
|
abi: mockTokenAbi,
|
||||||
|
functionName: "allowance",
|
||||||
|
args: [account, getP2PixAddress()],
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( allowance < parseEther(participant.offer.toString()) ) {
|
||||||
|
// Approve tokens
|
||||||
|
const chain = user.network.value;
|
||||||
|
const hash = await walletClient.writeContract({
|
||||||
|
address: tokenAddress,
|
||||||
|
abi: mockTokenAbi,
|
||||||
|
functionName: "approve",
|
||||||
|
args: [getP2PixAddress(), parseEther(participant.offer.toString())],
|
||||||
|
account,
|
||||||
|
chain,
|
||||||
|
});
|
||||||
|
|
||||||
|
await publicClient.waitForTransactionReceipt({ hash });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addDeposit = async (): Promise<any> => {
|
||||||
|
const { address, abi, client } = await getContract();
|
||||||
|
const walletClient = getWalletClient();
|
||||||
|
const user = useUser();
|
||||||
|
|
||||||
|
if (!walletClient) {
|
||||||
|
throw new Error("Wallet client not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [account] = await walletClient.getAddresses();
|
||||||
|
|
||||||
|
const sellerId = await createParticipant(user.seller.value);
|
||||||
|
user.setSellerId(sellerId.id);
|
||||||
|
if (!sellerId.id) {
|
||||||
|
throw new Error("Failed to create participant");
|
||||||
|
}
|
||||||
|
const chain = user.network.value;
|
||||||
|
const hash = await walletClient.writeContract({
|
||||||
|
address,
|
||||||
|
abi,
|
||||||
|
functionName: "deposit",
|
||||||
|
args: [
|
||||||
|
user.network.value.id + "-" + sellerId.id,
|
||||||
|
toHex("", { size: 32 }),
|
||||||
|
user.network.value.tokens[user.selectedToken.value].address,
|
||||||
|
parseEther(user.seller.value.offer.toString()),
|
||||||
true,
|
true,
|
||||||
ethers.utils.formatBytes32String("")
|
],
|
||||||
);
|
account,
|
||||||
|
chain,
|
||||||
|
});
|
||||||
|
|
||||||
await deposit.wait();
|
const receipt = await client.waitForTransactionReceipt({ hash });
|
||||||
|
return receipt;
|
||||||
return deposit;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export { approveTokens, addDeposit };
|
export { approveTokens, addDeposit };
|
||||||
@@ -1,54 +1,46 @@
|
|||||||
import { useEtherStore } from "@/store/ether";
|
import { formatEther, type Address } from "viem";
|
||||||
|
import { useUser } from "@/composables/useUser";
|
||||||
|
|
||||||
import { getContract, getProvider } from "./provider";
|
import { getPublicClient, getWalletClient, getContract } from "./provider";
|
||||||
import { getTokenAddress, possibleChains, isPossibleNetwork } from "./addresses";
|
|
||||||
|
|
||||||
import mockToken from "@/utils/smart_contract_files/MockToken.json";
|
import { getValidDeposits, getUnreleasedLockById } from "./events";
|
||||||
|
|
||||||
import { ethers, type Event, type BigNumber } from "ethers";
|
|
||||||
import { formatEther } from "ethers/lib/utils";
|
|
||||||
import { getValidDeposits } from "./events";
|
|
||||||
|
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
import type { WalletTransaction } from "@/model/WalletTransaction";
|
import type { WalletTransaction } from "@/model/WalletTransaction";
|
||||||
import type { UnreleasedLock } from "@/model/UnreleasedLock";
|
import type { UnreleasedLock } from "@/model/UnreleasedLock";
|
||||||
import type { Pix } from "@/model/Pix";
|
import { LockStatus } from "@/model/LockStatus";
|
||||||
|
|
||||||
const updateWalletStatus = async (): Promise<void> => {
|
export const updateWalletStatus = async (): Promise<void> => {
|
||||||
const etherStore = useEtherStore();
|
const user = useUser();
|
||||||
|
|
||||||
const provider = getProvider();
|
const publicClient = getPublicClient();
|
||||||
const signer = provider.getSigner();
|
const walletClient = getWalletClient();
|
||||||
|
|
||||||
const { chainId } = await provider.getNetwork();
|
if (!publicClient || !walletClient) {
|
||||||
if(!isPossibleNetwork(chainId.toString())){
|
console.error("Client not initialized");
|
||||||
window.alert("Invalid chain!:"+chainId);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
etherStore.setNetworkName(possibleChains[chainId]);
|
|
||||||
|
|
||||||
const mockTokenContract = new ethers.Contract(
|
// Get balance
|
||||||
getTokenAddress(),
|
const [account] = await walletClient.getAddresses();
|
||||||
mockToken.abi,
|
const balance = await publicClient.getBalance({ address: account });
|
||||||
signer
|
|
||||||
);
|
|
||||||
|
|
||||||
const walletAddress = await provider.send("eth_requestAccounts", []);
|
user.setWalletAddress(account);
|
||||||
const balance = await mockTokenContract.balanceOf(walletAddress[0]);
|
user.setBalance(formatEther(balance));
|
||||||
|
|
||||||
etherStore.setBalance(formatEther(balance));
|
|
||||||
etherStore.setWalletAddress(ethers.utils.getAddress(walletAddress[0]));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const listValidDepositTransactionsByWalletAddress = async (
|
export const listValidDepositTransactionsByWalletAddress = async (
|
||||||
walletAddress: string
|
walletAddress: Address
|
||||||
): Promise<ValidDeposit[]> => {
|
): Promise<ValidDeposit[]> => {
|
||||||
const walletDeposits = await getValidDeposits(getTokenAddress());
|
const user = useUser();
|
||||||
|
const walletDeposits = await getValidDeposits(
|
||||||
|
user.network.value.tokens[user.selectedToken.value].address,
|
||||||
|
user.network.value
|
||||||
|
);
|
||||||
if (walletDeposits) {
|
if (walletDeposits) {
|
||||||
return walletDeposits
|
return walletDeposits
|
||||||
.filter((deposit) => deposit.seller == walletAddress)
|
.filter((deposit) => deposit.seller == walletAddress)
|
||||||
.sort((a, b) => {
|
.sort((a: ValidDeposit, b: ValidDeposit) => {
|
||||||
return b.blockNumber - a.blockNumber;
|
return b.blockNumber - a.blockNumber;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -56,191 +48,416 @@ const listValidDepositTransactionsByWalletAddress = async (
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLockStatus = async (id: [BigNumber]): Promise<number> => {
|
const getLockStatus = async (id: bigint): Promise<LockStatus> => {
|
||||||
const p2pContract = getContract();
|
const { address, abi, client } = await getContract();
|
||||||
const res = await p2pContract.getLocksStatus([id]);
|
const [ sortedIDs , status ] = await client.readContract({
|
||||||
|
address,
|
||||||
return res[1][0];
|
abi,
|
||||||
|
functionName: "getLocksStatus",
|
||||||
|
args: [[id]],
|
||||||
|
});
|
||||||
|
return status[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterLockStatus = async (
|
export const listAllTransactionByWalletAddress = async (
|
||||||
transactions: Event[]
|
walletAddress: Address
|
||||||
): Promise<WalletTransaction[]> => {
|
): Promise<WalletTransaction[]> => {
|
||||||
const txs = await Promise.all(
|
const user = useUser();
|
||||||
transactions.map(async (transaction) => {
|
|
||||||
const tx: WalletTransaction = {
|
// Get the current network for the subgraph URL
|
||||||
token: transaction.args?.token ? transaction.args?.token : "",
|
const network = user.network.value;
|
||||||
blockNumber: transaction.blockNumber ? transaction.blockNumber : -1,
|
|
||||||
amount: transaction.args?.amount
|
// Query subgraph for all relevant transactions
|
||||||
? Number(formatEther(transaction.args?.amount))
|
const subgraphQuery = {
|
||||||
: -1,
|
query: `
|
||||||
seller: transaction.args?.seller ? transaction.args?.seller : "",
|
{
|
||||||
buyer: transaction.args?.buyer ? transaction.args?.buyer : "",
|
depositAddeds(where: {seller: "${walletAddress.toLowerCase()}"}) {
|
||||||
event: transaction.event ? transaction.event : "",
|
id
|
||||||
lockStatus:
|
seller
|
||||||
transaction.event == "LockAdded"
|
token
|
||||||
? await getLockStatus(transaction.args?.lockID)
|
amount
|
||||||
: -1,
|
blockTimestamp
|
||||||
transactionHash: transaction.transactionHash
|
blockNumber
|
||||||
? transaction.transactionHash
|
transactionHash
|
||||||
: "",
|
}
|
||||||
transactionID: transaction.args?.lockID
|
lockAddeds(where: {buyer: "${walletAddress.toLowerCase()}"}) {
|
||||||
? String(transaction.args?.lockID)
|
buyer
|
||||||
: "",
|
lockID
|
||||||
|
seller
|
||||||
|
amount
|
||||||
|
blockTimestamp
|
||||||
|
blockNumber
|
||||||
|
transactionHash
|
||||||
|
}
|
||||||
|
lockReleaseds(where: {buyer: "${walletAddress.toLowerCase()}"}) {
|
||||||
|
buyer
|
||||||
|
lockId
|
||||||
|
blockTimestamp
|
||||||
|
blockNumber
|
||||||
|
transactionHash
|
||||||
|
}
|
||||||
|
depositWithdrawns(where: {seller: "${walletAddress.toLowerCase()}"}) {
|
||||||
|
seller
|
||||||
|
token
|
||||||
|
amount
|
||||||
|
blockTimestamp
|
||||||
|
blockNumber
|
||||||
|
transactionHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return tx;
|
const response = await fetch(network.subgraphUrls[0], {
|
||||||
})
|
method: "POST",
|
||||||
);
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(subgraphQuery),
|
||||||
|
});
|
||||||
|
|
||||||
return txs;
|
const data = await response.json();
|
||||||
};
|
// Convert all transactions to common WalletTransaction format
|
||||||
|
const transactions: WalletTransaction[] = [];
|
||||||
|
|
||||||
const listAllTransactionByWalletAddress = async (
|
// Process deposit added events
|
||||||
walletAddress: string
|
if (data.data?.depositAddeds) {
|
||||||
): Promise<WalletTransaction[]> => {
|
for (const deposit of data.data.depositAddeds) {
|
||||||
const p2pContract = getContract(true);
|
transactions.push({
|
||||||
|
token: deposit.token,
|
||||||
|
blockNumber: parseInt(deposit.blockNumber),
|
||||||
|
amount: parseFloat(formatEther(BigInt(deposit.amount))),
|
||||||
|
seller: deposit.seller,
|
||||||
|
buyer: "",
|
||||||
|
event: "DepositAdded",
|
||||||
|
lockStatus: undefined,
|
||||||
|
transactionHash: deposit.transactionHash,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filterDeposits = p2pContract.filters.DepositAdded([walletAddress]);
|
// Process lock added events
|
||||||
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
|
if (data.data?.lockAddeds) {
|
||||||
|
for (const lock of data.data.lockAddeds) {
|
||||||
|
// Get lock status from the contract
|
||||||
|
const lockStatus = await getLockStatus(BigInt(lock.lockID));
|
||||||
|
|
||||||
const filterAddedLocks = p2pContract.filters.LockAdded([walletAddress]);
|
transactions.push({
|
||||||
const eventsAddedLocks = await p2pContract.queryFilter(filterAddedLocks);
|
token: lock.token,
|
||||||
|
blockNumber: parseInt(lock.blockNumber),
|
||||||
|
amount: parseFloat(formatEther(BigInt(lock.amount))),
|
||||||
|
seller: lock.seller,
|
||||||
|
buyer: lock.buyer,
|
||||||
|
event: "LockAdded",
|
||||||
|
lockStatus: lockStatus,
|
||||||
|
transactionHash: lock.transactionHash,
|
||||||
|
transactionID: lock.lockID.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]);
|
// Process lock released events
|
||||||
const eventsReleasedLocks = await p2pContract.queryFilter(
|
if (data.data?.lockReleaseds) {
|
||||||
filterReleasedLocks
|
for (const release of data.data.lockReleaseds) {
|
||||||
);
|
transactions.push({
|
||||||
|
token: undefined, // Subgraph doesn't provide token in this event, we could enhance this later
|
||||||
|
blockNumber: parseInt(release.blockNumber),
|
||||||
|
amount: -1, // Amount not available in this event
|
||||||
|
seller: "",
|
||||||
|
buyer: release.buyer,
|
||||||
|
event: "LockReleased",
|
||||||
|
lockStatus: undefined,
|
||||||
|
transactionHash: release.transactionHash,
|
||||||
|
transactionID: release.lockId.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filterWithdrawnDeposits = p2pContract.filters.DepositWithdrawn([
|
// Process deposit withdrawn events
|
||||||
walletAddress,
|
if (data.data?.depositWithdrawns) {
|
||||||
]);
|
for (const withdrawal of data.data.depositWithdrawns) {
|
||||||
const eventsWithdrawnDeposits = await p2pContract.queryFilter(
|
transactions.push({
|
||||||
filterWithdrawnDeposits
|
token: withdrawal.token,
|
||||||
);
|
blockNumber: parseInt(withdrawal.blockNumber),
|
||||||
|
amount: parseFloat(formatEther(BigInt(withdrawal.amount))),
|
||||||
|
seller: withdrawal.seller,
|
||||||
|
buyer: "",
|
||||||
|
event: "DepositWithdrawn",
|
||||||
|
lockStatus: undefined,
|
||||||
|
transactionHash: withdrawal.transactionHash,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const lockStatusFiltered = await filterLockStatus(
|
// Sort transactions by block number (newest first)
|
||||||
[
|
return transactions.sort((a, b) => b.blockNumber - a.blockNumber);
|
||||||
...eventsDeposits,
|
|
||||||
...eventsAddedLocks,
|
|
||||||
...eventsReleasedLocks,
|
|
||||||
...eventsWithdrawnDeposits,
|
|
||||||
].sort((a, b) => {
|
|
||||||
return b.blockNumber - a.blockNumber;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return lockStatusFiltered;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// get wallet's release transactions
|
// get wallet's release transactions
|
||||||
const listReleaseTransactionByWalletAddress = async (
|
export const listReleaseTransactionByWalletAddress = async (
|
||||||
walletAddress: string
|
walletAddress: Address
|
||||||
): Promise<Event[]> => {
|
) => {
|
||||||
const p2pContract = getContract(true);
|
const user = useUser();
|
||||||
|
const network = user.network.value;
|
||||||
|
|
||||||
const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]);
|
// Query subgraph for release transactions
|
||||||
const eventsReleasedLocks = await p2pContract.queryFilter(
|
const subgraphQuery = {
|
||||||
filterReleasedLocks
|
query: `
|
||||||
);
|
{
|
||||||
|
lockReleaseds(where: {buyer: "${walletAddress.toLowerCase()}"}) {
|
||||||
return eventsReleasedLocks.sort((a, b) => {
|
buyer
|
||||||
return b.blockNumber - a.blockNumber;
|
lockId
|
||||||
});
|
e2eId
|
||||||
};
|
blockTimestamp
|
||||||
|
blockNumber
|
||||||
const listLockTransactionByWalletAddress = async (
|
transactionHash
|
||||||
walletAddress: string
|
}
|
||||||
): Promise<Event[]> => {
|
}
|
||||||
const p2pContract = getContract(true);
|
`,
|
||||||
|
|
||||||
const filterAddedLocks = p2pContract.filters.LockAdded([walletAddress]);
|
|
||||||
const eventsReleasedLocks = await p2pContract.queryFilter(filterAddedLocks);
|
|
||||||
|
|
||||||
return eventsReleasedLocks.sort((a, b) => {
|
|
||||||
return b.blockNumber - a.blockNumber;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const listLockTransactionBySellerAddress = async (
|
|
||||||
sellerAddress: string
|
|
||||||
): Promise<Event[]> => {
|
|
||||||
const p2pContract = getContract(true);
|
|
||||||
|
|
||||||
const filterAddedLocks = p2pContract.filters.LockAdded();
|
|
||||||
const eventsReleasedLocks = await p2pContract.queryFilter(filterAddedLocks);
|
|
||||||
|
|
||||||
return eventsReleasedLocks.filter((lock) =>
|
|
||||||
lock.args?.seller
|
|
||||||
.toHexString()
|
|
||||||
.substring(3)
|
|
||||||
.includes(sellerAddress.substring(2).toLowerCase())
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkUnreleasedLock = async (
|
|
||||||
walletAddress: string
|
|
||||||
): Promise<UnreleasedLock | undefined> => {
|
|
||||||
const p2pContract = getContract();
|
|
||||||
const pixData: Pix = {
|
|
||||||
pixKey: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addedLocks = await listLockTransactionByWalletAddress(walletAddress);
|
// Fetch data from subgraph
|
||||||
const lockStatus = await p2pContract.getLocksStatus(
|
const response = await fetch(network.subgraphUrls[0], {
|
||||||
addedLocks.map((lock) => lock.args?.lockID)
|
method: "POST",
|
||||||
);
|
headers: {
|
||||||
const unreleasedLockId = lockStatus[1].findIndex(
|
"Content-Type": "application/json",
|
||||||
(lockStatus: number) => lockStatus == 1
|
},
|
||||||
);
|
body: JSON.stringify(subgraphQuery),
|
||||||
|
});
|
||||||
|
|
||||||
if (unreleasedLockId != -1) {
|
const data = await response.json();
|
||||||
const _lockID = lockStatus[0][unreleasedLockId];
|
|
||||||
const lock = await p2pContract.mapLocks(_lockID);
|
|
||||||
|
|
||||||
const pixTarget = lock.pixTarget;
|
// Process the subgraph response into the same format as the previous implementation
|
||||||
const amount = formatEther(lock?.amount);
|
if (!data.data?.lockReleaseds) {
|
||||||
pixData.pixKey = String(Number(pixTarget));
|
return [];
|
||||||
pixData.value = Number(amount);
|
}
|
||||||
|
|
||||||
|
// Transform the subgraph data to match the event log decode format
|
||||||
|
return data.data.lockReleaseds
|
||||||
|
.sort((a: any, b: any) => {
|
||||||
|
return parseInt(b.blockNumber) - parseInt(a.blockNumber);
|
||||||
|
})
|
||||||
|
.map((release: any) => {
|
||||||
|
try {
|
||||||
|
// Create a structure similar to the decoded event log
|
||||||
return {
|
return {
|
||||||
lockID: _lockID,
|
eventName: "LockReleased",
|
||||||
pix: pixData,
|
args: {
|
||||||
|
buyer: release.buyer,
|
||||||
|
lockID: BigInt(release.lockId),
|
||||||
|
e2eId: release.e2eId,
|
||||||
|
},
|
||||||
|
// Add any other necessary fields to match the original return format
|
||||||
|
blockNumber: BigInt(release.blockNumber),
|
||||||
|
transactionHash: release.transactionHash,
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing subgraph data", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((decoded: any) => decoded !== null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listLockTransactionByWalletAddress = async (walletAddress: Address) => {
|
||||||
|
const user = useUser();
|
||||||
|
const network = user.network.value;
|
||||||
|
|
||||||
|
// Query subgraph for lock added transactions
|
||||||
|
const subgraphQuery = {
|
||||||
|
query: `
|
||||||
|
{
|
||||||
|
lockAddeds(where: {buyer: "${walletAddress.toLowerCase()}"}) {
|
||||||
|
buyer
|
||||||
|
lockID
|
||||||
|
seller
|
||||||
|
amount
|
||||||
|
blockTimestamp
|
||||||
|
blockNumber
|
||||||
|
transactionHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch data from subgraph
|
||||||
|
const response = await fetch(network.subgraphUrls[0], {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(subgraphQuery),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.data?.lockAddeds) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform the subgraph data to match the event log decode format
|
||||||
|
return data.data.lockAddeds
|
||||||
|
.sort((a: any, b: any) => {
|
||||||
|
return parseInt(b.blockNumber) - parseInt(a.blockNumber);
|
||||||
|
})
|
||||||
|
.map((lock: any) => {
|
||||||
|
try {
|
||||||
|
// Create a structure similar to the decoded event log
|
||||||
|
return {
|
||||||
|
eventName: "LockAdded",
|
||||||
|
args: {
|
||||||
|
buyer: lock.buyer,
|
||||||
|
lockID: BigInt(lock.lockID),
|
||||||
|
seller: lock.seller,
|
||||||
|
token: lock.token,
|
||||||
|
amount: BigInt(lock.amount),
|
||||||
|
},
|
||||||
|
// Add other necessary fields to match the original format
|
||||||
|
blockNumber: BigInt(lock.blockNumber),
|
||||||
|
transactionHash: lock.transactionHash,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing subgraph data", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((decoded: any) => decoded !== null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching from subgraph:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getActiveLockAmount = async (walletAddress: string): Promise<number> => {
|
const listLockTransactionBySellerAddress = async (sellerAddress: Address) => {
|
||||||
const p2pContract = getContract();
|
const user = useUser();
|
||||||
|
const network = user.network.value;
|
||||||
|
|
||||||
|
// Query subgraph for lock added transactions where seller matches
|
||||||
|
const subgraphQuery = {
|
||||||
|
query: `
|
||||||
|
{
|
||||||
|
lockAddeds(where: {seller: "${sellerAddress.toLowerCase()}"}) {
|
||||||
|
buyer
|
||||||
|
lockID
|
||||||
|
seller
|
||||||
|
token
|
||||||
|
amount
|
||||||
|
blockTimestamp
|
||||||
|
blockNumber
|
||||||
|
transactionHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch data from subgraph
|
||||||
|
const response = await fetch(network.subgraphUrls[0], {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(subgraphQuery),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.data?.lockAddeds) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform the subgraph data to match the event log decode format
|
||||||
|
return data.data.lockAddeds
|
||||||
|
.sort((a: any, b: any) => {
|
||||||
|
return parseInt(b.blockNumber) - parseInt(a.blockNumber);
|
||||||
|
})
|
||||||
|
.map((lock: any) => {
|
||||||
|
try {
|
||||||
|
// Create a structure similar to the decoded event log
|
||||||
|
return {
|
||||||
|
eventName: "LockAdded",
|
||||||
|
args: {
|
||||||
|
buyer: lock.buyer,
|
||||||
|
lockID: BigInt(lock.lockID),
|
||||||
|
seller: lock.seller,
|
||||||
|
token: lock.token,
|
||||||
|
amount: BigInt(lock.amount),
|
||||||
|
},
|
||||||
|
// Add other necessary fields to match the original format
|
||||||
|
blockNumber: BigInt(lock.blockNumber),
|
||||||
|
transactionHash: lock.transactionHash,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing subgraph data", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((decoded: any) => decoded !== null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching from subgraph:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkUnreleasedLock = async (
|
||||||
|
walletAddress: Address
|
||||||
|
): Promise<UnreleasedLock | undefined> => {
|
||||||
|
const { address, abi, client } = await getContract();
|
||||||
|
const addedLocks = await listLockTransactionByWalletAddress(walletAddress);
|
||||||
|
|
||||||
|
if (!addedLocks.length) return undefined;
|
||||||
|
|
||||||
|
const lockIds = addedLocks.map((lock: any) => lock.args.lockID);
|
||||||
|
|
||||||
|
const [ sortedIDs, status ] = await client.readContract({
|
||||||
|
address,
|
||||||
|
abi,
|
||||||
|
functionName: "getLocksStatus",
|
||||||
|
args: [lockIds],
|
||||||
|
});
|
||||||
|
|
||||||
|
const unreleasedLockId = status.findIndex(
|
||||||
|
(status: LockStatus) => status == LockStatus.Active
|
||||||
|
);
|
||||||
|
|
||||||
|
if (unreleasedLockId !== -1)
|
||||||
|
return getUnreleasedLockById(sortedIDs[unreleasedLockId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActiveLockAmount = async (
|
||||||
|
walletAddress: Address
|
||||||
|
): Promise<number> => {
|
||||||
|
const { address, abi, client } = await getContract(true);
|
||||||
const lockSeller = await listLockTransactionBySellerAddress(walletAddress);
|
const lockSeller = await listLockTransactionBySellerAddress(walletAddress);
|
||||||
|
|
||||||
const lockStatus = await p2pContract.getLocksStatus(
|
if (!lockSeller.length) return 0;
|
||||||
lockSeller.map((lock) => lock.args?.lockID)
|
|
||||||
|
const lockIds = lockSeller.map((lock: any) => lock.args.lockID);
|
||||||
|
|
||||||
|
const [ sortedIDs, status ] = await client.readContract({
|
||||||
|
address,
|
||||||
|
abi,
|
||||||
|
functionName: "getLocksStatus",
|
||||||
|
args: [lockIds],
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapLocksRequests = status.map((id: LockStatus) =>
|
||||||
|
client.readContract({
|
||||||
|
address: address,
|
||||||
|
abi,
|
||||||
|
functionName: "mapLocks",
|
||||||
|
args: [BigInt(id)],
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeLockAmount = await lockStatus[1].reduce(
|
const mapLocksResults = await client.multicall({
|
||||||
async (sumValue: Promise<number>, currentStatus: number, index: number) => {
|
contracts: mapLocksRequests as any,
|
||||||
const currValue = await sumValue;
|
});
|
||||||
let valueToSum = 0;
|
|
||||||
|
|
||||||
if (currentStatus == 1) {
|
return mapLocksResults.reduce((total: number, lock: any, index: number) => {
|
||||||
const lock = await p2pContract.mapLocks(lockStatus[0][index]);
|
if (status[index] === 1) {
|
||||||
valueToSum = Number(formatEther(lock?.amount));
|
return total + Number(formatEther(lock.amount));
|
||||||
}
|
}
|
||||||
|
return total;
|
||||||
return currValue + valueToSum;
|
}, 0);
|
||||||
},
|
|
||||||
Promise.resolve(0)
|
|
||||||
);
|
|
||||||
|
|
||||||
return activeLockAmount;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
updateWalletStatus,
|
|
||||||
listValidDepositTransactionsByWalletAddress,
|
|
||||||
listAllTransactionByWalletAddress,
|
|
||||||
listReleaseTransactionByWalletAddress,
|
|
||||||
checkUnreleasedLock,
|
|
||||||
getActiveLockAmount,
|
|
||||||
};
|
};
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { mount } from "@vue/test-utils";
|
|
||||||
import BuyConfirmedComponent from "../BuyConfirmedComponent.vue";
|
|
||||||
import { createPinia, setActivePinia } from "pinia";
|
|
||||||
|
|
||||||
describe("BuyConfirmedComponent.vue", async () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
setActivePinia(createPinia());
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapper = mount(BuyConfirmedComponent, {
|
|
||||||
props: {
|
|
||||||
tokenAmount: 1,
|
|
||||||
isCurrentStep: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// test("Test component Header Text", () => {
|
|
||||||
// expect(wrapper.html()).toContain("Os tokens já foram transferidos");
|
|
||||||
// expect(wrapper.html()).toContain("para a sua carteira!");
|
|
||||||
// });
|
|
||||||
|
|
||||||
// test("Test component Container Text", () => {
|
|
||||||
// expect(wrapper.html()).toContain("Tokens recebidos");
|
|
||||||
// expect(wrapper.html()).toContain("BRZ");
|
|
||||||
// expect(wrapper.html()).toContain("Não encontrou os tokens?");
|
|
||||||
// expect(wrapper.html()).toContain("Clique no botão abaixo para");
|
|
||||||
// expect(wrapper.html()).toContain("cadastrar o BRZ em sua carteira.");
|
|
||||||
// });
|
|
||||||
|
|
||||||
test("Test makeAnotherTransactionEmit", async () => {
|
|
||||||
wrapper.vm.$emit("makeAnotherTransaction");
|
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.emitted("makeAnotherTransaction")).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -5,13 +5,12 @@ import {
|
|||||||
listAllTransactionByWalletAddress,
|
listAllTransactionByWalletAddress,
|
||||||
listValidDepositTransactionsByWalletAddress,
|
listValidDepositTransactionsByWalletAddress,
|
||||||
} from "@/blockchain/wallet";
|
} from "@/blockchain/wallet";
|
||||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
import CustomButton from "@/components/ui/CustomButton.vue";
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
import type { WalletTransaction } from "@/model/WalletTransaction";
|
import type { WalletTransaction } from "@/model/WalletTransaction";
|
||||||
import { useEtherStore } from "@/store/ether";
|
import { useUser } from "@/composables/useUser";
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { onMounted, ref, watch } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import ListingComponent from "../ListingComponent/ListingComponent.vue";
|
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
||||||
|
|
||||||
// props
|
// props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -19,8 +18,8 @@ const props = defineProps<{
|
|||||||
isCurrentStep: boolean;
|
isCurrentStep: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const etherStore = useEtherStore();
|
const user = useUser();
|
||||||
const { walletAddress } = storeToRefs(etherStore);
|
const { walletAddress } = useUser();
|
||||||
|
|
||||||
const lastWalletTransactions = ref<WalletTransaction[]>([]);
|
const lastWalletTransactions = ref<WalletTransaction[]>([]);
|
||||||
const depositList = ref<ValidDeposit[]>([]);
|
const depositList = ref<ValidDeposit[]>([]);
|
||||||
@@ -29,7 +28,7 @@ const activeLockAmount = ref<number>(0);
|
|||||||
// methods
|
// methods
|
||||||
|
|
||||||
const getWalletTransactions = async () => {
|
const getWalletTransactions = async () => {
|
||||||
etherStore.setLoadingWalletTransactions(true);
|
user.setLoadingWalletTransactions(true);
|
||||||
if (walletAddress.value) {
|
if (walletAddress.value) {
|
||||||
const walletDeposits = await listValidDepositTransactionsByWalletAddress(
|
const walletDeposits = await listValidDepositTransactionsByWalletAddress(
|
||||||
walletAddress.value
|
walletAddress.value
|
||||||
@@ -48,20 +47,20 @@ const getWalletTransactions = async () => {
|
|||||||
lastWalletTransactions.value = allUserTransactions;
|
lastWalletTransactions.value = allUserTransactions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
etherStore.setLoadingWalletTransactions(false);
|
user.setLoadingWalletTransactions(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const callWithdraw = async (amount: string) => {
|
const callWithdraw = async (amount: string) => {
|
||||||
if (amount) {
|
if (amount) {
|
||||||
etherStore.setLoadingWalletTransactions(true);
|
user.setLoadingWalletTransactions(true);
|
||||||
const withdraw = await withdrawDeposit(amount);
|
const withdraw = await withdrawDeposit(amount, user.network.value.tokens[user.selectedToken.value].address);
|
||||||
if (withdraw) {
|
if (withdraw) {
|
||||||
console.log("Saque realizado!");
|
console.log("Saque realizado!");
|
||||||
await getWalletTransactions();
|
await getWalletTransactions();
|
||||||
} else {
|
} else {
|
||||||
console.log("Não foi possível realizar o saque!");
|
console.log("Não foi possível realizar o saque!");
|
||||||
}
|
}
|
||||||
etherStore.setLoadingWalletTransactions(false);
|
user.setLoadingWalletTransactions(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,19 +85,21 @@ onMounted(async () => {
|
|||||||
para a sua carteira!
|
para a sua carteira!
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="blur-container">
|
<div class="main-container">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p>Tokens recebidos</p>
|
<p>Tokens recebidos</p>
|
||||||
<p class="text-2xl text-gray-900">{{ props.tokenAmount }} BRZ</p>
|
<p class="text-2xl text-gray-900">
|
||||||
|
{{ props.tokenAmount }} {{ user.selectedToken }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-5">
|
<div class="my-5">
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
<b>Não encontrou os tokens? </b><br />Clique no botão abaixo para
|
<b>Não encontrou os tokens? </b><br />Clique no botão abaixo para
|
||||||
<br />
|
<br />
|
||||||
cadastrar o BRZ em sua carteira.
|
cadastrar o {{ user.selectedToken }} em sua carteira.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<CustomButton :text="'Cadastrar token'" @buttonClicked="() => {}" />
|
<CustomButton :text="'Cadastrar token'" @buttonClicked="() => {}" />
|
||||||
@@ -143,42 +144,13 @@ p {
|
|||||||
.text {
|
.text {
|
||||||
@apply text-white text-center;
|
@apply text-white text-center;
|
||||||
}
|
}
|
||||||
.blur-container-row {
|
|
||||||
@apply flex flex-row justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blur-container {
|
|
||||||
@apply flex w-full max-w-xs md:max-w-lg flex-col justify-center items-center px-8 py-6 gap-4 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-release-info {
|
.last-release-info {
|
||||||
@apply font-medium text-base text-gray-900;
|
@apply font-medium text-base text-gray-900;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lg-view {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm-view {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
|
||||||
.lg-view {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm-view {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from "vue";
|
||||||
|
import { useUser } from "@/composables/useUser";
|
||||||
|
import SpinnerComponent from "@/components/ui/SpinnerComponent.vue";
|
||||||
|
import CustomButton from "@/components/ui/CustomButton.vue";
|
||||||
|
import { debounce } from "@/utils/debounce";
|
||||||
|
import { verifyNetworkLiquidity } from "@/utils/networkLiquidity";
|
||||||
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
|
import { decimalCount } from "@/utils/decimalCount";
|
||||||
|
import { getTokenImage } from "@/utils/imagesPath";
|
||||||
|
import { onClickOutside } from "@vueuse/core";
|
||||||
|
import { Networks } from "@/config/networks";
|
||||||
|
import { TokenEnum } from "@/model/NetworkEnum";
|
||||||
|
|
||||||
|
// Store reference
|
||||||
|
const user = useUser();
|
||||||
|
const selectTokenToggle = ref<boolean>(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
walletAddress,
|
||||||
|
network,
|
||||||
|
selectedToken,
|
||||||
|
depositsValidList,
|
||||||
|
loadingNetworkLiquidity,
|
||||||
|
} = user;
|
||||||
|
|
||||||
|
// html references
|
||||||
|
const tokenDropdownRef = ref<any>(null);
|
||||||
|
|
||||||
|
// Reactive state
|
||||||
|
const tokenValue = ref<number>(0);
|
||||||
|
const enableConfirmButton = ref<boolean>(false);
|
||||||
|
const hasLiquidity = ref<boolean>(true);
|
||||||
|
const validDecimals = ref<boolean>(true);
|
||||||
|
const identification = ref<string>("");
|
||||||
|
const selectedDeposits = ref<ValidDeposit[]>();
|
||||||
|
|
||||||
|
import ChevronDown from "@/assets/chevronDown.svg";
|
||||||
|
import { useOnboard } from "@web3-onboard/vue";
|
||||||
|
import { getParticipantID } from "@/blockchain/events";
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits(["tokenBuy"]);
|
||||||
|
|
||||||
|
// Blockchain methods
|
||||||
|
const connectAccount = async (): Promise<void> => {
|
||||||
|
const { connectWallet } = useOnboard();
|
||||||
|
await connectWallet();
|
||||||
|
};
|
||||||
|
|
||||||
|
const emitConfirmButton = async (): Promise<void> => {
|
||||||
|
const deposit = selectedDeposits.value?.find(
|
||||||
|
(d) => d.network === network.value
|
||||||
|
);
|
||||||
|
if (!deposit) return;
|
||||||
|
deposit.participantID = await getParticipantID(deposit.seller, deposit.token);
|
||||||
|
emit("tokenBuy", deposit, tokenValue.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Debounce methods
|
||||||
|
const handleInputEvent = (event: any): void => {
|
||||||
|
const { value } = event.target;
|
||||||
|
|
||||||
|
tokenValue.value = Number(value);
|
||||||
|
|
||||||
|
if (decimalCount(String(tokenValue.value)) > 2) {
|
||||||
|
validDecimals.value = false;
|
||||||
|
enableConfirmButton.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
validDecimals.value = true;
|
||||||
|
|
||||||
|
verifyLiquidity();
|
||||||
|
};
|
||||||
|
|
||||||
|
const openTokenSelection = (): void => {
|
||||||
|
selectTokenToggle.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
onClickOutside(tokenDropdownRef, () => {
|
||||||
|
selectTokenToggle.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelectedToken = (token: TokenEnum): void => {
|
||||||
|
user.setSelectedToken(token);
|
||||||
|
selectTokenToggle.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verify if there is a valid deposit to buy
|
||||||
|
const verifyLiquidity = (): void => {
|
||||||
|
enableConfirmButton.value = false;
|
||||||
|
if (!walletAddress.value)
|
||||||
|
return;
|
||||||
|
const selDeposits = verifyNetworkLiquidity(
|
||||||
|
tokenValue.value,
|
||||||
|
walletAddress.value,
|
||||||
|
depositsValidList.value
|
||||||
|
);
|
||||||
|
selectedDeposits.value = selDeposits;
|
||||||
|
hasLiquidity.value = !!selDeposits.find(
|
||||||
|
(d) => d.network === network.value
|
||||||
|
);
|
||||||
|
enableOrDisableConfirmButton();
|
||||||
|
};
|
||||||
|
|
||||||
|
const enableOrDisableConfirmButton = (): void => {
|
||||||
|
if (!selectedDeposits.value) {
|
||||||
|
enableConfirmButton.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedDeposits.value.find((d) => d.network === network.value)) {
|
||||||
|
enableConfirmButton.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
enableConfirmButton.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(network, (): void => {
|
||||||
|
verifyLiquidity();
|
||||||
|
enableOrDisableConfirmButton();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(walletAddress, (): void => {
|
||||||
|
verifyLiquidity();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add form submission handler
|
||||||
|
const handleSubmit = async (e: Event): Promise<void> => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (walletAddress.value) {
|
||||||
|
await emitConfirmButton();
|
||||||
|
} else {
|
||||||
|
await connectAccount();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="page">
|
||||||
|
<div class="text-container">
|
||||||
|
<span
|
||||||
|
class="text font-extrabold sm:text-5xl text-3xl sm:max-w-[29rem] max-w-[20rem]"
|
||||||
|
>
|
||||||
|
Adquira cripto com apenas um Pix</span
|
||||||
|
>
|
||||||
|
<span class="text font-medium sm:text-base text-sm max-w-[28rem]"
|
||||||
|
>Digite um valor, confira a oferta, conecte sua carteira e receba os
|
||||||
|
tokens após realizar o Pix</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<form class="main-container" @submit="handleSubmit">
|
||||||
|
<div class="backdrop-blur -z-10 w-full h-full"></div>
|
||||||
|
<div
|
||||||
|
class="flex flex-col w-full bg-white sm:px-10 px-6 py-5 rounded-lg border-y-10"
|
||||||
|
>
|
||||||
|
<div class="flex justify-between sm:w-full items-center">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="tokenAmount"
|
||||||
|
class="border-none outline-none text-lg text-gray-900"
|
||||||
|
v-bind:class="{
|
||||||
|
'font-semibold': tokenValue != undefined,
|
||||||
|
'text-xl': tokenValue != undefined,
|
||||||
|
}"
|
||||||
|
@input="debounce(handleInputEvent, 500)($event)"
|
||||||
|
placeholder="0"
|
||||||
|
step=".01"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="relative overflow-visible">
|
||||||
|
<button
|
||||||
|
ref="tokenDropdownRef"
|
||||||
|
class="flex flex-row items-center p-2 bg-gray-300 hover:bg-gray-200 focus:outline-indigo-800 focus:outline-2 rounded-3xl min-w-fit gap-2 transition-colors"
|
||||||
|
@click="openTokenSelection()"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Token image"
|
||||||
|
class="sm:w-fit w-4"
|
||||||
|
:src="getTokenImage(selectedToken)"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="text-gray-900 sm:text-lg text-md font-medium"
|
||||||
|
id="token"
|
||||||
|
>{{ selectedToken }}</span
|
||||||
|
>
|
||||||
|
<ChevronDown
|
||||||
|
class="pr-4 sm:pr-0 transition-all duration-500 ease-in-out invert"
|
||||||
|
:class="{ 'scale-y-[-1]': selectTokenToggle }"
|
||||||
|
alt="Chevron Down"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<transition name="dropdown">
|
||||||
|
<div
|
||||||
|
v-if="selectTokenToggle"
|
||||||
|
class="mt-2 text-gray-900 absolute right-0 z-50 w-full min-w-max"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-white rounded-xl z-10 border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="token in TokenEnum"
|
||||||
|
:key="token"
|
||||||
|
class="flex menu-button gap-2 px-4 cursor-pointer hover:bg-gray-300 transition-colors"
|
||||||
|
@click="handleSelectedToken(token)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:alt="token + ' logo'"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
:src="getTokenImage(token)"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="text-gray-900 py-4 text-end font-semibold text-sm"
|
||||||
|
>
|
||||||
|
{{ token }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full flex justify-center">
|
||||||
|
<hr class="w-4/5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="custom-divide py-2 mb-2"></div>
|
||||||
|
<div class="flex justify-between" v-if="!loadingNetworkLiquidity">
|
||||||
|
<p class="text-gray-500 font-normal text-sm w-auto">
|
||||||
|
~ R$ {{ tokenValue.toFixed(2) }}
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<img
|
||||||
|
alt="Rootstock image"
|
||||||
|
src="@/assets/rootstock.svg?url"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
v-if="
|
||||||
|
selectedDeposits &&
|
||||||
|
selectedDeposits.find((d) => d.network == Networks.rootstockTestnet)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
alt="Ethereum image"
|
||||||
|
src="@/assets/ethereum.svg?url"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
v-if="
|
||||||
|
selectedDeposits &&
|
||||||
|
selectedDeposits.find((d) => d.network == Networks.sepolia)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex justify-center items-center"
|
||||||
|
v-if="loadingNetworkLiquidity"
|
||||||
|
>
|
||||||
|
<span class="text-gray-900 font-normal text-sm mr-2"
|
||||||
|
>Carregando liquidez das redes.</span
|
||||||
|
>
|
||||||
|
<SpinnerComponent width="4" height="4"></SpinnerComponent>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex justify-center"
|
||||||
|
v-if="!validDecimals && !loadingNetworkLiquidity"
|
||||||
|
>
|
||||||
|
<span class="text-red-500 font-normal text-sm"
|
||||||
|
>Por favor utilize no máximo 2 casas decimais</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex justify-center"
|
||||||
|
v-else-if="
|
||||||
|
!hasLiquidity && !loadingNetworkLiquidity && tokenValue > 0
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span class="text-red-500 font-normal text-sm"
|
||||||
|
>Atualmente não há liquidez nas rede selecionada para sua
|
||||||
|
demanda</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="identification"
|
||||||
|
maxlength="14"
|
||||||
|
:pattern="'^\\d{11}$|^\\d{14}$'"
|
||||||
|
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||||
|
placeholder="Digite seu CPF ou CNPJ (somente números)"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action buttons -->
|
||||||
|
<CustomButton
|
||||||
|
v-if="walletAddress"
|
||||||
|
type="submit"
|
||||||
|
text="Confirmar Oferta"
|
||||||
|
/>
|
||||||
|
<CustomButton
|
||||||
|
v-else
|
||||||
|
text="Conectar carteira"
|
||||||
|
@buttonClicked="connectAccount()"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.custom-divide {
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
.bottom-position {
|
||||||
|
top: -20px;
|
||||||
|
right: 50%;
|
||||||
|
transform: translateX(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
@apply flex flex-col items-center justify-center w-full mt-16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
@apply flex flex-col items-center justify-center gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
@apply text-white text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-button {
|
||||||
|
@apply w-full py-3 px-6 rounded-lg font-semibold text-white bg-indigo-600
|
||||||
|
hover:bg-indigo-700 disabled:bg-gray-400 disabled:cursor-not-allowed
|
||||||
|
transition-colors duration-200;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
|
import CustomButton from "@/components/ui/CustomButton.vue";
|
||||||
|
import CustomModal from "@/components/ui/CustomModal.vue";
|
||||||
|
import SpinnerComponent from "@/components/ui/SpinnerComponent.vue";
|
||||||
|
import { createSolicitation, getSolicitation, type Offer } from "@/utils/bbPay";
|
||||||
|
import { getParticipantID } from "@/blockchain/events";
|
||||||
|
import { getUnreleasedLockById } from "@/blockchain/events";
|
||||||
|
import QRCode from "qrcode";
|
||||||
|
|
||||||
|
// Props
|
||||||
|
interface Props {
|
||||||
|
lockID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const qrCode = ref<string>("");
|
||||||
|
const qrCodeSvg = ref<string>("");
|
||||||
|
const showWarnModal = ref<boolean>(true);
|
||||||
|
const pixTimestamp = ref<string>("");
|
||||||
|
const releaseSignature = ref<string>("");
|
||||||
|
const solicitationData = ref<any>(null);
|
||||||
|
const pollingInterval = ref<NodeJS.Timeout | null>(null);
|
||||||
|
const copyFeedback = ref<boolean>(false);
|
||||||
|
const copyFeedbackTimeout = ref<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
|
// Function to generate QR code SVG
|
||||||
|
const generateQrCodeSvg = async (text: string) => {
|
||||||
|
try {
|
||||||
|
const svgString = await QRCode.toString(text, {
|
||||||
|
type: "svg",
|
||||||
|
width: 192, // 48 * 4 for better quality
|
||||||
|
margin: 2,
|
||||||
|
color: {
|
||||||
|
dark: "#000000",
|
||||||
|
light: "#FFFFFF",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
qrCodeSvg.value = svgString;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error generating QR code SVG:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits(["pixValidated"]);
|
||||||
|
|
||||||
|
// Function to check solicitation status
|
||||||
|
const checkSolicitationStatus = async () => {
|
||||||
|
if (!solicitationData.value?.numeroSolicitacao) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await getSolicitation(
|
||||||
|
solicitationData.value.numeroSolicitacao
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.signature) {
|
||||||
|
pixTimestamp.value = response.pixTimestamp;
|
||||||
|
releaseSignature.value = response.signature;
|
||||||
|
// Stop polling when payment is confirmed
|
||||||
|
if (pollingInterval.value) {
|
||||||
|
clearInterval(pollingInterval.value);
|
||||||
|
pollingInterval.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error checking solicitation status:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to start polling
|
||||||
|
const startPolling = () => {
|
||||||
|
// Clear any existing interval
|
||||||
|
if (pollingInterval.value) {
|
||||||
|
clearInterval(pollingInterval.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start new polling interval (10 seconds)
|
||||||
|
pollingInterval.value = setInterval(checkSolicitationStatus, 10000);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const copyToClipboard = async () => {
|
||||||
|
if (!qrCode.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(qrCode.value);
|
||||||
|
|
||||||
|
if (copyFeedbackTimeout.value) {
|
||||||
|
clearTimeout(copyFeedbackTimeout.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyFeedback.value = true;
|
||||||
|
|
||||||
|
copyFeedbackTimeout.value = setTimeout(() => {
|
||||||
|
copyFeedback.value = false;
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error copying to clipboard:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const { tokenAddress, sellerAddress, amount } = await getUnreleasedLockById(
|
||||||
|
BigInt(props.lockID)
|
||||||
|
);
|
||||||
|
|
||||||
|
const participantId = await getParticipantID(
|
||||||
|
sellerAddress,
|
||||||
|
tokenAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
const offer: Offer = {
|
||||||
|
amount,
|
||||||
|
sellerId: participantId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await createSolicitation(offer);
|
||||||
|
solicitationData.value = response;
|
||||||
|
|
||||||
|
// Update qrCode if the response contains QR code data
|
||||||
|
if (response?.informacoesPIX?.textoQrCode) {
|
||||||
|
qrCode.value = response.informacoesPIX?.textoQrCode;
|
||||||
|
// Generate SVG QR code
|
||||||
|
await generateQrCodeSvg(qrCode.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start polling for solicitation status
|
||||||
|
startPolling();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating solicitation:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up interval on component unmount
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (pollingInterval.value) {
|
||||||
|
clearInterval(pollingInterval.value);
|
||||||
|
pollingInterval.value = null;
|
||||||
|
}
|
||||||
|
if (copyFeedbackTimeout.value) {
|
||||||
|
clearTimeout(copyFeedbackTimeout.value);
|
||||||
|
copyFeedbackTimeout.value = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="page">
|
||||||
|
<div class="text-container">
|
||||||
|
<span
|
||||||
|
class="text font-extrabold lg:text-2xl text-xl sm:max-w-[30rem] max-w-[24rem]"
|
||||||
|
>
|
||||||
|
Utilize o QR Code ou copie o código para realizar o Pix
|
||||||
|
</span>
|
||||||
|
<span class="text font-medium lg:text-md text-sm max-w-[28rem]">
|
||||||
|
Após realizar o Pix no banco de sua preferência, clique no botão abaixo
|
||||||
|
para liberação dos tokens.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="main-container max-w-md text-black">
|
||||||
|
<div
|
||||||
|
class="flex-col items-center justify-center flex w-full bg-white sm:p-8 p-4 rounded-lg break-normal"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="qrCodeSvg"
|
||||||
|
v-html="qrCodeSvg"
|
||||||
|
class="w-48 h-48 flex items-center justify-center"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="w-48 h-48 flex items-center justify-center rounded-lg"
|
||||||
|
>
|
||||||
|
<SpinnerComponent width="8" height="8"></SpinnerComponent>
|
||||||
|
</div>
|
||||||
|
<span class="text-center font-bold">Código pix</span>
|
||||||
|
<div class="break-words w-4/5">
|
||||||
|
<span class="text-center text-xs">
|
||||||
|
{{ qrCode }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center gap-1">
|
||||||
|
<img
|
||||||
|
alt="Copy PIX code"
|
||||||
|
src="@/assets/copyPix.svg?url"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
class="pt-2 cursor-pointer hover:opacity-70 transition-opacity"
|
||||||
|
@click="copyToClipboard"
|
||||||
|
/>
|
||||||
|
<transition name="fade">
|
||||||
|
<span
|
||||||
|
v-if="copyFeedback"
|
||||||
|
class="text-xs text-emerald-500 font-semibold"
|
||||||
|
>
|
||||||
|
Código copiado!
|
||||||
|
</span>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CustomButton
|
||||||
|
:is-disabled="releaseSignature === ''"
|
||||||
|
:text="
|
||||||
|
releaseSignature ? 'Enviar para a rede' : 'Validando pagamento...'
|
||||||
|
"
|
||||||
|
@button-clicked="
|
||||||
|
emit('pixValidated', { pixTimestamp, signature: releaseSignature })
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CustomModal
|
||||||
|
v-if="showWarnModal"
|
||||||
|
@close-modal="showWarnModal = false"
|
||||||
|
:isRedirectModal="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page {
|
||||||
|
@apply flex flex-col items-center justify-center w-full mt-16;
|
||||||
|
}
|
||||||
|
|
||||||
|
::placeholder {
|
||||||
|
/* Most modern browsers support this now. */
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
color: #080808;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #080808;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
@apply rounded-lg border border-gray-200 p-2 text-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
@apply font-semibold tracking-wide text-emerald-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-divide {
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
.bottom-position {
|
||||||
|
top: -20px;
|
||||||
|
right: 50%;
|
||||||
|
transform: translateX(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
@apply flex flex-col items-center justify-center gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
@apply text-white text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blur-container {
|
||||||
|
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-6 max-w-screen-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
appearance: textfield;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fade transition for copy feedback */
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const props = defineProps({
|
|
||||||
text: String,
|
|
||||||
isDisabled: Boolean,
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["buttonClicked"]);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="button"
|
|
||||||
@click="emit('buttonClicked')"
|
|
||||||
v-bind:class="{ 'opacity-70': props.isDisabled }"
|
|
||||||
:disabled="props.isDisabled ? props.isDisabled : false"
|
|
||||||
>
|
|
||||||
{{ props.text }}
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.button {
|
|
||||||
@apply rounded-lg w-full text-base font-semibold text-gray-900 p-4 bg-amber-400;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { mount } from "@vue/test-utils";
|
|
||||||
import CustomButton from "../CustomButton.vue";
|
|
||||||
|
|
||||||
describe("CustomButton.vue", () => {
|
|
||||||
test("Test button content", () => {
|
|
||||||
const wrapper = mount(CustomButton, {
|
|
||||||
props: {
|
|
||||||
text: "Testing",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.html()).toContain("Testing");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test if disabled props works", () => {
|
|
||||||
const wrapper = mount(CustomButton, {
|
|
||||||
props: {
|
|
||||||
isDisabled: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
const button = wrapper.find(".button") as HTMLButtonElement;
|
|
||||||
//@ts-ignore
|
|
||||||
expect(button.element.disabled).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from "vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
isRedirectModal: Boolean,
|
|
||||||
});
|
|
||||||
|
|
||||||
const modalColor = ref<string>("white");
|
|
||||||
const modalHeight = ref<string>("250px");
|
|
||||||
const pFontSize = ref<string>("16px");
|
|
||||||
|
|
||||||
if (props.isRedirectModal) {
|
|
||||||
modalColor.value = "rgba(251, 191, 36, 1)";
|
|
||||||
modalHeight.value = "150px";
|
|
||||||
pFontSize.value = "20px";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="modal-overlay inset-0 fixed justify-center backdrop-blur-sm sm:backdrop-blur-none"
|
|
||||||
v-if="!isRedirectModal"
|
|
||||||
>
|
|
||||||
<div class="modal px-5 text-center">
|
|
||||||
<p
|
|
||||||
class="text-black tracking-tighter leading-tight my-6 mx-2 text-justify"
|
|
||||||
>
|
|
||||||
<strong>ATENÇÃO!</strong>
|
|
||||||
A transação só será processada após inserir o código de autenticação.
|
|
||||||
Caso contrário não conseguiremos comprovar o seu depósito e não será
|
|
||||||
possível transferir os tokens para sua carteira.
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
@click="$emit('close-modal')"
|
|
||||||
class="border-2 border-solid border-amber-400 mt-2"
|
|
||||||
>
|
|
||||||
Entendi
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="modal-overlay inset-0 fixed justify-center backdrop-blur-sm"
|
|
||||||
v-if="isRedirectModal"
|
|
||||||
>
|
|
||||||
<div class="modal px-5 text-center">
|
|
||||||
<p
|
|
||||||
class="text-black text-lg tracking-tighter leading-tight my-6 mx-2 text-justify font-semibold"
|
|
||||||
>
|
|
||||||
Retomar a última compra?
|
|
||||||
</p>
|
|
||||||
<div class="flex justify-around items-center px-2">
|
|
||||||
<button
|
|
||||||
@click="$emit('close-modal')"
|
|
||||||
class="border-2 border-solid border-white-400 mt-2 font-semibold"
|
|
||||||
>
|
|
||||||
Não
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
@click="$emit('go-to-lock')"
|
|
||||||
class="border-2 border-solid border-white-400 mt-2 font-semibold"
|
|
||||||
>
|
|
||||||
Sim
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.modal-overlay {
|
|
||||||
display: flex !important;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal {
|
|
||||||
background-color: v-bind(modalColor);
|
|
||||||
height: v-bind(modalHeight);
|
|
||||||
width: 300px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
.close {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-img {
|
|
||||||
width: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check {
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6 {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 28px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: v-bind(pFontSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 100px;
|
|
||||||
height: 40px;
|
|
||||||
color: black;
|
|
||||||
font-size: 14px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { mount } from "@vue/test-utils";
|
|
||||||
import CustomModal from "../CustomModal.vue";
|
|
||||||
|
|
||||||
describe("CustomModal test", () => {
|
|
||||||
test("Test custom modal when receive is redirect modal props as false", () => {
|
|
||||||
const wrapper = mount(CustomModal, {
|
|
||||||
props: {
|
|
||||||
isRedirectModal: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.html()).toContain("ATENÇÃO!");
|
|
||||||
expect(wrapper.html()).toContain("Entendi");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test custom modal when receive is redirect modal props as true", () => {
|
|
||||||
const wrapper = mount(CustomModal, {
|
|
||||||
props: {
|
|
||||||
isRedirectModal: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.html()).toContain("Retomar a última compra?");
|
|
||||||
expect(wrapper.html()).toContain("Não");
|
|
||||||
expect(wrapper.html()).toContain("Sim");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
|
import { ref, watch, onMounted, computed } from "vue";
|
||||||
|
import { debounce } from "@/utils/debounce";
|
||||||
|
import { decimalCount } from "@/utils/decimalCount";
|
||||||
|
import { useFloating, arrow, offset, flip, shift } from "@floating-ui/vue";
|
||||||
|
import IconButton from "../ui/IconButton.vue";
|
||||||
|
import withdrawIcon from "@/assets/withdraw.svg?url";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
validDeposits: ValidDeposit[];
|
||||||
|
activeLockAmount: number;
|
||||||
|
selectedToken: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
withdraw: [amount: string];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const withdrawAmount = ref<string>("");
|
||||||
|
const isCollapsibleOpen = ref<boolean>(false);
|
||||||
|
const validDecimals = ref<boolean>(true);
|
||||||
|
const validWithdrawAmount = ref<boolean>(true);
|
||||||
|
const enableConfirmButton = ref<boolean>(false);
|
||||||
|
const showInfoTooltip = ref<boolean>(false);
|
||||||
|
const floatingArrow = ref(null);
|
||||||
|
|
||||||
|
const reference = ref<HTMLElement | null>(null);
|
||||||
|
const floating = ref<HTMLElement | null>(null);
|
||||||
|
const infoText = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const remaining = computed(() => {
|
||||||
|
if (props.validDeposits.length > 0) {
|
||||||
|
const deposit = props.validDeposits[0];
|
||||||
|
return deposit ? deposit.remaining : 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleInputEvent = (event: any): void => {
|
||||||
|
const { value } = event.target;
|
||||||
|
|
||||||
|
if (decimalCount(String(value)) > 2) {
|
||||||
|
validDecimals.value = false;
|
||||||
|
enableConfirmButton.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
validDecimals.value = true;
|
||||||
|
|
||||||
|
if (value > remaining.value) {
|
||||||
|
validWithdrawAmount.value = false;
|
||||||
|
enableConfirmButton.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
validWithdrawAmount.value = true;
|
||||||
|
enableConfirmButton.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const callWithdraw = () => {
|
||||||
|
if (enableConfirmButton.value && withdrawAmount.value) {
|
||||||
|
emit("withdraw", withdrawAmount.value);
|
||||||
|
// Reset form after withdraw
|
||||||
|
withdrawAmount.value = "";
|
||||||
|
isCollapsibleOpen.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openWithdrawForm = () => {
|
||||||
|
isCollapsibleOpen.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelWithdraw = () => {
|
||||||
|
isCollapsibleOpen.value = false;
|
||||||
|
withdrawAmount.value = "";
|
||||||
|
validDecimals.value = true;
|
||||||
|
validWithdrawAmount.value = true;
|
||||||
|
enableConfirmButton.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
useFloating(reference, floating, {
|
||||||
|
placement: "right",
|
||||||
|
middleware: [
|
||||||
|
offset(10),
|
||||||
|
flip(),
|
||||||
|
shift(),
|
||||||
|
arrow({ element: floatingArrow }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full bg-white p-4 sm:p-6 rounded-lg">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm leading-5 font-medium text-gray-600">
|
||||||
|
Saldo disponível
|
||||||
|
</p>
|
||||||
|
<p class="text-xl leading-7 font-semibold text-gray-900">
|
||||||
|
{{ remaining }} {{ selectedToken }}
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-2 w-32 sm:w-56" v-if="activeLockAmount != 0">
|
||||||
|
<span class="text-xs font-normal text-gray-400" ref="infoText">
|
||||||
|
{{ `com ${activeLockAmount.toFixed(2)} ${selectedToken} em lock` }}
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="absolute mt-[2px] md-view"
|
||||||
|
:style="{ left: `${(infoText?.clientWidth ?? 108) + 4}px` }"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="info image"
|
||||||
|
src="@/assets/info.svg?url"
|
||||||
|
aria-describedby="tooltip"
|
||||||
|
ref="reference"
|
||||||
|
@mouseover="showInfoTooltip = true"
|
||||||
|
@mouseout="showInfoTooltip = false"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
role="tooltip"
|
||||||
|
ref="floating"
|
||||||
|
class="w-56 z-50 tooltip md-view"
|
||||||
|
v-if="showInfoTooltip"
|
||||||
|
>
|
||||||
|
Valor "em lock" significa que a quantia está aguardando
|
||||||
|
confirmação de compra e só estará disponível para saque caso a
|
||||||
|
transação expire.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="!isCollapsibleOpen" class="flex justify-end items-center">
|
||||||
|
<IconButton
|
||||||
|
text="Sacar"
|
||||||
|
:icon="withdrawIcon"
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
:full-width="false"
|
||||||
|
@click="openWithdrawForm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-5">
|
||||||
|
<div v-show="isCollapsibleOpen" class="py-2 w-100">
|
||||||
|
<p class="text-sm leading-5 font-medium">Valor do saque</p>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
@input="debounce(handleInputEvent, 500)($event)"
|
||||||
|
placeholder="0"
|
||||||
|
class="text-2xl text-gray-900 w-full outline-none"
|
||||||
|
v-model="withdrawAmount"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center" v-if="!validDecimals">
|
||||||
|
<span class="text-red-500 font-normal text-sm">
|
||||||
|
Por favor utilize no máximo 2 casas decimais
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center" v-else-if="!validWithdrawAmount">
|
||||||
|
<span class="text-red-500 font-normal text-sm">
|
||||||
|
Saldo insuficiente
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<hr v-show="isCollapsibleOpen" class="pb-3" />
|
||||||
|
<div
|
||||||
|
v-show="isCollapsibleOpen"
|
||||||
|
class="flex justify-between items-center"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
@click="cancelWithdraw"
|
||||||
|
class="text-black font-medium cursor-pointer hover:text-gray-600 transition-colors"
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
text="Sacar"
|
||||||
|
:icon="withdrawIcon"
|
||||||
|
variant="outline"
|
||||||
|
size="md"
|
||||||
|
:full-width="false"
|
||||||
|
:disabled="!enableConfirmButton"
|
||||||
|
@click="callWithdraw"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
p {
|
||||||
|
@apply text-gray-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
@apply bg-white text-gray-900 font-medium text-xs md:text-base px-3 py-2 rounded border-2 border-emerald-500 left-5 top-[-3rem];
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
appearance: textfield;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
.md-view {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,17 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { withdrawDeposit } from "@/blockchain/buyerMethods";
|
|
||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
import type { WalletTransaction } from "@/model/WalletTransaction";
|
import type { WalletTransaction } from "@/model/WalletTransaction";
|
||||||
import { useEtherStore } from "@/store/ether";
|
import { useUser } from "@/composables/useUser";
|
||||||
import { storeToRefs } from "pinia";
|
import { ref, watch } from "vue";
|
||||||
import { ref, watch, onMounted } from "vue";
|
import SpinnerComponent from "../ui/SpinnerComponent.vue";
|
||||||
import SpinnerComponent from "../SpinnerComponent.vue";
|
import BalanceCard from "./BalanceCard.vue";
|
||||||
import { decimalCount } from "@/utils/decimalCount";
|
import TransactionCard from "./TransactionCard.vue";
|
||||||
import { debounce } from "@/utils/debounce";
|
|
||||||
import { useFloating, arrow, offset, flip, shift } from "@floating-ui/vue";
|
|
||||||
|
|
||||||
const etherStore = useEtherStore();
|
const user = useUser();
|
||||||
|
|
||||||
// props
|
// props
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -22,82 +18,12 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits(["depositWithdrawn"]);
|
const emit = defineEmits(["depositWithdrawn"]);
|
||||||
|
|
||||||
const { loadingWalletTransactions } = storeToRefs(etherStore);
|
const { loadingWalletTransactions } = user;
|
||||||
const remaining = ref<number>(0.0);
|
|
||||||
const itemsToShow = ref<WalletTransaction[]>([]);
|
const itemsToShow = ref<WalletTransaction[]>([]);
|
||||||
const withdrawAmount = ref<string>("");
|
|
||||||
const withdrawButtonOpacity = ref<number>(0.6);
|
|
||||||
const withdrawButtonCursor = ref<string>("not-allowed");
|
|
||||||
const isCollapsibleOpen = ref<boolean>(false);
|
|
||||||
const validDecimals = ref<boolean>(true);
|
|
||||||
const validWithdrawAmount = ref<boolean>(true);
|
|
||||||
const enableConfirmButton = ref<boolean>(false);
|
|
||||||
const showInfoTooltip = ref<boolean>(false);
|
|
||||||
const floatingArrow = ref(null);
|
|
||||||
|
|
||||||
const reference = ref<HTMLElement | null>(null);
|
const callWithdraw = (amount: string) => {
|
||||||
const floating = ref<HTMLElement | null>(null);
|
emit("depositWithdrawn", amount);
|
||||||
const infoText = ref<HTMLElement | null>(null);
|
|
||||||
|
|
||||||
// Debounce methods
|
|
||||||
const handleInputEvent = (event: any): void => {
|
|
||||||
const { value } = event.target;
|
|
||||||
|
|
||||||
if (decimalCount(String(value)) > 2) {
|
|
||||||
validDecimals.value = false;
|
|
||||||
enableConfirmButton.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
validDecimals.value = true;
|
|
||||||
|
|
||||||
if (value > remaining.value) {
|
|
||||||
validWithdrawAmount.value = false;
|
|
||||||
enableConfirmButton.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
validWithdrawAmount.value = true;
|
|
||||||
enableConfirmButton.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const callWithdraw = () => {
|
|
||||||
emit("depositWithdrawn", withdrawAmount.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(enableConfirmButton, (): void => {
|
|
||||||
if (!enableConfirmButton.value) {
|
|
||||||
withdrawButtonOpacity.value = 0.7;
|
|
||||||
withdrawButtonCursor.value = "not-allowed";
|
|
||||||
} else {
|
|
||||||
withdrawButtonOpacity.value = 1;
|
|
||||||
withdrawButtonCursor.value = "pointer";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(withdrawAmount, (): void => {
|
|
||||||
if (!withdrawAmount.value || !enableConfirmButton.value) {
|
|
||||||
withdrawButtonOpacity.value = 0.7;
|
|
||||||
withdrawButtonCursor.value = "not-allowed";
|
|
||||||
} else {
|
|
||||||
withdrawButtonOpacity.value = 1;
|
|
||||||
withdrawButtonCursor.value = "pointer";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const getRemaining = (): number => {
|
|
||||||
if (props.validDeposits instanceof Array) {
|
|
||||||
// Here we are getting only the first element of the list because
|
|
||||||
// in this release only the BRL token is being used.
|
|
||||||
const deposit = props.validDeposits[0];
|
|
||||||
remaining.value = deposit ? deposit.remaining : 0;
|
|
||||||
return deposit ? deposit.remaining : 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getExplorer = (): string => {
|
|
||||||
return etherStore.networkName == NetworkEnum.ethereum
|
|
||||||
? "Etherscan"
|
|
||||||
: "Polygonscan";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const showInitialItems = (): void => {
|
const showInitialItems = (): void => {
|
||||||
@@ -105,10 +31,7 @@ const showInitialItems = (): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openEtherscanUrl = (transactionHash: string): void => {
|
const openEtherscanUrl = (transactionHash: string): void => {
|
||||||
const networkUrl =
|
const networkUrl = user.network.value.blockExplorers?.default.url;
|
||||||
etherStore.networkName == NetworkEnum.ethereum
|
|
||||||
? "goerli.etherscan.io"
|
|
||||||
: "mumbai.polygonscan.com";
|
|
||||||
const url = `https://${networkUrl}/tx/${transactionHash}`;
|
const url = `https://${networkUrl}/tx/${transactionHash}`;
|
||||||
window.open(url, "_blank");
|
window.open(url, "_blank");
|
||||||
};
|
};
|
||||||
@@ -120,31 +43,6 @@ const loadMore = (): void => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEventName = (event: string | undefined): string => {
|
|
||||||
if (!event) return "Desconhecido";
|
|
||||||
|
|
||||||
const possibleEventName: { [key: string]: string } = {
|
|
||||||
DepositAdded: "Oferta",
|
|
||||||
LockAdded: "Reserva",
|
|
||||||
LockReleased: "Compra",
|
|
||||||
DepositWithdrawn: "Retirada",
|
|
||||||
};
|
|
||||||
|
|
||||||
return possibleEventName[event];
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
useFloating(reference, floating, {
|
|
||||||
placement: "right",
|
|
||||||
middleware: [
|
|
||||||
offset(10),
|
|
||||||
flip(),
|
|
||||||
shift(),
|
|
||||||
arrow({ element: floatingArrow }),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// watch props changes
|
// watch props changes
|
||||||
watch(props, async (): Promise<void> => {
|
watch(props, async (): Promise<void> => {
|
||||||
const itemsToShowQty = itemsToShow.value.length;
|
const itemsToShowQty = itemsToShow.value.length;
|
||||||
@@ -161,181 +59,31 @@ showInitialItems();
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="blur-container" v-if="loadingWalletTransactions">
|
<div
|
||||||
|
class="main-container max-w-md flex justify-center items-center min-h-[200px] w-16 h-16"
|
||||||
|
v-if="loadingWalletTransactions"
|
||||||
|
>
|
||||||
|
Carregando ofertas...
|
||||||
<SpinnerComponent width="8" height="8"></SpinnerComponent>
|
<SpinnerComponent width="8" height="8"></SpinnerComponent>
|
||||||
</div>
|
</div>
|
||||||
<div class="blur-container" v-if="!loadingWalletTransactions">
|
<div class="main-container max-w-md" v-else>
|
||||||
<div
|
<BalanceCard
|
||||||
class="w-full bg-white p-4 sm:p-6 rounded-lg"
|
|
||||||
v-if="props.validDeposits.length > 0"
|
v-if="props.validDeposits.length > 0"
|
||||||
>
|
:valid-deposits="props.validDeposits"
|
||||||
<div class="flex justify-between items-center">
|
:active-lock-amount="activeLockAmount"
|
||||||
<div>
|
:selected-token="user.selectedToken.value"
|
||||||
<p class="text-sm leading-5 font-medium text-gray-600">
|
@withdraw="callWithdraw"
|
||||||
Saldo disponível
|
|
||||||
</p>
|
|
||||||
<p class="text-xl leading-7 font-semibold text-gray-900">
|
|
||||||
{{ getRemaining() }} BRZ
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-2 w-32 sm:w-56" v-if="activeLockAmount != 0">
|
|
||||||
<span class="text-xs font-normal text-gray-400" ref="infoText">{{
|
|
||||||
`com ${activeLockAmount.toFixed(2)} BRZ em lock`
|
|
||||||
}}</span>
|
|
||||||
<div
|
|
||||||
class="absolute mt-[2px] md-view"
|
|
||||||
:style="{ left: `${(infoText?.clientWidth ?? 108) + 4}px` }"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="info image"
|
|
||||||
src="@/assets/info.svg"
|
|
||||||
aria-describedby="tooltip"
|
|
||||||
ref="reference"
|
|
||||||
@mouseover="showInfoTooltip = true"
|
|
||||||
@mouseout="showInfoTooltip = false"
|
|
||||||
/>
|
/>
|
||||||
<div
|
|
||||||
role="tooltip"
|
|
||||||
ref="floating"
|
|
||||||
class="w-56 z-50 tooltip md-view"
|
|
||||||
v-if="showInfoTooltip"
|
|
||||||
>
|
|
||||||
Valor “em lock” significa que a quantia está aguardando
|
|
||||||
confirmação de compra e só estará disponível para saque caso a
|
|
||||||
transação expire.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-show="!isCollapsibleOpen" class="flex justify-end items-center">
|
|
||||||
<div
|
|
||||||
class="flex gap-2 cursor-pointer items-center justify-self-center border-2 p-2 border-amber-300 rounded-md"
|
|
||||||
@click="[(isCollapsibleOpen = true)]"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Withdraw image"
|
|
||||||
src="@/assets/withdraw.svg"
|
|
||||||
class="w-3 h-3 sm:w-4 sm:h-4"
|
|
||||||
/>
|
|
||||||
<span class="last-release-info">Sacar</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pt-5">
|
|
||||||
<div v-show="isCollapsibleOpen" class="py-2 w-100">
|
|
||||||
<p class="text-sm leading-5 font-medium">Valor do saque</p>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
name=""
|
|
||||||
@input="debounce(handleInputEvent, 500)($event)"
|
|
||||||
placeholder="0"
|
|
||||||
class="text-2xl text-gray-900 w-full outline-none"
|
|
||||||
v-model="withdrawAmount"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center" v-if="!validDecimals">
|
|
||||||
<span class="text-red-500 font-normal text-sm"
|
|
||||||
>Por favor utilize no máximo 2 casas decimais</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center" v-else-if="!validWithdrawAmount">
|
|
||||||
<span class="text-red-500 font-normal text-sm"
|
|
||||||
>Saldo insuficiente</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<hr v-show="isCollapsibleOpen" class="pb-3" />
|
|
||||||
<div
|
|
||||||
v-show="isCollapsibleOpen"
|
|
||||||
class="flex justify-between items-center"
|
|
||||||
>
|
|
||||||
<h1
|
|
||||||
@click="[(isCollapsibleOpen = false)]"
|
|
||||||
class="text-black font-medium cursor-pointer"
|
|
||||||
>
|
|
||||||
Cancelar
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div
|
<TransactionCard
|
||||||
class="withdraw-button flex gap-2 items-center justify-self-center border-2 p-2 border-amber-300 rounded-md"
|
|
||||||
@click="callWithdraw"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Withdraw image"
|
|
||||||
src="@/assets/withdraw.svg"
|
|
||||||
class="w-3 h-3 sm:w-4 sm:h-4"
|
|
||||||
/>
|
|
||||||
<span class="last-release-info">Sacar</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="w-full bg-white p-4 sm:p-6 rounded-lg"
|
|
||||||
v-for="item in itemsToShow"
|
v-for="item in itemsToShow"
|
||||||
:key="item.blockNumber"
|
:key="item.blockNumber"
|
||||||
>
|
:selected-token="user.selectedToken.value"
|
||||||
<div class="item-container">
|
:transaction="item"
|
||||||
<div class="flex flex-col self-start">
|
:network-name="user.network.value.name"
|
||||||
<span class="text-xs sm:text-sm leading-5 font-medium text-gray-600">
|
@open-explorer="openEtherscanUrl"
|
||||||
{{ getEventName(item.event) }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="text-xl sm:text-xl leading-7 font-semibold text-gray-900"
|
|
||||||
>
|
|
||||||
{{ item.amount }}
|
|
||||||
BRZ
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="bg-amber-300 status-text"
|
|
||||||
v-if="getEventName(item.event) == 'Reserva' && item.lockStatus == 1"
|
|
||||||
>
|
|
||||||
Em Aberto
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-[#94A3B8] status-text"
|
|
||||||
v-if="getEventName(item.event) == 'Reserva' && item.lockStatus == 2"
|
|
||||||
>
|
|
||||||
Expirado
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="bg-emerald-300 status-text"
|
|
||||||
v-if="
|
|
||||||
(getEventName(item.event) == 'Reserva' && item.lockStatus == 3) ||
|
|
||||||
getEventName(item.event) != 'Reserva'
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Finalizado
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex gap-2 cursor-pointer items-center justify-self-center w-full"
|
|
||||||
@click="openEtherscanUrl(item.transactionHash)"
|
|
||||||
v-if="getEventName(item.event) != 'Reserva' || item.lockStatus != 1"
|
|
||||||
>
|
|
||||||
<span class="last-release-info">{{ getExplorer() }}</span>
|
|
||||||
<img
|
|
||||||
alt="Redirect image"
|
|
||||||
src="@/assets/redirect.svg"
|
|
||||||
class="w-3 h-3 sm:w-4 sm:h-4"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex gap-2 justify-self-center w-full"
|
|
||||||
v-if="getEventName(item.event) == 'Reserva' && item.lockStatus == 1"
|
|
||||||
>
|
|
||||||
<RouterLink
|
|
||||||
:to="{
|
|
||||||
name: 'home',
|
|
||||||
force: true,
|
|
||||||
state: { lockID: item.transactionID },
|
|
||||||
}"
|
|
||||||
class="router-button"
|
|
||||||
>Continuar</RouterLink
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="flex flex-col justify-center items-center w-full mt-2 gap-2"
|
class="flex flex-col justify-center items-center w-full mt-2 gap-2"
|
||||||
v-if="
|
v-if="
|
||||||
@@ -345,14 +93,14 @@ showInitialItems();
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="text-white font-semibold"
|
class="text-white font-semibold border-2 border-amber-300 rounded-lg px-4 py-2 hover:bg-amber-300/10 transition-colors cursor-pointer"
|
||||||
@click="loadMore()"
|
@click="loadMore()"
|
||||||
>
|
>
|
||||||
Carregar mais
|
Carregar mais
|
||||||
</button>
|
</button>
|
||||||
<span class="text-gray-300">
|
<span class="text-gray-300 text-sm">
|
||||||
({{ itemsToShow.length }} de {{ props.walletTransactions.length }}
|
{{ itemsToShow.length }} de {{ props.walletTransactions.length }}
|
||||||
transações )
|
transações
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -363,66 +111,5 @@ showInitialItems();
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.page {
|
/* Minimal styles - most styles moved to child components */
|
||||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
@apply text-gray-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-container {
|
|
||||||
@apply flex flex-col items-center justify-center gap-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item-container {
|
|
||||||
@apply flex justify-between items-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-text {
|
|
||||||
@apply text-xs sm:text-base font-medium text-gray-900 rounded-lg text-center mb-2 px-2 py-1 mt-4;
|
|
||||||
}
|
|
||||||
.text {
|
|
||||||
@apply text-white text-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blur-container {
|
|
||||||
@apply flex flex-col justify-center items-center px-4 py-3 sm:px-8 sm:py-6 gap-4 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md w-auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-container {
|
|
||||||
@apply grid grid-cols-4 grid-flow-row items-center px-8 py-6 gap-4 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10 w-auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-release-info {
|
|
||||||
@apply font-medium text-xs sm:text-sm text-gray-900 justify-self-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
@apply bg-white text-gray-900 font-medium text-xs md:text-base px-3 py-2 rounded border-2 border-emerald-500 left-5 top-[-3rem];
|
|
||||||
}
|
|
||||||
|
|
||||||
.router-button {
|
|
||||||
@apply rounded-lg border-amber-300 border-2 px-3 py-2 text-gray-900 font-semibold sm:text-base text-xs hover:bg-transparent w-full text-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.withdraw-button {
|
|
||||||
opacity: v-bind(withdrawButtonOpacity);
|
|
||||||
cursor: v-bind(withdrawButtonCursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 640px) {
|
|
||||||
.md-view {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { WalletTransaction } from "@/model/WalletTransaction";
|
||||||
|
import { TokenEnum } from "@/model/NetworkEnum";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import StatusBadge, { type StatusType } from "../ui/StatusBadge.vue";
|
||||||
|
import { Networks } from "@/config/networks";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
transaction: WalletTransaction;
|
||||||
|
networkName: keyof typeof Networks;
|
||||||
|
selectedToken: TokenEnum;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
openExplorer: [transactionHash: string];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const eventName = computed(() => {
|
||||||
|
if (!props.transaction.event) return "Desconhecido";
|
||||||
|
|
||||||
|
const possibleEventName: { [key: string]: string } = {
|
||||||
|
DepositAdded: "Oferta",
|
||||||
|
LockAdded: "Reserva",
|
||||||
|
LockReleased: "Compra",
|
||||||
|
DepositWithdrawn: "Retirada",
|
||||||
|
};
|
||||||
|
|
||||||
|
return possibleEventName[props.transaction.event] || "Desconhecido";
|
||||||
|
});
|
||||||
|
|
||||||
|
const explorerName = computed(() => {
|
||||||
|
return Networks[props.networkName].blockExplorers?.default.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusType = computed((): StatusType => {
|
||||||
|
if (eventName.value === "Reserva") {
|
||||||
|
switch (props.transaction.lockStatus) {
|
||||||
|
case 1:
|
||||||
|
return "open";
|
||||||
|
case 2:
|
||||||
|
return "expired";
|
||||||
|
case 3:
|
||||||
|
return "completed";
|
||||||
|
default:
|
||||||
|
return "completed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "completed";
|
||||||
|
});
|
||||||
|
|
||||||
|
const showExplorerLink = computed(() => {
|
||||||
|
return eventName.value !== "Reserva" || props.transaction.lockStatus !== 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const showContinueButton = computed(() => {
|
||||||
|
return eventName.value === "Reserva" && props.transaction.lockStatus === 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleExplorerClick = () => {
|
||||||
|
emit("openExplorer", props.transaction.transactionHash);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full bg-white p-4 sm:p-6 rounded-lg">
|
||||||
|
<div class="item-container">
|
||||||
|
<div class="flex flex-col self-start">
|
||||||
|
<span class="text-xs sm:text-sm leading-5 font-medium text-gray-600">
|
||||||
|
{{ eventName }}
|
||||||
|
</span>
|
||||||
|
<span class="text-xl sm:text-xl leading-7 font-semibold text-gray-900">
|
||||||
|
{{ transaction.amount }} {{ selectedToken }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<div class="mb-2 mt-4">
|
||||||
|
<StatusBadge :status="statusType" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showExplorerLink"
|
||||||
|
class="flex gap-2 cursor-pointer items-center justify-self-center w-full"
|
||||||
|
@click="handleExplorerClick"
|
||||||
|
>
|
||||||
|
<span class="last-release-info">{{ explorerName }}</span>
|
||||||
|
<img
|
||||||
|
alt="Redirect image"
|
||||||
|
src="@/assets/redirect.svg?url"
|
||||||
|
class="w-3 h-3 sm:w-4 sm:h-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showContinueButton"
|
||||||
|
class="flex gap-2 justify-self-center w-full"
|
||||||
|
>
|
||||||
|
<RouterLink
|
||||||
|
:to="{
|
||||||
|
name: 'home',
|
||||||
|
force: true,
|
||||||
|
state: { lockID: transaction.transactionID },
|
||||||
|
}"
|
||||||
|
class="router-button"
|
||||||
|
>
|
||||||
|
Continuar
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.item-container {
|
||||||
|
@apply flex justify-between items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.last-release-info {
|
||||||
|
@apply font-medium text-xs sm:text-sm text-gray-900 justify-self-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.router-button {
|
||||||
|
@apply rounded-lg border-amber-300 border-2 px-3 py-2 text-gray-900 font-semibold sm:text-base text-xs hover:bg-transparent w-full text-center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
import { mount } from "@vue/test-utils";
|
|
||||||
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
|
||||||
import { createPinia, setActivePinia } from "pinia";
|
|
||||||
import { expect } from "vitest";
|
|
||||||
import { MockValidDeposits } from "@/model/mock/ValidDepositMock";
|
|
||||||
import { MockWalletTransactions } from "@/model/mock/WalletTransactionMock";
|
|
||||||
import { useEtherStore } from "@/store/ether";
|
|
||||||
|
|
||||||
describe("ListingComponent.vue", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
setActivePinia(createPinia());
|
|
||||||
useEtherStore().setLoadingWalletTransactions(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test Message when an empty array is received", () => {
|
|
||||||
const wrapper = mount(ListingComponent, {
|
|
||||||
props: {
|
|
||||||
validDeposits: [],
|
|
||||||
walletTransactions: [],
|
|
||||||
activeLockAmount: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.html()).toContain("Não há nenhuma transação anterior");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test number of elements in the list first render", () => {
|
|
||||||
const wrapper = mount(ListingComponent, {
|
|
||||||
props: {
|
|
||||||
validDeposits: [],
|
|
||||||
walletTransactions: MockWalletTransactions,
|
|
||||||
activeLockAmount: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const elements = wrapper.findAll(".item-container");
|
|
||||||
|
|
||||||
expect(elements).toHaveLength(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test load more button behavior", async () => {
|
|
||||||
const wrapper = mount(ListingComponent, {
|
|
||||||
props: {
|
|
||||||
validDeposits: MockValidDeposits,
|
|
||||||
walletTransactions: MockWalletTransactions,
|
|
||||||
activeLockAmount: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const btn = wrapper.find("button");
|
|
||||||
|
|
||||||
let elements = wrapper.findAll(".item-container");
|
|
||||||
expect(elements).toHaveLength(3);
|
|
||||||
|
|
||||||
await btn.trigger("click");
|
|
||||||
|
|
||||||
elements = wrapper.findAll(".item-container");
|
|
||||||
|
|
||||||
expect(elements).toHaveLength(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test withdraw offer button emit", async () => {
|
|
||||||
const wrapper = mount(ListingComponent, {
|
|
||||||
props: {
|
|
||||||
validDeposits: MockValidDeposits,
|
|
||||||
walletTransactions: MockWalletTransactions,
|
|
||||||
activeLockAmount: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
wrapper.vm.$emit("depositWithdrawn");
|
|
||||||
|
|
||||||
await wrapper.vm.$nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.emitted("depositWithdrawn")).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test should render lock info when active lock amount is greater than 0", () => {
|
|
||||||
const wrapper = mount(ListingComponent, {
|
|
||||||
props: {
|
|
||||||
validDeposits: MockValidDeposits,
|
|
||||||
walletTransactions: [],
|
|
||||||
activeLockAmount: 50,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.html()).toContain("com 50.00 BRZ em lock");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { mount } from "@vue/test-utils";
|
|
||||||
import LoadingComponent from "../LoadingComponent.vue";
|
|
||||||
|
|
||||||
describe("Loading.vue", () => {
|
|
||||||
test("Test loading content with received props", () => {
|
|
||||||
const wrapper = mount(LoadingComponent, {
|
|
||||||
props: {
|
|
||||||
title: "MockTitle",
|
|
||||||
message: "MockMessage",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.html()).toContain("MockTitle");
|
|
||||||
expect(wrapper.html()).toContain("MockMessage");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test default text if props title isnt passed", () => {
|
|
||||||
const wrapper = mount(LoadingComponent, {
|
|
||||||
props: {
|
|
||||||
message: "MockMessage",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(wrapper.html()).toContain("Confirme em sua carteira");
|
|
||||||
expect(wrapper.html()).toContain("MockMessage");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { pix } from "@/utils/QrCodePix";
|
|
||||||
import { onMounted, onUnmounted, ref } from "vue";
|
|
||||||
import { debounce } from "@/utils/debounce";
|
|
||||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
|
||||||
import CustomModal from "@/components//CustomModal/CustomModal.vue";
|
|
||||||
import api from "@/services/index";
|
|
||||||
|
|
||||||
// props and store references
|
|
||||||
const props = defineProps({
|
|
||||||
pixTarget: String,
|
|
||||||
tokenValue: Number,
|
|
||||||
});
|
|
||||||
|
|
||||||
const windowSize = ref<number>(window.innerWidth);
|
|
||||||
const qrCode = ref<string>("");
|
|
||||||
const qrCodePayload = ref<string>("");
|
|
||||||
const isPixValid = ref<boolean>(false);
|
|
||||||
const isCodeInputEmpty = ref<boolean>(true);
|
|
||||||
const showWarnModal = ref<boolean>(true);
|
|
||||||
const e2eId = ref<string>("");
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(["pixValidated"]);
|
|
||||||
|
|
||||||
const pixQrCode = pix({
|
|
||||||
pixKey: props.pixTarget ?? "",
|
|
||||||
value: props.tokenValue,
|
|
||||||
});
|
|
||||||
|
|
||||||
pixQrCode.base64QrCode().then((code: string) => {
|
|
||||||
qrCode.value = code;
|
|
||||||
});
|
|
||||||
|
|
||||||
qrCodePayload.value = pixQrCode.payload();
|
|
||||||
|
|
||||||
const handleInputEvent = async (event: any): Promise<void> => {
|
|
||||||
const { value } = event.target;
|
|
||||||
e2eId.value = value;
|
|
||||||
await validatePix();
|
|
||||||
};
|
|
||||||
|
|
||||||
const validatePix = async (): Promise<void> => {
|
|
||||||
if (e2eId.value == "") {
|
|
||||||
isPixValid.value = false;
|
|
||||||
isCodeInputEmpty.value = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const sellerPixKey = props.pixTarget;
|
|
||||||
const transactionValue = props.tokenValue;
|
|
||||||
|
|
||||||
if (sellerPixKey && transactionValue) {
|
|
||||||
const body_req = {
|
|
||||||
e2e_id: e2eId.value,
|
|
||||||
pix_key: sellerPixKey,
|
|
||||||
pix_value: transactionValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
isCodeInputEmpty.value = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await api.post("validate_pix", body_req);
|
|
||||||
isPixValid.value = true;
|
|
||||||
} catch (error) {
|
|
||||||
isPixValid.value = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isCodeInputEmpty.value = false;
|
|
||||||
isPixValid.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
window.addEventListener(
|
|
||||||
"resize",
|
|
||||||
() => (windowSize.value = window.innerWidth)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener(
|
|
||||||
"resize",
|
|
||||||
() => (windowSize.value = window.innerWidth)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="page">
|
|
||||||
<div class="text-container">
|
|
||||||
<span
|
|
||||||
class="text font-extrabold lg:text-2xl text-xl sm:max-w-[30rem] max-w-[24rem]"
|
|
||||||
>
|
|
||||||
Utilize o QR Code ou copie o código para realizar o Pix
|
|
||||||
</span>
|
|
||||||
<span class="text font-medium lg:text-md text-sm max-w-[28rem]">
|
|
||||||
Após realizar o Pix no banco de sua preferência, insira o código de
|
|
||||||
autenticação para enviar a transação para a rede.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="blur-container sm:max-w-[28rem] max-w-[20rem] text-black">
|
|
||||||
<div
|
|
||||||
class="flex-col items-center justify-center flex w-full bg-white sm:p-8 p-4 rounded-lg break-normal"
|
|
||||||
>
|
|
||||||
<img alt="Qr code image" :src="qrCode" class="w-48 h-48" />
|
|
||||||
<span class="text-center font-bold">Código pix</span>
|
|
||||||
<div class="break-words w-4/5">
|
|
||||||
<span class="text-center text-xs">
|
|
||||||
{{ qrCodePayload }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
alt="Copy PIX code"
|
|
||||||
src="@/assets/copyPix.svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
class="pt-2 lg:mb-5 cursor-pointer"
|
|
||||||
/>
|
|
||||||
<span class="text-xs text-start lg-view">
|
|
||||||
<strong>ATENÇÃO!</strong> A transação só será processada após inserir
|
|
||||||
o código de autenticação. Caso contrário não conseguiremos comprovar o
|
|
||||||
seu depósito e não será possível transferir os tokens para sua
|
|
||||||
carteira. Confira aqui como encontrar o código no comprovante.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex-col items-center justify-center flex w-full bg-white p-5 rounded-lg px-5"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Digite o código do comprovante PIX"
|
|
||||||
@input="debounce(handleInputEvent, 500)($event)"
|
|
||||||
class="sm:text-md text-sm w-full box-border p-2 sm:h-6 h-2 mb-2 outline-none"
|
|
||||||
/>
|
|
||||||
<div class="custom-divide" v-if="!isCodeInputEmpty"></div>
|
|
||||||
<div
|
|
||||||
class="flex flex-col w-full"
|
|
||||||
v-if="!isPixValid && !isCodeInputEmpty"
|
|
||||||
>
|
|
||||||
<div class="flex items-center h-8">
|
|
||||||
<img
|
|
||||||
alt="Invalid Icon"
|
|
||||||
src="@/assets/invalidIcon.svg"
|
|
||||||
width="14"
|
|
||||||
class="cursor-pointer align-middle inline-block"
|
|
||||||
/>
|
|
||||||
<span class="px-1 text-red-500 font-normal text-xs"
|
|
||||||
>Código inválido. Por favor, confira e tente novamente.</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col w-full" v-else-if="isPixValid == true">
|
|
||||||
<div class="flex items-center h-8">
|
|
||||||
<img
|
|
||||||
alt="Valid Icon"
|
|
||||||
src="@/assets/validIcon.svg"
|
|
||||||
width="14"
|
|
||||||
class="cursor-pointer align-middle inline-block"
|
|
||||||
/>
|
|
||||||
<span class="px-1 text-green-500 font-normal text-sm">
|
|
||||||
Código válido.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<CustomButton
|
|
||||||
:is-disabled="isPixValid == false"
|
|
||||||
:text="'Enviar para a rede'"
|
|
||||||
@button-clicked="emit('pixValidated', e2eId)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CustomModal
|
|
||||||
v-if="showWarnModal && windowSize <= 500"
|
|
||||||
@close-modal="showWarnModal = false"
|
|
||||||
:isRedirectModal="false"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.page {
|
|
||||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
|
||||||
}
|
|
||||||
|
|
||||||
::placeholder {
|
|
||||||
/* Most modern browsers support this now. */
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
color: #080808;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
color: #080808;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input {
|
|
||||||
@apply rounded-lg border border-gray-200 p-2 text-black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
@apply font-semibold tracking-wide text-emerald-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-divide {
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid #d1d5db;
|
|
||||||
}
|
|
||||||
.bottom-position {
|
|
||||||
top: -20px;
|
|
||||||
right: 50%;
|
|
||||||
transform: translateX(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-container {
|
|
||||||
@apply flex flex-col items-center justify-center gap-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
@apply text-white text-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blur-container {
|
|
||||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lg-view {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm-view {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
|
||||||
.lg-view {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm-view {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch } from "vue";
|
|
||||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
|
||||||
import { debounce } from "@/utils/debounce";
|
|
||||||
import { useEtherStore } from "@/store/ether";
|
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { connectProvider } from "@/blockchain/provider";
|
|
||||||
import { verifyNetworkLiquidity } from "@/utils/networkLiquidity";
|
|
||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
|
||||||
import { decimalCount } from "@/utils/decimalCount";
|
|
||||||
import SpinnerComponent from "./SpinnerComponent.vue";
|
|
||||||
|
|
||||||
// Store reference
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
|
|
||||||
const {
|
|
||||||
walletAddress,
|
|
||||||
networkName,
|
|
||||||
depositsValidListGoerli,
|
|
||||||
depositsValidListMumbai,
|
|
||||||
loadingNetworkLiquidity,
|
|
||||||
} = storeToRefs(etherStore);
|
|
||||||
|
|
||||||
// Reactive state
|
|
||||||
const tokenValue = ref<number>(0);
|
|
||||||
const enableConfirmButton = ref<boolean>(false);
|
|
||||||
const enableWalletButton = ref<boolean>(false);
|
|
||||||
const hasLiquidity = ref<boolean>(true);
|
|
||||||
const validDecimals = ref<boolean>(true);
|
|
||||||
const selectedGoerliDeposit = ref<ValidDeposit>();
|
|
||||||
const selectedMumbaiDeposit = ref<ValidDeposit>();
|
|
||||||
const selectedRootstockDeposit = ref<ValidDeposit>();
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(["tokenBuy"]);
|
|
||||||
|
|
||||||
// Blockchain methods
|
|
||||||
const connectAccount = async (): Promise<void> => {
|
|
||||||
await connectProvider();
|
|
||||||
|
|
||||||
enableOrDisableConfirmButton();
|
|
||||||
};
|
|
||||||
|
|
||||||
const emitConfirmButton = (): void => {
|
|
||||||
const selectedDeposit =
|
|
||||||
networkName.value == NetworkEnum.ethereum
|
|
||||||
? selectedGoerliDeposit.value
|
|
||||||
: selectedMumbaiDeposit.value;
|
|
||||||
emit("tokenBuy", selectedDeposit, tokenValue.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Debounce methods
|
|
||||||
const handleInputEvent = (event: any): void => {
|
|
||||||
const { value } = event.target;
|
|
||||||
|
|
||||||
tokenValue.value = Number(value);
|
|
||||||
|
|
||||||
if (decimalCount(String(tokenValue.value)) > 2) {
|
|
||||||
validDecimals.value = false;
|
|
||||||
enableConfirmButton.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
validDecimals.value = true;
|
|
||||||
|
|
||||||
verifyLiquidity();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Verify if there is a valid deposit to buy
|
|
||||||
const verifyLiquidity = (): void => {
|
|
||||||
enableConfirmButton.value = false;
|
|
||||||
selectedGoerliDeposit.value = undefined;
|
|
||||||
selectedMumbaiDeposit.value = undefined;
|
|
||||||
selectedRootstockDeposit.value = undefined;
|
|
||||||
|
|
||||||
if (tokenValue.value <= 0) {
|
|
||||||
enableWalletButton.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedGoerliDeposit.value = verifyNetworkLiquidity(
|
|
||||||
tokenValue.value,
|
|
||||||
walletAddress.value,
|
|
||||||
depositsValidListGoerli.value
|
|
||||||
);
|
|
||||||
selectedMumbaiDeposit.value = verifyNetworkLiquidity(
|
|
||||||
tokenValue.value,
|
|
||||||
walletAddress.value,
|
|
||||||
depositsValidListMumbai.value
|
|
||||||
);
|
|
||||||
|
|
||||||
enableOrDisableConfirmButton();
|
|
||||||
if (selectedGoerliDeposit.value || selectedMumbaiDeposit.value) {
|
|
||||||
hasLiquidity.value = true;
|
|
||||||
enableWalletButton.value = true;
|
|
||||||
} else {
|
|
||||||
hasLiquidity.value = false;
|
|
||||||
enableWalletButton.value = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const enableOrDisableConfirmButton = (): void => {
|
|
||||||
if (selectedGoerliDeposit.value && networkName.value == NetworkEnum.ethereum)
|
|
||||||
enableConfirmButton.value = true;
|
|
||||||
else if (
|
|
||||||
selectedMumbaiDeposit.value &&
|
|
||||||
networkName.value == NetworkEnum.polygon
|
|
||||||
)
|
|
||||||
enableConfirmButton.value = true;
|
|
||||||
else enableConfirmButton.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(networkName, (): void => {
|
|
||||||
verifyLiquidity();
|
|
||||||
enableOrDisableConfirmButton();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(walletAddress, (): void => {
|
|
||||||
verifyLiquidity();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="page">
|
|
||||||
<div class="text-container">
|
|
||||||
<span
|
|
||||||
class="text font-extrabold sm:text-5xl text-3xl sm:max-w-[29rem] max-w-[20rem]"
|
|
||||||
>
|
|
||||||
Adquira cripto com apenas um Pix</span
|
|
||||||
>
|
|
||||||
<span class="text font-medium sm:text-base text-sm max-w-[28rem]"
|
|
||||||
>Digite um valor, confira a oferta, conecte sua carteira e receba os
|
|
||||||
tokens após realizar o Pix</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="blur-container">
|
|
||||||
<div class="backdrop-blur -z-10 w-full h-full"></div>
|
|
||||||
<div
|
|
||||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-5 rounded-lg border-y-10"
|
|
||||||
>
|
|
||||||
<div class="flex justify-between sm:w-full items-center">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
class="border-none outline-none text-lg text-gray-900 w-3/4"
|
|
||||||
v-bind:class="{
|
|
||||||
'font-semibold': tokenValue != undefined,
|
|
||||||
'text-xl': tokenValue != undefined,
|
|
||||||
}"
|
|
||||||
@input="debounce(handleInputEvent, 500)($event)"
|
|
||||||
placeholder="0 "
|
|
||||||
step=".01"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Token image"
|
|
||||||
class="sm:w-fit w-4"
|
|
||||||
src="@/assets/brz.svg"
|
|
||||||
/>
|
|
||||||
<span class="text-gray-900 sm:text-lg text-md w-fit" id="brz"
|
|
||||||
>BRZ</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="custom-divide py-2 mb-2"></div>
|
|
||||||
<div
|
|
||||||
class="flex justify-between"
|
|
||||||
v-if="hasLiquidity && !loadingNetworkLiquidity"
|
|
||||||
>
|
|
||||||
<p class="text-gray-500 font-normal text-sm w-auto">
|
|
||||||
~ R$ {{ tokenValue.toFixed(2) }}
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<img
|
|
||||||
alt="Polygon image"
|
|
||||||
src="@/assets/polygon.svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
v-if="selectedMumbaiDeposit"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
alt="Ethereum image"
|
|
||||||
src="@/assets/ethereum.svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
v-if="selectedGoerliDeposit"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex justify-center items-center"
|
|
||||||
v-if="loadingNetworkLiquidity"
|
|
||||||
>
|
|
||||||
<span class="text-gray-900 font-normal text-sm mr-2"
|
|
||||||
>Carregando liquidez das redes.</span
|
|
||||||
>
|
|
||||||
<SpinnerComponent width="4" height="4"></SpinnerComponent>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex justify-center"
|
|
||||||
v-if="!validDecimals && !loadingNetworkLiquidity"
|
|
||||||
>
|
|
||||||
<span class="text-red-500 font-normal text-sm"
|
|
||||||
>Por favor utilize no máximo 2 casas decimais</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex justify-center"
|
|
||||||
v-else-if="!hasLiquidity && !loadingNetworkLiquidity"
|
|
||||||
>
|
|
||||||
<span class="text-red-500 font-normal text-sm"
|
|
||||||
>Atualmente não há liquidez nas redes para sua demanda</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<CustomButton
|
|
||||||
v-if="!walletAddress"
|
|
||||||
:text="'Conectar carteira'"
|
|
||||||
:is-disabled="!enableWalletButton"
|
|
||||||
@buttonClicked="connectAccount()"
|
|
||||||
/>
|
|
||||||
<CustomButton
|
|
||||||
v-if="walletAddress"
|
|
||||||
:text="'Confirmar compra'"
|
|
||||||
:is-disabled="!enableConfirmButton"
|
|
||||||
@buttonClicked="emitConfirmButton()"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.custom-divide {
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid #d1d5db;
|
|
||||||
}
|
|
||||||
.bottom-position {
|
|
||||||
top: -20px;
|
|
||||||
right: 50%;
|
|
||||||
transform: translateX(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-container {
|
|
||||||
@apply flex flex-col items-center justify-center gap-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
@apply text-white text-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blur-container {
|
|
||||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 mt-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { useUser } from "@/composables/useUser";
|
||||||
|
import CustomButton from "@/components/ui/CustomButton.vue";
|
||||||
|
import { postProcessKey } from "@/utils/pixKeyFormat";
|
||||||
|
import { TokenEnum } from "@/model/NetworkEnum";
|
||||||
|
import { getTokenImage } from "@/utils/imagesPath";
|
||||||
|
import { useOnboard } from "@web3-onboard/vue";
|
||||||
|
import ChevronDown from "@/assets/chevron.svg";
|
||||||
|
|
||||||
|
// Import the bank list
|
||||||
|
import bankList from "@/utils/files/isbpList.json";
|
||||||
|
import type { Participant } from "@/utils/bbPay";
|
||||||
|
|
||||||
|
// Define Bank interface
|
||||||
|
interface Bank {
|
||||||
|
ISPB: string;
|
||||||
|
longName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// html references
|
||||||
|
const tokenDropdownRef = ref<any>(null);
|
||||||
|
const formRef = ref<HTMLFormElement | null>(null);
|
||||||
|
|
||||||
|
// Reactive state
|
||||||
|
const user = useUser();
|
||||||
|
const { walletAddress, selectedToken } = user;
|
||||||
|
|
||||||
|
const offer = ref<string>("");
|
||||||
|
const identification = ref<string>("");
|
||||||
|
const account = ref<string>("");
|
||||||
|
const branch = ref<string>("");
|
||||||
|
const accountType = ref<string>("");
|
||||||
|
const selectTokenToggle = ref<boolean>(false);
|
||||||
|
const savingsVariation = ref<string>("");
|
||||||
|
const errors = ref<{ [key: string]: string }>({});
|
||||||
|
|
||||||
|
// Bank selection
|
||||||
|
const bankSearchQuery = ref<string>("");
|
||||||
|
const showBankList = ref<boolean>(false);
|
||||||
|
const selectedBank = ref<Bank | null>(null);
|
||||||
|
|
||||||
|
const filteredBanks = computed(() => {
|
||||||
|
if (!bankSearchQuery.value) return [];
|
||||||
|
return bankList
|
||||||
|
.filter((bank) =>
|
||||||
|
bank.longName.toLowerCase().includes(bankSearchQuery.value.toLowerCase())
|
||||||
|
)
|
||||||
|
.slice(0, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBankSelect = (bank: Bank) => {
|
||||||
|
selectedBank.value = bank;
|
||||||
|
bankSearchQuery.value = bank.longName;
|
||||||
|
showBankList.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits(["approveTokens"]);
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const connectAccount = async (): Promise<void> => {
|
||||||
|
const { connectWallet } = useOnboard();
|
||||||
|
await connectWallet();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e: Event): void => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const processedIdentification = postProcessKey(identification.value);
|
||||||
|
|
||||||
|
const data: Participant = {
|
||||||
|
offer: offer.value,
|
||||||
|
chainID: user.network.value.id,
|
||||||
|
identification: processedIdentification,
|
||||||
|
bankIspb: selectedBank.value?.ISPB,
|
||||||
|
accountType: accountType.value,
|
||||||
|
account: account.value,
|
||||||
|
branch: branch.value,
|
||||||
|
savingsVariation: savingsVariation.value || "",
|
||||||
|
};
|
||||||
|
|
||||||
|
emit("approveTokens", data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Token selection
|
||||||
|
const openTokenSelection = (): void => {
|
||||||
|
selectTokenToggle.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectedToken = (token: TokenEnum): void => {
|
||||||
|
user.setSelectedToken(token);
|
||||||
|
selectTokenToggle.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="page w-full">
|
||||||
|
<div class="text-container">
|
||||||
|
<span
|
||||||
|
class="text font-extrabold sm:text-5xl text-3xl sm:max-w-[29rem] max-w-[20rem]"
|
||||||
|
>
|
||||||
|
Venda cripto e receba em Pix
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="text font-medium sm:text-base text-xs sm:max-w-[28rem] max-w-[30rem] sm:tracking-normal tracking-wide"
|
||||||
|
>
|
||||||
|
Digite sua oferta, informe a chave Pix, selecione a rede, aprove o envio
|
||||||
|
da transação e confirme sua oferta.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form ref="formRef" @submit="handleSubmit" class="main-container">
|
||||||
|
<!-- Offer input -->
|
||||||
|
<div
|
||||||
|
class="flex justify-between items-center w-full bg-white sm:px-10 px-6 py-5 rounded-lg border-y-10 gap-4"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
v-model="offer"
|
||||||
|
class="border-none outline-none text-gray-900 sm:w-fit w-3/4 flex-grow"
|
||||||
|
:class="{
|
||||||
|
'!font-medium': offer !== undefined && offer !== '',
|
||||||
|
'text-xl': offer !== undefined && offer !== '',
|
||||||
|
}"
|
||||||
|
min="0.01"
|
||||||
|
max="999999999.99"
|
||||||
|
pattern="\d+(\.\d{0,2})?"
|
||||||
|
placeholder="Digite sua oferta (mínimo R$0,01)"
|
||||||
|
step=".01"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="relative overflow-visible">
|
||||||
|
<button
|
||||||
|
ref="tokenDropdownRef"
|
||||||
|
class="flex flex-row items-center p-2 bg-gray-300 hover:bg-gray-200 focus:outline-indigo-800 focus:outline-2 rounded-3xl min-w-fit gap-2 transition-colors"
|
||||||
|
@click="openTokenSelection()"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Token image"
|
||||||
|
class="sm:w-fit w-4"
|
||||||
|
:src="getTokenImage(selectedToken)"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="text-gray-900 sm:text-lg text-md font-medium"
|
||||||
|
id="token"
|
||||||
|
>
|
||||||
|
{{ selectedToken }}
|
||||||
|
</span>
|
||||||
|
<ChevronDown
|
||||||
|
class="text-gray-900 pr-4 sm:pr-0 transition-all duration-500 ease-in-out"
|
||||||
|
:class="{ 'scale-y-[-1]': selectTokenToggle }"
|
||||||
|
alt="Chevron Down"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<transition name="dropdown">
|
||||||
|
<div
|
||||||
|
v-if="selectTokenToggle"
|
||||||
|
class="mt-2 text-gray-900 absolute right-0 z-50 w-full min-w-max"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="bg-white rounded-xl z-10 border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="token in TokenEnum"
|
||||||
|
:key="token"
|
||||||
|
class="flex menu-button gap-2 px-4 cursor-pointer hover:bg-gray-300 transition-colors"
|
||||||
|
@click="handleSelectedToken(token)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:alt="token + ' logo'"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
:src="getTokenImage(token)"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="text-gray-900 py-4 text-end font-semibold text-sm"
|
||||||
|
>
|
||||||
|
{{ token }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- CPF or CNPJ input -->
|
||||||
|
<div
|
||||||
|
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="identification"
|
||||||
|
maxlength="14"
|
||||||
|
:pattern="'^\\d{11}$|^\\d{14}$'"
|
||||||
|
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||||
|
:class="{ 'text-xl font-medium': identification }"
|
||||||
|
placeholder="Digite seu CPF ou CNPJ (somente números)"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- Bank selection -->
|
||||||
|
<div
|
||||||
|
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||||
|
>
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="bankSearchQuery"
|
||||||
|
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||||
|
:class="{ 'text-xl font-medium': bankSearchQuery }"
|
||||||
|
placeholder="Buscar banco"
|
||||||
|
@focus="showBankList = true"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="showBankList && filteredBanks.length > 0"
|
||||||
|
class="absolute top-full left-0 right-0 mt-1 bg-white border border-gray-200 rounded-md shadow-lg z-50 max-h-60 overflow-y-auto"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="bank in filteredBanks"
|
||||||
|
:key="bank.ISPB"
|
||||||
|
class="px-4 py-2 hover:bg-gray-100 cursor-pointer transition-colors"
|
||||||
|
@click="handleBankSelect(bank)"
|
||||||
|
>
|
||||||
|
<div class="text-sm font-medium text-gray-900">
|
||||||
|
{{ bank.longName }}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-500">ISPB: {{ bank.ISPB }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span v-if="errors.bank" class="text-red-500 text-sm mt-2">{{
|
||||||
|
errors.bank
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<!-- Account and Branch inputs -->
|
||||||
|
<div
|
||||||
|
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||||
|
>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="account"
|
||||||
|
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||||
|
:class="{ 'text-xl font-medium': account }"
|
||||||
|
placeholder="Número da conta"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="branch"
|
||||||
|
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||||
|
:class="{ 'text-xl font-medium': branch }"
|
||||||
|
placeholder="Agência"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Account Type Selection -->
|
||||||
|
<div
|
||||||
|
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||||
|
>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<select
|
||||||
|
v-model="accountType"
|
||||||
|
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<option value="" disabled selected>Tipo de conta</option>
|
||||||
|
<option value="1">Conta Corrente</option>
|
||||||
|
<option value="2">Conta Poupança</option>
|
||||||
|
<option value="3">Conta Salário</option>
|
||||||
|
<option value="4">Conta Pré-Paga</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Savings Account Variation -->
|
||||||
|
<Transition name="resize">
|
||||||
|
<input
|
||||||
|
v-if="accountType === '2'"
|
||||||
|
type="text"
|
||||||
|
v-model="savingsVariation"
|
||||||
|
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||||
|
:class="{ 'text-xl font-medium': savingsVariation }"
|
||||||
|
placeholder="Variação da poupança"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
<!-- Action buttons -->
|
||||||
|
<CustomButton v-if="walletAddress" type="submit" text="Aprovar tokens" />
|
||||||
|
<CustomButton
|
||||||
|
v-else
|
||||||
|
text="Conectar carteira"
|
||||||
|
@buttonClicked="connectAccount()"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.custom-divide {
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
.bottom-position {
|
||||||
|
top: -20px;
|
||||||
|
right: 50%;
|
||||||
|
transform: translateX(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
@apply flex flex-col items-center justify-center w-full mt-16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-container {
|
||||||
|
@apply flex flex-col items-center justify-center gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
@apply text-white text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
@apply sm:text-lg text-sm;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
import { useUser } from "@/composables/useUser";
|
||||||
|
import CustomButton from "@/components/ui/CustomButton.vue";
|
||||||
import { debounce } from "@/utils/debounce";
|
import { debounce } from "@/utils/debounce";
|
||||||
import { decimalCount } from "@/utils/decimalCount";
|
import { decimalCount } from "@/utils/decimalCount";
|
||||||
|
import { getTokenImage } from "@/utils/imagesPath";
|
||||||
|
import { useOnboard } from "@web3-onboard/vue";
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const user = useUser();
|
||||||
|
const { walletAddress } = user;
|
||||||
|
|
||||||
// Reactive state
|
// Reactive state
|
||||||
const tokenValue = ref<number>(0);
|
const tokenValue = ref<number>(0);
|
||||||
@@ -13,6 +20,12 @@ const validDecimals = ref<boolean>(true);
|
|||||||
// Emits
|
// Emits
|
||||||
const emit = defineEmits(["tokenBuy"]);
|
const emit = defineEmits(["tokenBuy"]);
|
||||||
|
|
||||||
|
// Blockchain methods
|
||||||
|
const connectAccount = async (): Promise<void> => {
|
||||||
|
const { connectWallet } = useOnboard();
|
||||||
|
await connectWallet();
|
||||||
|
};
|
||||||
|
|
||||||
// Debounce methods
|
// Debounce methods
|
||||||
const handleInputEvent = (event: any): void => {
|
const handleInputEvent = (event: any): void => {
|
||||||
const { value } = event.target;
|
const { value } = event.target;
|
||||||
@@ -39,7 +52,7 @@ const handleInputEvent = (event: any): void => {
|
|||||||
tokens após realizar o Pix</span
|
tokens após realizar o Pix</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="blur-container">
|
<div class="main-container">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||||
>
|
>
|
||||||
@@ -58,8 +71,14 @@ const handleInputEvent = (event: any): void => {
|
|||||||
<div
|
<div
|
||||||
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
||||||
>
|
>
|
||||||
<img alt="Token image" class="w-fit" src="@/assets/brz.svg" />
|
<img
|
||||||
<span class="text-gray-900 text-lg w-fit" id="brz">BRZ</span>
|
alt="Token image"
|
||||||
|
class="w-fit"
|
||||||
|
:src="getTokenImage(user.selectedToken.value)"
|
||||||
|
/>
|
||||||
|
<span class="text-gray-900 text-lg w-fit" id="token">{{
|
||||||
|
user.selectedToken
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -71,13 +90,13 @@ const handleInputEvent = (event: any): void => {
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<img
|
<img
|
||||||
alt="Polygon image"
|
alt="Polygon image"
|
||||||
src="@/assets/polygon.svg"
|
src="@/assets/polygon.svg?url"
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
alt="Ethereum image"
|
alt="Ethereum image"
|
||||||
src="@/assets/ethereum.svg"
|
src="@/assets/ethereum.svg?url"
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
/>
|
/>
|
||||||
@@ -94,10 +113,17 @@ const handleInputEvent = (event: any): void => {
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CustomButton
|
<CustomButton
|
||||||
|
v-if="walletAddress"
|
||||||
:text="'Conectar carteira'"
|
:text="'Conectar carteira'"
|
||||||
@buttonClicked="emit('tokenBuy')"
|
@buttonClicked="emit('tokenBuy')"
|
||||||
/>
|
/>
|
||||||
|
<CustomButton
|
||||||
|
v-if="!walletAddress"
|
||||||
|
:text="'Conectar carteira'"
|
||||||
|
@buttonClicked="connectAccount()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -125,14 +151,6 @@ const handleInputEvent = (event: any): void => {
|
|||||||
@apply text-white text-center;
|
@apply text-white text-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blur-container {
|
|
||||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
import CustomButton from "@/components/ui/CustomButton.vue";
|
||||||
|
|
||||||
// Emits
|
// Emits
|
||||||
const emit = defineEmits(["sendNetwork"]);
|
const emit = defineEmits(["sendNetwork"]);
|
||||||
|
|
||||||
// props and store references
|
// props and store references
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pixKey: String,
|
sellerId: String,
|
||||||
offer: Number,
|
offer: Number,
|
||||||
|
selectedToken: String,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -19,24 +20,26 @@ const props = defineProps({
|
|||||||
>Envie sua oferta para a rede
|
>Envie sua oferta para a rede
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="text text-xl font-normal sm:text-base text-xs sm:max-w-[30rem] max-w-[22rem]"
|
class="text font-normal sm:text-base text-xs sm:max-w-[30rem] max-w-[22rem]"
|
||||||
>Após a confirmação sua oferta estará disponível para outros usuários.
|
>Após a confirmação sua oferta estará disponível para outros usuários.
|
||||||
Caso deseje retirar a oferta, será necessário aguardar 24h para receber
|
Caso deseje retirar a oferta, será necessário aguardar 24h para receber
|
||||||
os tokens de volta.</span
|
os tokens de volta.</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="blur-container">
|
<div class="main-container">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p>Tokens ofertados</p>
|
<p>Tokens ofertados</p>
|
||||||
<p class="text-2xl text-gray-900">{{ props.offer }} BRZ</p>
|
<p class="text-2xl text-gray-900">
|
||||||
|
{{ props.offer }} {{ props.selectedToken }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<p>Chave Pix</p>
|
<p>Chave Pix</p>
|
||||||
<p class="text-xl text-gray-900 break-words">
|
<p class="text-xl text-gray-900 break-words">
|
||||||
{{ props.pixKey }}
|
{{ props.sellerId }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
@@ -72,18 +75,10 @@ p {
|
|||||||
@apply text-white text-center;
|
@apply text-white text-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blur-container {
|
|
||||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 sm:w-1/3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-deposit-info {
|
.last-deposit-info {
|
||||||
@apply font-medium text-base;
|
@apply font-medium text-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
|
|||||||
@@ -1,186 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from "vue";
|
|
||||||
import CustomButton from "../CustomButton/CustomButton.vue";
|
|
||||||
import { debounce } from "@/utils/debounce";
|
|
||||||
import { decimalCount } from "@/utils/decimalCount";
|
|
||||||
import { pixFormatValidation, postProcessKey } from "@/utils/pixKeyFormat";
|
|
||||||
import { useEtherStore } from "@/store/ether";
|
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { connectProvider } from "@/blockchain/provider";
|
|
||||||
|
|
||||||
// Reactive state
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
const { walletAddress } = storeToRefs(etherStore);
|
|
||||||
|
|
||||||
const offer = ref<string>("");
|
|
||||||
const pixKey = ref<string>("");
|
|
||||||
|
|
||||||
const enableSelectButton = ref<boolean>(false);
|
|
||||||
const hasLiquidity = ref<boolean>(true);
|
|
||||||
const validDecimals = ref<boolean>(true);
|
|
||||||
const validPixFormat = ref<boolean>(true);
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(["approveTokens"]);
|
|
||||||
|
|
||||||
// Debounce methods
|
|
||||||
const handleInputEvent = (event: any): void => {
|
|
||||||
const { value } = event.target;
|
|
||||||
|
|
||||||
offer.value = value;
|
|
||||||
|
|
||||||
if (decimalCount(offer.value) > 2) {
|
|
||||||
validDecimals.value = false;
|
|
||||||
enableSelectButton.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
validDecimals.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePixKeyInputEvent = (event: any): void => {
|
|
||||||
const { value } = event.target;
|
|
||||||
|
|
||||||
pixKey.value = value;
|
|
||||||
|
|
||||||
if (pixFormatValidation(pixKey.value)) {
|
|
||||||
validPixFormat.value = true;
|
|
||||||
enableSelectButton.value = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
enableSelectButton.value = false;
|
|
||||||
validPixFormat.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleButtonClick = async (
|
|
||||||
offer: string,
|
|
||||||
pixKey: string
|
|
||||||
): Promise<void> => {
|
|
||||||
const postProcessedPixKey = postProcessKey(pixKey);
|
|
||||||
if (walletAddress.value)
|
|
||||||
emit("approveTokens", { offer, postProcessedPixKey });
|
|
||||||
else await connectProvider();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="page w-full">
|
|
||||||
<div class="text-container">
|
|
||||||
<span
|
|
||||||
class="text font-extrabold sm:text-5xl text-3xl sm:max-w-[29rem] max-w-[20rem]"
|
|
||||||
>Venda cripto e receba em Pix</span
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="text font-medium sm:text-base text-xs sm:max-w-[28rem] max-w-[30rem] sm:tracking-normal tracking-wide"
|
|
||||||
>Digite sua oferta, informe a chave Pix, selecione a rede, aprove o
|
|
||||||
envio da transação e confirme sua oferta.</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="blur-container">
|
|
||||||
<div class="backdrop-blur -z-10 w-full h-full"></div>
|
|
||||||
<div
|
|
||||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-5 rounded-lg border-y-10"
|
|
||||||
>
|
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
v-model="offer"
|
|
||||||
class="border-none outline-none text-gray-900 sm:w-fit w-3/4"
|
|
||||||
v-bind:class="{
|
|
||||||
'font-semibold': offer != undefined,
|
|
||||||
'text-xl': offer != undefined,
|
|
||||||
}"
|
|
||||||
@input="debounce(handleInputEvent, 500)($event)"
|
|
||||||
placeholder="Digite sua oferta"
|
|
||||||
step=".01"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Token image"
|
|
||||||
class="sm:w-fit w-4"
|
|
||||||
src="@/assets/brz.svg"
|
|
||||||
/>
|
|
||||||
<span class="text-gray-900 w-fit" id="brz"> BRZ </span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex pt-2 justify-center" v-if="!validDecimals">
|
|
||||||
<span class="text-red-500 font-normal text-sm"
|
|
||||||
>Por favor utilize no máximo 2 casas decimais</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="flex pt-2 justify-center" v-else-if="!hasLiquidity">
|
|
||||||
<span class="text-red-500 font-normal text-sm"
|
|
||||||
>Atualmente não há liquidez nas redes para sua demanda</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-8 rounded-lg border-y-10"
|
|
||||||
>
|
|
||||||
<div class="flex justify-between w-full items-center">
|
|
||||||
<input
|
|
||||||
@input="debounce(handlePixKeyInputEvent, 500)($event)"
|
|
||||||
type="text"
|
|
||||||
v-model="pixKey"
|
|
||||||
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-fit"
|
|
||||||
placeholder="Digite a chave Pix"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex pt-2 justify-center" v-if="!validPixFormat">
|
|
||||||
<span class="text-red-500 font-normal text-sm"
|
|
||||||
>Por favor utilize telefone, CPF ou CNPJ</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<CustomButton
|
|
||||||
:text="walletAddress ? 'Aprovar tokens' : 'Conectar Carteira'"
|
|
||||||
:isDisabled="!validDecimals || !validPixFormat"
|
|
||||||
@buttonClicked="handleButtonClick(offer, pixKey)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.custom-divide {
|
|
||||||
width: 100%;
|
|
||||||
border-bottom: 1px solid #d1d5db;
|
|
||||||
}
|
|
||||||
.bottom-position {
|
|
||||||
top: -20px;
|
|
||||||
right: 50%;
|
|
||||||
transform: translateX(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-container {
|
|
||||||
@apply flex flex-col items-center justify-center gap-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
@apply text-white text-center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blur-container {
|
|
||||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 mt-10 w-auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button,
|
|
||||||
input[type="number"]::-webkit-outer-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
@apply sm:text-lg text-sm;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,35 +1,64 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from "pinia";
|
import { ref, watch } from "vue";
|
||||||
import { useEtherStore } from "@/store/ether";
|
import { useUser } from "@/composables/useUser";
|
||||||
import { ref } from "vue";
|
|
||||||
import { onClickOutside } from "@vueuse/core";
|
import { onClickOutside } from "@vueuse/core";
|
||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
import { getNetworkImage } from "@/utils/imagesPath";
|
||||||
import { connectProvider, requestNetworkChange } from "@/blockchain/provider";
|
import { Networks } from "@/config/networks";
|
||||||
import ethereumImage from "@/assets/ethereum.svg";
|
import { useOnboard } from "@web3-onboard/vue";
|
||||||
import polygonImage from "@/assets/polygon.svg";
|
|
||||||
import rootstockImage from "@/assets/rootstock.svg";
|
|
||||||
|
|
||||||
// Store reference
|
import ChevronDown from "@/assets/chevronDown.svg";
|
||||||
const etherStore = useEtherStore();
|
import TwitterIcon from "@/assets/twitterIcon.svg";
|
||||||
|
import LinkedinIcon from "@/assets/linkedinIcon.svg";
|
||||||
|
import GithubIcon from "@/assets/githubIcon.svg";
|
||||||
|
import { connectProvider } from "@/blockchain/provider";
|
||||||
|
import { DEFAULT_NETWORK } from "@/config/networks";
|
||||||
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
|
|
||||||
const { walletAddress, sellerView } = storeToRefs(etherStore);
|
// Use the new composable
|
||||||
|
const user = useUser();
|
||||||
|
const { walletAddress, sellerView, network } = user;
|
||||||
|
|
||||||
const menuOpenToggle = ref<boolean>(false);
|
const menuOpenToggle = ref<boolean>(false);
|
||||||
const menuHoverToggle = ref<boolean>(false);
|
|
||||||
|
|
||||||
const infoMenuOpenToggle = ref<boolean>(false);
|
const infoMenuOpenToggle = ref<boolean>(false);
|
||||||
const currencyMenuOpenToggle = ref<boolean>(false);
|
const currencyMenuOpenToggle = ref<boolean>(false);
|
||||||
const currencyMenuHoverToggle = ref<boolean>(false);
|
|
||||||
const infoMenuRef = ref<any>(null);
|
const infoMenuRef = ref<any>(null);
|
||||||
const walletAddressRef = ref<any>(null);
|
const walletAddressRef = ref<any>(null);
|
||||||
const currencyRef = ref<any>(null);
|
const currencyRef = ref<any>(null);
|
||||||
|
|
||||||
//Methods
|
const { connectedWallet, connectedChain, setChain, disconnectWallet } =
|
||||||
const connectMetaMask = async (): Promise<void> => {
|
useOnboard();
|
||||||
await connectProvider();
|
|
||||||
|
const connnectWallet = async (): Promise<void> => {
|
||||||
|
const { connectWallet } = useOnboard();
|
||||||
|
await connectWallet();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
watch(connectedWallet, async (newVal: any) => {
|
||||||
|
connectProvider(newVal.provider);
|
||||||
|
const addresses = await newVal.provider.request({ method: "eth_accounts" });
|
||||||
|
user.setWalletAddress(addresses.shift());
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(connectedChain, (newVal: any) => {
|
||||||
|
// Check if connected chain is valid, otherwise default to Sepolia
|
||||||
|
if (
|
||||||
|
!newVal ||
|
||||||
|
!Object.values(Networks).some(
|
||||||
|
(network) => network.id === Number(newVal.id)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
"Invalid or unsupported network detected, defaulting to Sepolia"
|
||||||
|
);
|
||||||
|
user.setNetwork(DEFAULT_NETWORK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
user.setNetworkById(newVal?.id);
|
||||||
|
});
|
||||||
|
|
||||||
const formatWalletAddress = (): string => {
|
const formatWalletAddress = (): string => {
|
||||||
|
if (!walletAddress.value)
|
||||||
|
throw new Error("Wallet not connected");
|
||||||
const walletAddressLength = walletAddress.value.length;
|
const walletAddressLength = walletAddress.value.length;
|
||||||
const initialText = walletAddress.value.substring(0, 5);
|
const initialText = walletAddress.value.substring(0, 5);
|
||||||
const finalText = walletAddress.value.substring(
|
const finalText = walletAddress.value.substring(
|
||||||
@@ -39,41 +68,36 @@ const formatWalletAddress = (): string => {
|
|||||||
return `${initialText}...${finalText}`;
|
return `${initialText}...${finalText}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const disconnectUser = (): void => {
|
const disconnectUser = async (): Promise<void> => {
|
||||||
etherStore.setWalletAddress("");
|
user.setWalletAddress(null);
|
||||||
|
await disconnectWallet({ label: connectedWallet.value?.label || "" });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
window.location.reload();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeMenu = (): void => {
|
const closeMenu = (): void => {
|
||||||
menuOpenToggle.value = false;
|
menuOpenToggle.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const networkChange = async (network: NetworkEnum): Promise<void> => {
|
const networkChange = async (network: NetworkConfig): Promise<void> => {
|
||||||
currencyMenuOpenToggle.value = false;
|
currencyMenuOpenToggle.value = false;
|
||||||
const change = await requestNetworkChange(network);
|
const chainId = network.id.toString(16)
|
||||||
if (change) etherStore.setNetworkName(network);
|
try {
|
||||||
};
|
await setChain({
|
||||||
|
chainId: `0x${chainId}`,
|
||||||
const getNetworkImage = (networkName: NetworkEnum): string => {
|
wallet: connectedWallet.value?.label || "",
|
||||||
let validImages = {
|
});
|
||||||
Ethereum: ethereumImage,
|
user.setNetwork(network);
|
||||||
Polygon: polygonImage,
|
} catch (error) {
|
||||||
Rootstock: rootstockImage,
|
console.log("Error changing network", error);
|
||||||
Localhost: ethereumImage,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return validImages[networkName];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickOutside(walletAddressRef, () => {
|
onClickOutside(walletAddressRef, () => {
|
||||||
menuHoverToggle.value = false;
|
|
||||||
menuOpenToggle.value = false;
|
menuOpenToggle.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
onClickOutside(currencyRef, () => {
|
onClickOutside(currencyRef, () => {
|
||||||
currencyMenuOpenToggle.value = false;
|
currencyMenuOpenToggle.value = false;
|
||||||
currencyMenuHoverToggle.value = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onClickOutside(infoMenuRef, () => {
|
onClickOutside(infoMenuRef, () => {
|
||||||
@@ -86,30 +110,55 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
<RouterLink :to="'/'" class="default-button">
|
<RouterLink :to="'/'" class="default-button">
|
||||||
<img
|
<img
|
||||||
alt="P2Pix logo"
|
alt="P2Pix logo"
|
||||||
class="logo lg-view"
|
class="logo hidden md:inline-block"
|
||||||
src="@/assets/logo.svg"
|
width="200"
|
||||||
width="75"
|
|
||||||
height="75"
|
height="75"
|
||||||
|
src="@/assets/logo.svg?url"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
alt="P2Pix logo"
|
alt="P2Pix logo"
|
||||||
class="logo sm-view w-10/12"
|
class="logo inline-block md:hidden w-10/12"
|
||||||
src="@/assets/logo2.svg"
|
|
||||||
width="40"
|
width="40"
|
||||||
height="40"
|
height="40"
|
||||||
|
src="@/assets/logo2.svg?url"
|
||||||
/>
|
/>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
<div class="flex sm:gap-4 gap-2 items-center">
|
<div class="flex sm:gap-4 gap-2 items-center">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
<button
|
||||||
|
ref="infoMenuRef"
|
||||||
|
class="default-button hidden md:inline-block cursor-pointer"
|
||||||
|
@click="
|
||||||
|
[
|
||||||
|
(infoMenuOpenToggle = !infoMenuOpenToggle),
|
||||||
|
(menuOpenToggle = false),
|
||||||
|
(currencyMenuOpenToggle = false),
|
||||||
|
]
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<h1
|
||||||
|
class="topbar-text topbar-link"
|
||||||
|
:style="{
|
||||||
|
'border-bottom': infoMenuOpenToggle
|
||||||
|
? '2px solid white'
|
||||||
|
: '2px solid transparent',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
Menu
|
||||||
|
</h1>
|
||||||
|
</button>
|
||||||
|
<transition name="dropdown">
|
||||||
<div
|
<div
|
||||||
v-show="infoMenuOpenToggle"
|
v-show="infoMenuOpenToggle"
|
||||||
class="mt-10 absolute w-full text-gray-900 lg-view"
|
class="mt-10 absolute w-full text-gray-900 hidden lg:inline-block"
|
||||||
>
|
>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<div class="bg-white rounded-md z-10 -left-32 w-52">
|
<div class="bg-white rounded-md z-10 -left-36 w-52">
|
||||||
<div class="menu-button gap-2 px-4 rounded-md cursor-pointer">
|
<div class="menu-button gap-2 px-4 rounded-md cursor-pointer">
|
||||||
<span class="text-gray-900 py-4 text-end font-semibold text-sm">
|
<span
|
||||||
|
class="text-gray-900 py-4 text-end font-semibold text-sm"
|
||||||
|
>
|
||||||
Documentação
|
Documentação
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -130,36 +179,37 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
<hr class="w-4/5" />
|
<hr class="w-4/5" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="sm:text-center sm:justify-center ml-8 sm:ml-0 gap-2 px-4 rounded-md float-right"
|
class="sm:text-center sm:justify-center ml-8 sm:ml-0 gap-2 px-4 rounded-md"
|
||||||
>
|
>
|
||||||
<div class="redirect_button flex mr-4">
|
<div class="redirect_button flex justify-around">
|
||||||
<div class="mr-6">
|
<a href="https://www.twitter.com/doiim">
|
||||||
<img
|
<img
|
||||||
alt="Twitter"
|
alt="Twitter"
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
src="@/assets/twitterIcon.svg"
|
src="@/assets/twitterIcon.svg?url"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
onclick="location.href = 'https://www.twitter.com/doiim';"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</a>
|
||||||
<div class="mr-6">
|
<a href="https://www.linkedin.com/company/doiim/">
|
||||||
<img
|
<img
|
||||||
alt="Discord"
|
alt="LinkedIn"
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
src="@/assets/discordIcon.svg"
|
src="@/assets/linkedinIcon.svg?url"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
|
href="https://www.linkedin.com/company/doiim/"
|
||||||
/>
|
/>
|
||||||
</div>
|
</a>
|
||||||
|
<a href="https://www.github.com/doiim">
|
||||||
<img
|
<img
|
||||||
alt="Github"
|
alt="Github"
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
src="@/assets/githubIcon.svg"
|
src="@/assets/githubIcon.svg?url"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
onclick="location.href = 'https://github.com/doiim';"
|
|
||||||
/>
|
/>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
@@ -168,53 +218,37 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
</transition>
|
||||||
ref="infoMenuRef"
|
|
||||||
class="default-button lg-view cursor-pointer"
|
|
||||||
@click="
|
|
||||||
[
|
|
||||||
(infoMenuOpenToggle = !infoMenuOpenToggle),
|
|
||||||
(menuOpenToggle = false),
|
|
||||||
(currencyMenuOpenToggle = false),
|
|
||||||
]
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<h1
|
|
||||||
class="text-gray-50 font-semibold sm:text-base"
|
|
||||||
:style="{
|
|
||||||
'border-bottom': infoMenuOpenToggle
|
|
||||||
? '2px solid white'
|
|
||||||
: 'transparent',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
Menu
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
:to="'/faq'"
|
:to="'/faq'"
|
||||||
v-if="!walletAddress"
|
v-if="!walletAddress"
|
||||||
class="default-button sm-view"
|
class="default-button inline-block md:hidden"
|
||||||
>
|
>
|
||||||
FAQ
|
FAQ
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
:to="sellerView ? '/' : '/seller'"
|
:to="sellerView ? '/' : '/seller'"
|
||||||
class="default-button sm:whitespace-normal whitespace-nowrap lg-view"
|
class="default-button whitespace-nowrap w-40 sm:w-44 md:w-36 hidden md:inline-block"
|
||||||
>
|
>
|
||||||
|
<div class="topbar-text topbar-link text-center mx-auto inline-block">
|
||||||
{{ sellerView ? "Quero comprar" : "Quero vender" }}
|
{{ sellerView ? "Quero comprar" : "Quero vender" }}
|
||||||
|
</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
:to="sellerView ? '/' : '/seller'"
|
:to="sellerView ? '/' : '/seller'"
|
||||||
v-if="!walletAddress"
|
v-if="!walletAddress"
|
||||||
class="default-button sm:whitespace-normal whitespace-nowrap sm-view"
|
class="default-button sm:whitespace-normal whitespace-nowrap inline-block md:hidden w-40 sm:w-44 md:w-36"
|
||||||
>
|
>
|
||||||
|
<div class="topbar-text topbar-link text-center mx-auto inline-block">
|
||||||
{{ sellerView ? "Quero comprar" : "Quero vender" }}
|
{{ sellerView ? "Quero comprar" : "Quero vender" }}
|
||||||
|
</div>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<div class="flex flex-col" v-if="walletAddress">
|
<div class="flex flex-col relative" v-if="walletAddress">
|
||||||
<div
|
<div
|
||||||
ref="currencyRef"
|
ref="currencyRef"
|
||||||
class="group top-bar-info cursor-pointer hover:bg-white h-10"
|
class="top-bar-info cursor-pointer h-10 group hover:bg-gray-50 transition-all duration-500 ease-in-out"
|
||||||
|
:class="{ 'bg-gray-50': currencyMenuOpenToggle }"
|
||||||
@click="
|
@click="
|
||||||
[
|
[
|
||||||
(currencyMenuOpenToggle = !currencyMenuOpenToggle),
|
(currencyMenuOpenToggle = !currencyMenuOpenToggle),
|
||||||
@@ -222,103 +256,55 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
(infoMenuOpenToggle = false),
|
(infoMenuOpenToggle = false),
|
||||||
]
|
]
|
||||||
"
|
"
|
||||||
@mouseover="currencyMenuHoverToggle = true"
|
|
||||||
@mouseout="currencyMenuHoverToggle = false"
|
|
||||||
:style="{
|
|
||||||
backgroundColor: currencyMenuOpenToggle
|
|
||||||
? '#F9F9F9'
|
|
||||||
: currencyMenuHoverToggle
|
|
||||||
? '#F9F9F9'
|
|
||||||
: 'transparent',
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Choosed network image"
|
alt="Choosed network image"
|
||||||
:src="getNetworkImage(etherStore.networkName)"
|
:src="getNetworkImage(network.name)"
|
||||||
height="24"
|
height="24"
|
||||||
width="24"
|
width="24"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="default-text group-hover:text-gray-900 lg-view"
|
class="default-text hidden sm:inline-block text-gray-50 group-hover:text-gray-900 transition-all duration-500 ease-in-out whitespace-nowrap text-ellipsis overflow-hidden"
|
||||||
:style="{
|
:class="{ '!text-gray-900': currencyMenuOpenToggle }"
|
||||||
color: currencyMenuOpenToggle
|
|
||||||
? '#000000'
|
|
||||||
: currencyMenuHoverToggle
|
|
||||||
? '#000000'
|
|
||||||
: 'rgb(249 250 251)',
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
{{ etherStore.networkName }}
|
{{
|
||||||
|
user.network.value.name || "Invalid Chain"
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<img
|
<div
|
||||||
class="text-gray-900"
|
class="transition-all duration-500 ease-in-out mt-1"
|
||||||
v-if="!currencyMenuHoverToggle && !currencyMenuOpenToggle"
|
:class="{ 'scale-y-[-1]': currencyMenuOpenToggle }"
|
||||||
alt="Chevron Down"
|
>
|
||||||
src="@/assets/chevronDown.svg"
|
<ChevronDown
|
||||||
/>
|
viewBox="0 0 16 16"
|
||||||
<img
|
class="h-4 w-4 text-white group-hover:text-gray-900"
|
||||||
v-if="currencyMenuOpenToggle"
|
:class="{ '!text-gray-900': currencyMenuOpenToggle }"
|
||||||
alt="Chevron Up"
|
|
||||||
src="@/assets/chevronUp.svg"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-if="currencyMenuHoverToggle && !currencyMenuOpenToggle"
|
|
||||||
alt="Chevron Down Black"
|
|
||||||
src="@/assets/chevronDownBlack.svg"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition name="dropdown">
|
||||||
<div
|
<div
|
||||||
v-show="currencyMenuOpenToggle"
|
v-show="currencyMenuOpenToggle"
|
||||||
class="mt-10 pl-3 absolute w-full text-gray-900 lg-view"
|
class="mt-10 absolute text-gray-900 min-w-max left-0 hidden md:inline-block"
|
||||||
>
|
>
|
||||||
<div class="mt-2">
|
|
||||||
<div class="bg-white rounded-md z-10">
|
|
||||||
<div
|
<div
|
||||||
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
|
class="mt-2 bg-white rounded-md border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
||||||
@click="networkChange(NetworkEnum.rootstock)"
|
>
|
||||||
|
<div
|
||||||
|
v-for="network in Networks"
|
||||||
|
:key="network.id"
|
||||||
|
class="menu-button p-4 gap-2 cursor-pointer hover:bg-gray-200 flex items-center !justify-start whitespace-nowrap transition-colors duration-150 ease-in-out"
|
||||||
|
@click="networkChange(network)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Rootstock image"
|
:alt="network.name + ' image'"
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
src="@/assets/rootstock.svg"
|
:src="getNetworkImage(network.name)"
|
||||||
|
class="mr-2 ml-1"
|
||||||
/>
|
/>
|
||||||
<span class="text-gray-900 py-4 text-end font-semibold text-sm">
|
<span class="text-gray-900 font-semibold text-sm">
|
||||||
Rootstock
|
{{ network.name }}
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex justify-center">
|
|
||||||
<hr class="w-4/5" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
|
|
||||||
@click="networkChange(NetworkEnum.ethereum)"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Ethereum image"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
src="@/assets/ethereum.svg"
|
|
||||||
/>
|
|
||||||
<span class="text-gray-900 py-4 text-end font-semibold text-sm">
|
|
||||||
Ethereum
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex justify-center">
|
|
||||||
<hr class="w-4/5" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
|
|
||||||
@click="networkChange(NetworkEnum.polygon)"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Polygon image"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
src="@/assets/polygon.svg"
|
|
||||||
/>
|
|
||||||
<span class="text-gray-900 py-4 text-end font-semibold text-sm">
|
|
||||||
Polygon
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
@@ -326,29 +312,30 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
v-if="!walletAddress"
|
v-if="!walletAddress"
|
||||||
class="border-amber-500 border-2 rounded default-button lg-view"
|
class="border-amber-500 border-2 rounded default-button hidden md:inline-block"
|
||||||
@click="connectMetaMask()"
|
@click="connnectWallet()"
|
||||||
>
|
>
|
||||||
Conectar carteira
|
Conectar carteira
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
v-if="!walletAddress"
|
v-if="!walletAddress"
|
||||||
class="border-amber-500 border-2 rounded default-button sm-view"
|
class="border-amber-500 border-2 rounded default-button inline-block md:hidden"
|
||||||
@click="connectMetaMask()"
|
@click="connnectWallet()"
|
||||||
>
|
>
|
||||||
Conectar
|
Conectar
|
||||||
</button>
|
</button>
|
||||||
<div v-if="walletAddress" class="account-info">
|
<div v-if="walletAddress" class="account-info">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col relative">
|
||||||
<div
|
<div
|
||||||
ref="walletAddressRef"
|
ref="walletAddressRef"
|
||||||
class="top-bar-info cursor-pointer h-10"
|
class="top-bar-info cursor-pointer h-10 group hover:bg-gray-50 transition-all duration-500 ease-in-out"
|
||||||
|
:class="{ 'bg-gray-50': menuOpenToggle }"
|
||||||
@click="
|
@click="
|
||||||
[
|
[
|
||||||
(menuOpenToggle = !menuOpenToggle),
|
(menuOpenToggle = !menuOpenToggle),
|
||||||
@@ -356,74 +343,61 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
(infoMenuOpenToggle = false),
|
(infoMenuOpenToggle = false),
|
||||||
]
|
]
|
||||||
"
|
"
|
||||||
@mouseover="menuHoverToggle = true"
|
|
||||||
@mouseout="menuHoverToggle = false"
|
|
||||||
:style="{
|
|
||||||
backgroundColor: menuOpenToggle
|
|
||||||
? '#F9F9F9'
|
|
||||||
: menuHoverToggle
|
|
||||||
? '#F9F9F9'
|
|
||||||
: 'transparent',
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<img alt="Account image" src="@/assets/account.svg" />
|
<img alt="Account image" src="@/assets/account.svg?url" />
|
||||||
<span
|
<span
|
||||||
class="default-text"
|
class="default-text text-gray-50 group-hover:text-gray-900 transition-all duration-500 ease-in-out truncate text-ellipsis"
|
||||||
:style="{
|
:class="{ '!text-gray-900': menuOpenToggle }"
|
||||||
color: menuOpenToggle
|
|
||||||
? '#000000'
|
|
||||||
: menuHoverToggle
|
|
||||||
? '#000000'
|
|
||||||
: 'rgb(249 250 251)',
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
{{ formatWalletAddress() }}
|
{{ formatWalletAddress() }}
|
||||||
</span>
|
</span>
|
||||||
<img
|
<div
|
||||||
class="text-gray-900"
|
class="transition-all duration-500 ease-in-out mt-1"
|
||||||
v-if="!menuHoverToggle && !menuOpenToggle"
|
:class="{ 'scale-y-[-1]': menuOpenToggle }"
|
||||||
alt="Chevron Down"
|
>
|
||||||
src="@/assets/chevronDown.svg"
|
<ChevronDown
|
||||||
/>
|
viewBox="0 0 16 16"
|
||||||
<img
|
class="h-4 w-4 text-white group-hover:text-gray-900"
|
||||||
v-if="menuOpenToggle"
|
:class="{ '!text-gray-900': menuOpenToggle }"
|
||||||
alt="Chevron Up"
|
|
||||||
src="@/assets/chevronUp.svg"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-if="menuHoverToggle && !menuOpenToggle"
|
|
||||||
alt="Chevron Down Black"
|
|
||||||
src="@/assets/chevronDownBlack.svg"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition name="dropdown">
|
||||||
<div
|
<div
|
||||||
v-show="menuOpenToggle"
|
v-show="menuOpenToggle"
|
||||||
class="mt-10 absolute w-full text-gray-900 lg-view"
|
class="mt-10 absolute w-full text-gray-900 hidden md:inline-block"
|
||||||
>
|
>
|
||||||
<div class="pl-4 mt-2">
|
<div class="pl-4 mt-2">
|
||||||
<div class="bg-white rounded-md z-10">
|
<div
|
||||||
<div class="menu-button" @click="closeMenu()">
|
class="bg-white rounded-md z-10 border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
||||||
<RouterLink to="/manage_bids" class="redirect_button">
|
>
|
||||||
|
<RouterLink
|
||||||
|
to="/manage_bids"
|
||||||
|
class="redirect_button menu-button"
|
||||||
|
@click="closeMenu()"
|
||||||
|
>
|
||||||
Gerenciar Ofertas
|
Gerenciar Ofertas
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
|
||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
<hr class="w-4/5" />
|
<hr class="w-4/5" />
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-button" @click="disconnectUser">
|
<RouterLink
|
||||||
<RouterLink to="/" class="redirect_button">
|
to="/"
|
||||||
|
class="redirect_button menu-button"
|
||||||
|
@click="disconnectUser"
|
||||||
|
>
|
||||||
Desconectar
|
Desconectar
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="menuOpenToggle"
|
v-show="menuOpenToggle"
|
||||||
class="mobile-menu fixed w-4/5 text-gray-900 sm-view"
|
class="mobile-menu fixed w-4/5 text-gray-900 inline-block md:hidden"
|
||||||
>
|
>
|
||||||
<div class="pl-4 mt-2 h-full">
|
<div class="pl-4 mt-2 h-full">
|
||||||
<div class="bg-white rounded-md z-10 h-full">
|
<div class="bg-white rounded-md z-10 h-full">
|
||||||
@@ -454,92 +428,54 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
<hr class="w-4/5" />
|
<hr class="w-4/5" />
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-button pb-10">
|
<div class="redirect_button mx-10">
|
||||||
<div class="redirect_button flex">
|
<a
|
||||||
<img
|
href="https://www.twitter.com/doiim/"
|
||||||
alt="Twitter"
|
target="_blank"
|
||||||
width="20"
|
rel="noreferrer"
|
||||||
height="20"
|
>
|
||||||
src="@/assets/twitterIcon.svg"
|
<TwitterIcon alt="Twitter" width="20" height="20" />
|
||||||
class="mr-6"
|
</a>
|
||||||
onclick="location.href = 'https://www.twitter.com/doiim';"
|
<a
|
||||||
/>
|
href="https://www.linkedin.com/company/doiim/"
|
||||||
<img
|
target="_blank"
|
||||||
alt="Discord"
|
rel="noreferrer"
|
||||||
width="20"
|
>
|
||||||
height="20"
|
<LinkedinIcon alt="LinkedIn" width="20" height="20" />
|
||||||
src="@/assets/discordIcon.svg"
|
</a>
|
||||||
class="mr-6"
|
<a
|
||||||
/>
|
href="https://github.com/doiim/"
|
||||||
<img
|
target="_blank"
|
||||||
alt="Github"
|
rel="noreferrer"
|
||||||
width="20"
|
>
|
||||||
height="20"
|
<GithubIcon alt="Github" width="20" height="20" />
|
||||||
src="@/assets/githubIcon.svg"
|
</a>
|
||||||
onclick="location.href = 'https://github.com/doiim';"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-show="currencyMenuOpenToggle"
|
v-show="currencyMenuOpenToggle"
|
||||||
class="mobile-menu fixed w-4/5 text-gray-900 sm-view"
|
class="mobile-menu fixed w-4/5 text-gray-900 inline-block md:hidden"
|
||||||
>
|
>
|
||||||
<div class="pl-4 mt-2 h-full">
|
<div class="pl-4 mt-2 h-full">
|
||||||
<div class="bg-white rounded-md z-10 h-full">
|
<div class="bg-white rounded-md z-10 h-full">
|
||||||
<div
|
<div
|
||||||
class="menu-button gap-2 sm:px-4 rounded-md cursor-pointer py-2"
|
v-for="network in Networks"
|
||||||
@click="networkChange(NetworkEnum.rootstock)"
|
:key="network.id"
|
||||||
|
class="menu-button gap-2 sm:px-4 rounded-md cursor-pointer py-2 px-4"
|
||||||
|
@click="networkChange(network)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Rootstock image"
|
:alt="network.name + 'image'"
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
src="@/assets/rootstock.svg"
|
:src="getNetworkImage(network.name)"
|
||||||
/>
|
/>
|
||||||
<span class="text-gray-900 py-4 text-end font-bold text-sm">
|
<span class="text-gray-900 py-4 text-end font-bold text-sm">
|
||||||
Rootstock
|
{{ network.name }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex justify-center pb-12">
|
|
||||||
<hr class="w-4/5" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-button gap-2 sm:px-4 rounded-md cursor-pointer py-2"
|
|
||||||
@click="networkChange(NetworkEnum.ethereum)"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Ethereum image"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
src="@/assets/ethereum.svg"
|
|
||||||
/>
|
|
||||||
<span class="text-gray-900 py-4 text-end font-bold text-sm">
|
|
||||||
Ethereum
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex justify-center">
|
|
||||||
<hr class="w-4/5" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="menu-button gap-2 sm:px-4 rounded-md cursor-pointer py-2"
|
|
||||||
@click="networkChange(NetworkEnum.polygon)"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Polygon image"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
src="@/assets/polygon.svg"
|
|
||||||
/>
|
|
||||||
<span class="text-gray-900 py-4 text-end font-bold text-sm">
|
|
||||||
Polygon
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex justify-center pb-12">
|
|
||||||
<hr class="w-4/5" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -555,6 +491,13 @@ header {
|
|||||||
@apply sm:px-4 px-3 py-2 rounded text-gray-50 font-semibold sm:text-base text-xs hover:bg-transparent;
|
@apply sm:px-4 px-3 py-2 rounded text-gray-50 font-semibold sm:text-base text-xs hover:bg-transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topbar-text {
|
||||||
|
@apply text-gray-50 font-semibold sm:text-base mt-0.5 transition-all duration-500;
|
||||||
|
}
|
||||||
|
.topbar-link {
|
||||||
|
@apply mt-0.5 hover:border-b-2 border-b-2 border-transparent hover:!border-white;
|
||||||
|
}
|
||||||
|
|
||||||
.account-info {
|
.account-info {
|
||||||
@apply flex items-center gap-6;
|
@apply flex items-center gap-6;
|
||||||
}
|
}
|
||||||
@@ -564,42 +507,24 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.top-bar-info {
|
.top-bar-info {
|
||||||
@apply flex justify-between gap-2 px-4 py-2 border-amber-500 border-2 sm:rounded rounded-lg items-center;
|
@apply flex justify-between items-center gap-2 px-4 py-2 border-amber-500 border-2 sm:rounded rounded-lg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redirect_button {
|
.redirect_button {
|
||||||
@apply py-5 text-gray-900 sm:font-semibold font-bold sm:text-xs text-sm w-full;
|
@apply py-5 px-4 text-gray-900 flex sm:font-semibold font-bold sm:text-xs text-sm justify-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-button {
|
.menu-button {
|
||||||
@apply flex sm:text-center sm:justify-center hover:bg-gray-200 ml-8 sm:ml-0;
|
@apply flex sm:text-center sm:justify-center hover:bg-gray-200 hover:rounded-lg w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
@apply bg-gray-200 rounded;
|
@apply bg-gray-200 rounded;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lg-view {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm-view {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu {
|
.mobile-menu {
|
||||||
margin-top: 1400px;
|
margin-top: 1400px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 500px) {
|
|
||||||
.lg-view {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sm-view {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/* eslint-disable no-undef */
|
|
||||||
import { shallowMount } from "@vue/test-utils";
|
|
||||||
import TopBar from "../TopBar.vue";
|
|
||||||
import { useEtherStore } from "../../../store/ether";
|
|
||||||
|
|
||||||
import { createPinia, setActivePinia } from "pinia";
|
|
||||||
|
|
||||||
describe("TopBar.vue", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
setActivePinia(createPinia());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render connect wallet button", () => {
|
|
||||||
const wrapper = shallowMount(TopBar);
|
|
||||||
expect(wrapper.html()).toContain("Conectar carteira");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render button to change to seller view when in buyer screen", () => {
|
|
||||||
const wrapper = shallowMount(TopBar);
|
|
||||||
expect(wrapper.html()).toContain("Quero vender");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render button to change to seller view when in buyer screen", () => {
|
|
||||||
const etherStore = useEtherStore();
|
|
||||||
etherStore.setSellerView(true);
|
|
||||||
const wrapper = shallowMount(TopBar);
|
|
||||||
expect(wrapper.html()).toContain("Quero comprar");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render the P2Pix logo correctly", () => {
|
|
||||||
const wrapper = shallowMount(TopBar);
|
|
||||||
const img = wrapper.findAll(".logo");
|
|
||||||
expect(img.length).toBe(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, computed } from "vue";
|
||||||
|
import { TokenEnum } from "@/model/NetworkEnum";
|
||||||
|
import { decimalCount } from "@/utils/decimalCount";
|
||||||
|
import { debounce } from "@/utils/debounce";
|
||||||
|
import TokenSelector from "./TokenSelector.vue";
|
||||||
|
import ErrorMessage from "./ErrorMessage.vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: number;
|
||||||
|
selectedToken: TokenEnum;
|
||||||
|
placeholder?: string;
|
||||||
|
showTokenSelector?: boolean;
|
||||||
|
showConversion?: boolean;
|
||||||
|
conversionRate?: number;
|
||||||
|
minValue?: number;
|
||||||
|
maxValue?: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
placeholder: "0",
|
||||||
|
showTokenSelector: true,
|
||||||
|
showConversion: true,
|
||||||
|
conversionRate: 1,
|
||||||
|
minValue: 0,
|
||||||
|
disabled: false,
|
||||||
|
required: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"update:modelValue": [value: number];
|
||||||
|
"update:selectedToken": [token: TokenEnum];
|
||||||
|
error: [message: string | null];
|
||||||
|
valid: [isValid: boolean];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const inputValue = ref<string>(String(props.modelValue || ""));
|
||||||
|
const validDecimals = ref(true);
|
||||||
|
const validRange = ref(true);
|
||||||
|
|
||||||
|
const convertedValue = computed(() => {
|
||||||
|
return (props.modelValue * props.conversionRate).toFixed(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorMessage = computed(() => {
|
||||||
|
if (!validDecimals.value) {
|
||||||
|
return "Por favor utilize no máximo 2 casas decimais";
|
||||||
|
}
|
||||||
|
if (!validRange.value) {
|
||||||
|
if (props.minValue && props.modelValue < props.minValue) {
|
||||||
|
return `Valor mínimo: ${props.minValue}`;
|
||||||
|
}
|
||||||
|
if (props.maxValue && props.modelValue > props.maxValue) {
|
||||||
|
return `Valor máximo: ${props.maxValue}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isValid = computed(() => {
|
||||||
|
return validDecimals.value && validRange.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleInput = (event: Event) => {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
const value = target.value;
|
||||||
|
inputValue.value = value;
|
||||||
|
|
||||||
|
const numValue = Number(value);
|
||||||
|
|
||||||
|
// Validar decimais
|
||||||
|
if (decimalCount(value) > 2) {
|
||||||
|
validDecimals.value = false;
|
||||||
|
emit("error", "Por favor utilize no máximo 2 casas decimais");
|
||||||
|
emit("valid", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
validDecimals.value = true;
|
||||||
|
|
||||||
|
// Validar range
|
||||||
|
if (props.minValue !== undefined && numValue < props.minValue) {
|
||||||
|
validRange.value = false;
|
||||||
|
emit("error", `Valor mínimo: ${props.minValue}`);
|
||||||
|
emit("valid", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.maxValue !== undefined && numValue > props.maxValue) {
|
||||||
|
validRange.value = false;
|
||||||
|
emit("error", `Valor máximo: ${props.maxValue}`);
|
||||||
|
emit("valid", false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
validRange.value = true;
|
||||||
|
|
||||||
|
emit("update:modelValue", numValue);
|
||||||
|
emit("error", null);
|
||||||
|
emit("valid", true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const debouncedHandleInput = debounce(handleInput, 500);
|
||||||
|
|
||||||
|
const handleTokenChange = (token: TokenEnum) => {
|
||||||
|
emit("update:selectedToken", token);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
if (newVal !== Number(inputValue.value)) {
|
||||||
|
inputValue.value = String(newVal || "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="amount-input-container">
|
||||||
|
<div class="input-row">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
:value="inputValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="disabled"
|
||||||
|
:required="required"
|
||||||
|
class="amount-input"
|
||||||
|
:class="{
|
||||||
|
'font-semibold text-xl': modelValue > 0,
|
||||||
|
'has-error': !isValid,
|
||||||
|
}"
|
||||||
|
step="0.01"
|
||||||
|
@input="debouncedHandleInput"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TokenSelector
|
||||||
|
v-if="showTokenSelector"
|
||||||
|
:model-value="selectedToken"
|
||||||
|
:disabled="disabled"
|
||||||
|
size="md"
|
||||||
|
@update:model-value="handleTokenChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-else class="token-display">
|
||||||
|
{{ selectedToken }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<p v-if="showConversion" class="conversion-text">
|
||||||
|
~ R$ {{ convertedValue }}
|
||||||
|
</p>
|
||||||
|
<slot name="extra-info"></slot>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ErrorMessage
|
||||||
|
v-if="errorMessage"
|
||||||
|
:message="errorMessage"
|
||||||
|
type="error"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.amount-input-container {
|
||||||
|
@apply flex flex-col w-full gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-row {
|
||||||
|
@apply flex justify-between items-center w-full gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-input {
|
||||||
|
@apply border-none outline-none text-lg text-gray-900 flex-1 bg-transparent;
|
||||||
|
appearance: textfield;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-input::-webkit-inner-spin-button,
|
||||||
|
.amount-input::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-input:disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-input.has-error {
|
||||||
|
@apply text-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-display {
|
||||||
|
@apply flex items-center px-3 py-2 bg-gray-300 rounded-3xl min-w-fit text-gray-900 font-medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
@apply w-full border-b border-gray-300 my-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
@apply flex justify-between items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversion-text {
|
||||||
|
@apply text-gray-500 font-normal text-sm;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import bankList from "@/utils/files/isbpList.json";
|
||||||
|
|
||||||
|
export interface Bank {
|
||||||
|
ISPB: string;
|
||||||
|
longName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: string | null;
|
||||||
|
disabled?: boolean;
|
||||||
|
placeholder?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
placeholder: "Busque e selecione seu banco",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"update:modelValue": [value: string];
|
||||||
|
change: [bank: Bank];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const bankItems = computed(() => {
|
||||||
|
return bankList.map((bank) => ({
|
||||||
|
value: bank.ISPB,
|
||||||
|
label: bank.longName,
|
||||||
|
bank: bank,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedItem = computed(() => {
|
||||||
|
if (!props.modelValue) return null;
|
||||||
|
return bankItems.value.find((item) => item.value === props.modelValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchQuery = computed({
|
||||||
|
get: () => selectedItem.value?.label || "",
|
||||||
|
set: (value: string) => {
|
||||||
|
// Handled by input
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredBanks = computed(() => {
|
||||||
|
if (!searchQuery.value) return [];
|
||||||
|
|
||||||
|
const query = searchQuery.value.toLowerCase();
|
||||||
|
return bankList
|
||||||
|
.filter((bank) => bank.longName.toLowerCase().includes(query))
|
||||||
|
.slice(0, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
const showBankList = computed(() => {
|
||||||
|
return filteredBanks.value.length > 0 && searchQuery.value.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectBank = (bank: Bank) => {
|
||||||
|
emit("update:modelValue", bank.ISPB);
|
||||||
|
emit("change", bank);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="bank-selector">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="searchQuery"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:disabled="disabled"
|
||||||
|
class="bank-input"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<transition name="dropdown-fade">
|
||||||
|
<div v-if="showBankList" class="bank-list">
|
||||||
|
<div
|
||||||
|
v-for="bank in filteredBanks"
|
||||||
|
:key="bank.ISPB"
|
||||||
|
class="bank-item"
|
||||||
|
@click="selectBank(bank)"
|
||||||
|
>
|
||||||
|
<span class="bank-name">{{ bank.longName }}</span>
|
||||||
|
<span class="bank-ispb">{{ bank.ISPB }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.bank-selector {
|
||||||
|
@apply relative w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bank-input {
|
||||||
|
@apply w-full px-4 py-3 border-none outline-none rounded-lg bg-white text-gray-900 text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bank-input:focus {
|
||||||
|
@apply ring-2 ring-indigo-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bank-input:disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed bg-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bank-list {
|
||||||
|
@apply absolute top-full left-0 right-0 mt-2 bg-white rounded-lg border border-gray-300 shadow-lg z-50 max-h-64 overflow-y-auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bank-item {
|
||||||
|
@apply flex justify-between items-center px-4 py-3 cursor-pointer hover:bg-gray-100 transition-colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bank-name {
|
||||||
|
@apply text-gray-900 font-medium text-sm flex-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bank-ispb {
|
||||||
|
@apply text-gray-500 text-xs ml-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animação */
|
||||||
|
.dropdown-fade-enter-active,
|
||||||
|
.dropdown-fade-leave-active {
|
||||||
|
@apply transition-all duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-fade-enter-from,
|
||||||
|
.dropdown-fade-leave-to {
|
||||||
|
@apply opacity-0 -translate-y-2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -38,9 +38,9 @@ switch (props.type) {
|
|||||||
<div
|
<div
|
||||||
class="modal-overlay sm:h-12 h-full inset-0 absolute backdrop-blur-sm sm:backdrop-blur-none"
|
class="modal-overlay sm:h-12 h-full inset-0 absolute backdrop-blur-sm sm:backdrop-blur-none"
|
||||||
>
|
>
|
||||||
<div class="modal px-12 pl-72 text-center sm:flex justify-between hidden">
|
<div class="modal px-12 text-center sm:flex justify-between hidden">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="text-black tracking-tighter leading-tight my-2">
|
<p class="text-black tracking-tighter leading-tight py-2">
|
||||||
{{ alertText }}
|
{{ alertText }}
|
||||||
</p>
|
</p>
|
||||||
<button v-if="props.type === 'redirect'" @click="$emit('go-to-lock')">
|
<button v-if="props.type === 'redirect'" @click="$emit('go-to-lock')">
|
||||||
@@ -110,7 +110,6 @@ switch (props.type) {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding-left: v-bind(alertPaddingLeft);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-mobile {
|
.modal-mobile {
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost";
|
||||||
|
export type ButtonSize = "sm" | "md" | "lg" | "xl";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
text: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
variant?: ButtonVariant;
|
||||||
|
size?: ButtonSize;
|
||||||
|
icon?: string;
|
||||||
|
iconPosition?: "left" | "right";
|
||||||
|
fullWidth?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
isDisabled: false,
|
||||||
|
variant: "primary",
|
||||||
|
size: "xl",
|
||||||
|
iconPosition: "left",
|
||||||
|
fullWidth: true,
|
||||||
|
loading: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits(["buttonClicked"]);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (!props.isDisabled && !props.loading) {
|
||||||
|
emit("buttonClicked");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="[
|
||||||
|
'button',
|
||||||
|
`variant-${variant}`,
|
||||||
|
`size-${size}`,
|
||||||
|
{ 'is-disabled': isDisabled || loading, 'full-width': fullWidth },
|
||||||
|
]"
|
||||||
|
:disabled="isDisabled || loading"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<span v-if="loading" class="loader"></span>
|
||||||
|
<template v-else>
|
||||||
|
<img
|
||||||
|
v-if="icon && iconPosition === 'left'"
|
||||||
|
:src="icon"
|
||||||
|
:alt="`${text} icon`"
|
||||||
|
class="button-icon"
|
||||||
|
/>
|
||||||
|
<span class="button-text">{{ text }}</span>
|
||||||
|
<img
|
||||||
|
v-if="icon && iconPosition === 'right'"
|
||||||
|
:src="icon"
|
||||||
|
:alt="`${text} icon`"
|
||||||
|
class="button-icon"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.button {
|
||||||
|
@apply rounded-lg font-semibold transition-all duration-200 cursor-pointer flex items-center justify-center gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover:not(.is-disabled) {
|
||||||
|
@apply transform scale-[1.02];
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.is-disabled {
|
||||||
|
@apply opacity-70 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.full-width {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variantes */
|
||||||
|
.variant-primary {
|
||||||
|
@apply bg-amber-400 text-gray-900 border-2 border-amber-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-primary:hover:not(.is-disabled) {
|
||||||
|
@apply bg-amber-500 border-amber-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-secondary {
|
||||||
|
@apply bg-gray-200 text-gray-900 border-2 border-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-secondary:hover:not(.is-disabled) {
|
||||||
|
@apply bg-gray-300 border-gray-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-outline {
|
||||||
|
@apply bg-transparent text-gray-900 border-2 border-amber-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-outline:hover:not(.is-disabled) {
|
||||||
|
@apply bg-amber-400/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-ghost {
|
||||||
|
@apply bg-transparent text-gray-900 border-2 border-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-ghost:hover:not(.is-disabled) {
|
||||||
|
@apply bg-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tamanhos */
|
||||||
|
.size-sm {
|
||||||
|
@apply px-2 py-1 text-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-sm .button-icon {
|
||||||
|
@apply w-3 h-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-md {
|
||||||
|
@apply px-3 py-2 text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-md .button-icon {
|
||||||
|
@apply w-4 h-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-lg {
|
||||||
|
@apply px-4 py-3 text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-lg .button-icon {
|
||||||
|
@apply w-5 h-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-xl {
|
||||||
|
@apply p-4 text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-xl .button-icon {
|
||||||
|
@apply w-5 h-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
@apply flex-shrink-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text {
|
||||||
|
@apply font-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loader animation */
|
||||||
|
.loader {
|
||||||
|
@apply w-5 h-5 border-2 border-gray-900 border-t-transparent rounded-full animate-spin;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
isRedirectModal: Boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
const modalColor = ref<string>("white");
|
||||||
|
const modalHeight = ref<string>("250px");
|
||||||
|
const pFontSize = ref<string>("16px");
|
||||||
|
|
||||||
|
if (props.isRedirectModal) {
|
||||||
|
modalColor.value = "rgba(251, 191, 36, 1)";
|
||||||
|
modalHeight.value = "150px";
|
||||||
|
pFontSize.value = "20px";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="modal-overlay inset-0 fixed hidden md:block justify-center backdrop-blur-sm sm:backdrop-blur-none"
|
||||||
|
v-if="!isRedirectModal"
|
||||||
|
>
|
||||||
|
<div class="modal px-5 text-center">
|
||||||
|
<p
|
||||||
|
class="text-black tracking-tighter leading-tight my-6 mx-2 text-justify"
|
||||||
|
>
|
||||||
|
<strong>ATENÇÃO!</strong>
|
||||||
|
A transação só será processada após efetuado o pagamento do Pix. Caso
|
||||||
|
contrário não conseguiremos comprovar o seu depósito e não será
|
||||||
|
possível transferir os tokens para sua carteira.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
@click="$emit('close-modal')"
|
||||||
|
class="border-2 border-solid border-amber-400 mt-2"
|
||||||
|
>
|
||||||
|
Entendi
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="modal-overlay inset-0 fixed justify-center backdrop-blur-sm"
|
||||||
|
v-if="isRedirectModal"
|
||||||
|
>
|
||||||
|
<div class="modal px-5 text-center">
|
||||||
|
<p
|
||||||
|
class="text-black text-lg tracking-tighter leading-tight my-6 mx-2 text-justify font-semibold"
|
||||||
|
>
|
||||||
|
Retomar a última compra?
|
||||||
|
</p>
|
||||||
|
<div class="flex justify-around items-center px-2">
|
||||||
|
<button
|
||||||
|
@click="$emit('close-modal')"
|
||||||
|
class="border-2 border-solid border-white-400 mt-2 font-semibold"
|
||||||
|
>
|
||||||
|
Não
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="$emit('go-to-lock')"
|
||||||
|
class="border-2 border-solid border-white-400 mt-2 font-semibold"
|
||||||
|
>
|
||||||
|
Sim
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-overlay {
|
||||||
|
display: flex !important;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background-color: v-bind(modalColor);
|
||||||
|
height: v-bind(modalHeight);
|
||||||
|
width: 300px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
.close {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-img {
|
||||||
|
width: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 28px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: v-bind(pFontSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
color: black;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
<script setup lang="ts" generic="T">
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { onClickOutside } from "@vueuse/core";
|
||||||
|
import ChevronDown from "@/assets/chevronDown.svg";
|
||||||
|
|
||||||
|
export interface DropdownItem<T = any> {
|
||||||
|
value: T;
|
||||||
|
label: string;
|
||||||
|
icon?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
items: DropdownItem<T>[];
|
||||||
|
modelValue: T;
|
||||||
|
placeholder?: string;
|
||||||
|
searchable?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
showIcon?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
placeholder: "Selecione...",
|
||||||
|
searchable: false,
|
||||||
|
disabled: false,
|
||||||
|
size: "md",
|
||||||
|
showIcon: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"update:modelValue": [value: T];
|
||||||
|
change: [value: T];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const isOpen = ref(false);
|
||||||
|
const searchQuery = ref("");
|
||||||
|
const dropdownRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const selectedItem = computed(() => {
|
||||||
|
return props.items.find((item) => item.value === props.modelValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredItems = computed(() => {
|
||||||
|
if (!props.searchable || !searchQuery.value) {
|
||||||
|
return props.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = searchQuery.value.toLowerCase();
|
||||||
|
return props.items.filter((item) =>
|
||||||
|
item.label.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
if (!props.disabled) {
|
||||||
|
isOpen.value = !isOpen.value;
|
||||||
|
if (!isOpen.value) {
|
||||||
|
searchQuery.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectItem = (item: DropdownItem<T>) => {
|
||||||
|
if (!item.disabled) {
|
||||||
|
emit("update:modelValue", item.value);
|
||||||
|
emit("change", item.value);
|
||||||
|
isOpen.value = false;
|
||||||
|
searchQuery.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onClickOutside(dropdownRef, () => {
|
||||||
|
isOpen.value = false;
|
||||||
|
searchQuery.value = "";
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="dropdownRef" class="dropdown-container">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="[
|
||||||
|
'dropdown-trigger',
|
||||||
|
`size-${size}`,
|
||||||
|
{ disabled: disabled, open: isOpen },
|
||||||
|
]"
|
||||||
|
@click="toggleDropdown"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="selectedItem?.icon && showIcon"
|
||||||
|
:src="selectedItem.icon"
|
||||||
|
:alt="selectedItem.label"
|
||||||
|
class="item-icon"
|
||||||
|
/>
|
||||||
|
<span class="selected-text">
|
||||||
|
{{ selectedItem?.label || placeholder }}
|
||||||
|
</span>
|
||||||
|
<ChevronDown
|
||||||
|
class="chevron"
|
||||||
|
:class="{ rotated: isOpen }"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<transition name="dropdown-fade">
|
||||||
|
<div v-if="isOpen" class="dropdown-menu">
|
||||||
|
<input
|
||||||
|
v-if="searchable"
|
||||||
|
v-model="searchQuery"
|
||||||
|
type="text"
|
||||||
|
class="search-input"
|
||||||
|
placeholder="Buscar..."
|
||||||
|
@click.stop
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="items-container">
|
||||||
|
<div
|
||||||
|
v-for="item in filteredItems"
|
||||||
|
:key="String(item.value)"
|
||||||
|
:class="[
|
||||||
|
'dropdown-item',
|
||||||
|
{
|
||||||
|
selected: item.value === modelValue,
|
||||||
|
disabled: item.disabled,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
@click="selectItem(item)"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="item.icon && showIcon"
|
||||||
|
:src="item.icon"
|
||||||
|
:alt="item.label"
|
||||||
|
class="item-icon"
|
||||||
|
/>
|
||||||
|
<span class="item-label">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="filteredItems.length === 0" class="no-results">
|
||||||
|
Nenhum resultado encontrado
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dropdown-container {
|
||||||
|
@apply relative inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger {
|
||||||
|
@apply flex items-center gap-2 bg-gray-300 hover:bg-gray-200 rounded-3xl transition-colors cursor-pointer border-none outline-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger:focus {
|
||||||
|
@apply outline-2 outline-indigo-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger.disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger.disabled:hover {
|
||||||
|
@apply bg-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-sm {
|
||||||
|
@apply px-2 py-1 text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-md {
|
||||||
|
@apply px-3 py-2 text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-lg {
|
||||||
|
@apply px-4 py-3 text-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-icon {
|
||||||
|
@apply sm:w-fit w-4 flex-shrink-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-text {
|
||||||
|
@apply text-gray-900 font-medium min-w-fit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chevron {
|
||||||
|
@apply transition-transform duration-300 invert pr-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chevron.rotated {
|
||||||
|
@apply rotate-180;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
@apply absolute right-0 mt-2 bg-white rounded-xl border border-gray-300 shadow-md z-50 min-w-max w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
@apply w-full px-4 py-3 border-b border-gray-200 outline-none text-gray-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input:focus {
|
||||||
|
@apply border-indigo-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-container {
|
||||||
|
@apply max-h-64 overflow-y-auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
@apply flex items-center gap-2 px-4 py-4 cursor-pointer hover:bg-gray-300 transition-colors text-gray-900 font-semibold text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item.selected {
|
||||||
|
@apply bg-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item.disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item.disabled:hover {
|
||||||
|
@apply bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-label {
|
||||||
|
@apply text-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-results {
|
||||||
|
@apply px-4 py-6 text-center text-gray-500 text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animação */
|
||||||
|
.dropdown-fade-enter-active,
|
||||||
|
.dropdown-fade-leave-active {
|
||||||
|
@apply transition-all duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-fade-enter-from,
|
||||||
|
.dropdown-fade-leave-to {
|
||||||
|
@apply opacity-0 -translate-y-2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
export type ErrorType = "error" | "warning" | "info";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
message: string;
|
||||||
|
type?: ErrorType;
|
||||||
|
centered?: boolean;
|
||||||
|
icon?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
centered: true,
|
||||||
|
icon: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const colorClasses = {
|
||||||
|
error: "text-red-500",
|
||||||
|
warning: "text-amber-500",
|
||||||
|
info: "text-blue-500",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="['error-message-container', { centered: centered }]">
|
||||||
|
<div :class="['error-message', colorClasses[type]]">
|
||||||
|
<span v-if="icon" class="icon">⚠</span>
|
||||||
|
<span class="message">{{ message }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error-message-container {
|
||||||
|
@apply flex w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message-container.centered {
|
||||||
|
@apply justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
@apply font-normal text-sm flex items-center gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@apply text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
@apply leading-tight;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
export type FormCardPadding = "sm" | "md" | "lg";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
padding?: FormCardPadding;
|
||||||
|
fullWidth?: boolean;
|
||||||
|
noBorder?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
padding: "md",
|
||||||
|
fullWidth: true,
|
||||||
|
noBorder: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'form-card',
|
||||||
|
`padding-${padding}`,
|
||||||
|
{ 'full-width': fullWidth, 'no-border': noBorder },
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-card {
|
||||||
|
@apply flex flex-col bg-white rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card:not(.no-border) {
|
||||||
|
@apply border-y-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-card.full-width {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-sm {
|
||||||
|
@apply px-4 py-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-md {
|
||||||
|
@apply sm:px-10 px-6 py-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-lg {
|
||||||
|
@apply px-12 py-8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
export type IconButtonVariant = "primary" | "secondary" | "outline" | "ghost";
|
||||||
|
export type IconButtonSize = "sm" | "md" | "lg";
|
||||||
|
export type IconPosition = "left" | "right";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
text: string;
|
||||||
|
icon?: string;
|
||||||
|
variant?: IconButtonVariant;
|
||||||
|
size?: IconButtonSize;
|
||||||
|
iconPosition?: IconPosition;
|
||||||
|
disabled?: boolean;
|
||||||
|
fullWidth?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
variant: "outline",
|
||||||
|
size: "md",
|
||||||
|
iconPosition: "left",
|
||||||
|
disabled: false,
|
||||||
|
fullWidth: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
click: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (!props.disabled) {
|
||||||
|
emit("click");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="[
|
||||||
|
'icon-button',
|
||||||
|
`variant-${variant}`,
|
||||||
|
`size-${size}`,
|
||||||
|
{ 'is-disabled': disabled, 'full-width': fullWidth },
|
||||||
|
]"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="icon && iconPosition === 'left'"
|
||||||
|
:src="icon"
|
||||||
|
:alt="`${text} icon`"
|
||||||
|
class="button-icon"
|
||||||
|
/>
|
||||||
|
<span class="button-text">{{ text }}</span>
|
||||||
|
<img
|
||||||
|
v-if="icon && iconPosition === 'right'"
|
||||||
|
:src="icon"
|
||||||
|
:alt="`${text} icon`"
|
||||||
|
class="button-icon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icon-button {
|
||||||
|
@apply flex items-center justify-center gap-2 font-medium rounded-lg transition-all duration-200 cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:hover:not(.is-disabled) {
|
||||||
|
@apply transform scale-[1.02];
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button.is-disabled {
|
||||||
|
@apply opacity-60 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button.full-width {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Variantes */
|
||||||
|
.variant-primary {
|
||||||
|
@apply bg-amber-400 text-gray-900 border-2 border-amber-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-primary:hover:not(.is-disabled) {
|
||||||
|
@apply bg-amber-500 border-amber-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-secondary {
|
||||||
|
@apply bg-gray-200 text-gray-900 border-2 border-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-secondary:hover:not(.is-disabled) {
|
||||||
|
@apply bg-gray-300 border-gray-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-outline {
|
||||||
|
@apply bg-transparent text-gray-900 border-2 border-amber-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-outline:hover:not(.is-disabled) {
|
||||||
|
@apply bg-amber-300/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-ghost {
|
||||||
|
@apply bg-transparent text-gray-900 border-2 border-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.variant-ghost:hover:not(.is-disabled) {
|
||||||
|
@apply bg-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tamanhos */
|
||||||
|
.size-sm {
|
||||||
|
@apply px-2 py-1 text-xs;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-sm .button-icon {
|
||||||
|
@apply w-3 h-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-md {
|
||||||
|
@apply px-3 py-2 text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-md .button-icon {
|
||||||
|
@apply w-4 h-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-lg {
|
||||||
|
@apply px-4 py-3 text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-lg .button-icon {
|
||||||
|
@apply w-5 h-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-text {
|
||||||
|
@apply font-semibold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
@apply flex-shrink-0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { useFloating, arrow, offset, flip, shift } from "@floating-ui/vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
text: string;
|
||||||
|
placement?: "top" | "bottom" | "left" | "right";
|
||||||
|
iconSrc?: string;
|
||||||
|
showOnHover?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
placement: "right",
|
||||||
|
iconSrc: "",
|
||||||
|
showOnHover: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const showTooltip = ref<boolean>(false);
|
||||||
|
const reference = ref<HTMLElement | null>(null);
|
||||||
|
const floating = ref<HTMLElement | null>(null);
|
||||||
|
const floatingArrow = ref(null);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
useFloating(reference, floating, {
|
||||||
|
placement: props.placement,
|
||||||
|
middleware: [offset(10), flip(), shift(), arrow({ element: floatingArrow })],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleMouseOver = () => {
|
||||||
|
if (props.showOnHover) {
|
||||||
|
showTooltip.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseOut = () => {
|
||||||
|
if (props.showOnHover) {
|
||||||
|
showTooltip.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleTooltip = () => {
|
||||||
|
if (!props.showOnHover) {
|
||||||
|
showTooltip.value = !showTooltip.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="info-tooltip-container">
|
||||||
|
<img
|
||||||
|
:src="iconSrc || '/src/assets/info.svg'"
|
||||||
|
alt="info icon"
|
||||||
|
class="info-icon"
|
||||||
|
ref="reference"
|
||||||
|
@mouseover="handleMouseOver"
|
||||||
|
@mouseout="handleMouseOut"
|
||||||
|
@click="toggleTooltip"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="showTooltip"
|
||||||
|
role="tooltip"
|
||||||
|
ref="floating"
|
||||||
|
class="tooltip-content"
|
||||||
|
>
|
||||||
|
{{ text }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.info-tooltip-container {
|
||||||
|
@apply relative inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-icon {
|
||||||
|
@apply cursor-pointer transition-opacity hover:opacity-70;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-content {
|
||||||
|
@apply bg-white text-gray-900 font-medium text-xs md:text-sm px-3 py-2 rounded border-2 border-emerald-500 z-50 max-w-xs shadow-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
.tooltip-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ const props = defineProps({
|
|||||||
{{ props.title ? props.title : "Confirme em sua carteira" }}
|
{{ props.title ? props.title : "Confirme em sua carteira" }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="blur-container sm:w-[26rem] w-[20rem]">
|
<div class="main-container max-w-md">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col w-full bg-white sm:px-10 px-4 py-5 rounded-lg border-y-10"
|
class="flex flex-col w-full bg-white sm:px-10 px-4 py-5 rounded-lg border-y-10"
|
||||||
>
|
>
|
||||||
@@ -24,7 +24,7 @@ const props = defineProps({
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Polygon image"
|
alt="Polygon image"
|
||||||
src="@/assets/validating.svg"
|
src="@/assets/validating.svg?url"
|
||||||
width="96"
|
width="96"
|
||||||
height="48"
|
height="48"
|
||||||
/>
|
/>
|
||||||
@@ -60,10 +60,6 @@ const props = defineProps({
|
|||||||
@apply text-white text-center;
|
@apply text-white text-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blur-container {
|
|
||||||
@apply flex flex-col justify-center items-center sm:px-8 px-6 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"] {
|
input[type="number"] {
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import SpinnerComponent from "./SpinnerComponent.vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
message?: string;
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
centered?: boolean;
|
||||||
|
inline?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
message: "Carregando...",
|
||||||
|
size: "md",
|
||||||
|
centered: true,
|
||||||
|
inline: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const sizeMap = {
|
||||||
|
sm: { spinner: "4", text: "text-sm" },
|
||||||
|
md: { spinner: "6", text: "text-base" },
|
||||||
|
lg: { spinner: "8", text: "text-lg" },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'loading-state',
|
||||||
|
{ centered: centered, inline: inline },
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span v-if="message" :class="['loading-message', sizeMap[size].text]">
|
||||||
|
{{ message }}
|
||||||
|
</span>
|
||||||
|
<SpinnerComponent
|
||||||
|
:width="sizeMap[size].spinner"
|
||||||
|
:height="sizeMap[size].spinner"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.loading-state {
|
||||||
|
@apply flex items-center gap-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-state.centered {
|
||||||
|
@apply justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-state.inline {
|
||||||
|
@apply inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-message {
|
||||||
|
@apply text-gray-900 font-normal;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { getNetworkImage } from "@/utils/imagesPath";
|
||||||
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
networks: NetworkConfig[];
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
showLabel?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
size: "md",
|
||||||
|
showLabel: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const sizeMap = {
|
||||||
|
sm: 16,
|
||||||
|
md: 24,
|
||||||
|
lg: 32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const networkData = computed(() => {
|
||||||
|
return props.networks.map((network) => ({
|
||||||
|
network,
|
||||||
|
image: getNetworkImage(network.name),
|
||||||
|
name: network.name,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="network-badges">
|
||||||
|
<div
|
||||||
|
v-for="data in networkData"
|
||||||
|
:key="data.network.id"
|
||||||
|
class="network-badge"
|
||||||
|
:title="data.name"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:alt="`${data.name} logo`"
|
||||||
|
:src="data.image"
|
||||||
|
:width="sizeMap[size]"
|
||||||
|
:height="sizeMap[size]"
|
||||||
|
class="network-icon"
|
||||||
|
/>
|
||||||
|
<span v-if="showLabel" class="network-label">
|
||||||
|
{{ data.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.network-badges {
|
||||||
|
@apply flex gap-2 items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-badge {
|
||||||
|
@apply flex items-center gap-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-icon {
|
||||||
|
@apply flex-shrink-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-label {
|
||||||
|
@apply text-sm font-medium text-gray-900;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { Networks } from "@/config/networks";
|
||||||
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
|
import { getNetworkImage } from "@/utils/imagesPath";
|
||||||
|
import Dropdown, { type DropdownItem } from "./Dropdown.vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: NetworkConfig;
|
||||||
|
disabled?: boolean;
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
availableNetworks?: NetworkConfig[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
size: "md",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"update:modelValue": [value: NetworkConfig];
|
||||||
|
change: [value: NetworkConfig];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const networkItems = computed((): DropdownItem<NetworkConfig>[] => {
|
||||||
|
return Object.values(Networks).map((network) => ({
|
||||||
|
value: network,
|
||||||
|
label: network.name,
|
||||||
|
icon: getNetworkImage(network.name),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (value: NetworkConfig) => {
|
||||||
|
emit("update:modelValue", value);
|
||||||
|
emit("change", value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dropdown
|
||||||
|
:model-value="modelValue"
|
||||||
|
:items="networkItems"
|
||||||
|
:disabled="disabled"
|
||||||
|
:size="size"
|
||||||
|
:show-icon="true"
|
||||||
|
@update:model-value="handleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
export type HeaderSize = "sm" | "md" | "lg";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
size?: HeaderSize;
|
||||||
|
centered?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
size: "lg",
|
||||||
|
centered: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="['page-header', `size-${size}`, { centered: centered }]"
|
||||||
|
>
|
||||||
|
<h1 class="title text-white font-extrabold">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<p v-if="subtitle" class="subtitle text-white font-medium">
|
||||||
|
{{ subtitle }}
|
||||||
|
</p>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page-header {
|
||||||
|
@apply flex flex-col gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header.centered {
|
||||||
|
@apply items-center justify-center text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tamanhos */
|
||||||
|
.size-sm .title {
|
||||||
|
@apply sm:text-2xl text-xl sm:max-w-[20rem] max-w-[16rem];
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-sm .subtitle {
|
||||||
|
@apply sm:text-sm text-xs sm:max-w-[18rem] max-w-[14rem];
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-md .title {
|
||||||
|
@apply sm:text-4xl text-2xl sm:max-w-[28rem] max-w-[22rem];
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-md .subtitle {
|
||||||
|
@apply sm:text-base text-sm sm:max-w-[26rem] max-w-[20rem];
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-lg .title {
|
||||||
|
@apply sm:text-5xl text-3xl sm:max-w-[29rem] max-w-[20rem];
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-lg .subtitle {
|
||||||
|
@apply sm:text-base text-sm sm:max-w-[28rem] max-w-[30rem] sm:tracking-normal tracking-wide;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
export type StatusType = "open" | "expired" | "completed" | "pending";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
status: StatusType;
|
||||||
|
customText?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const statusConfig = computed(() => {
|
||||||
|
const configs: Record<StatusType, { text: string; color: string }> = {
|
||||||
|
open: {
|
||||||
|
text: "Em Aberto",
|
||||||
|
color: "bg-amber-300",
|
||||||
|
},
|
||||||
|
expired: {
|
||||||
|
text: "Expirado",
|
||||||
|
color: "bg-[#94A3B8]",
|
||||||
|
},
|
||||||
|
completed: {
|
||||||
|
text: "Finalizado",
|
||||||
|
color: "bg-emerald-300",
|
||||||
|
},
|
||||||
|
pending: {
|
||||||
|
text: "Pendente",
|
||||||
|
color: "bg-gray-300",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return configs[props.status];
|
||||||
|
});
|
||||||
|
|
||||||
|
const displayText = computed(() => {
|
||||||
|
return props.customText || statusConfig.value.text;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="[statusConfig.color, 'status-badge']">
|
||||||
|
{{ displayText }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.status-badge {
|
||||||
|
@apply text-xs sm:text-base font-medium text-gray-900 rounded-lg text-center px-2 py-1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, onMounted } from "vue";
|
||||||
|
import { useOnboard } from "@web3-onboard/vue";
|
||||||
|
import { Networks } from "@/config/networks";
|
||||||
|
import { useUser } from "@/composables/useUser";
|
||||||
|
|
||||||
|
const { connectedWallet } = useOnboard();
|
||||||
|
const user = useUser();
|
||||||
|
const { network } = user;
|
||||||
|
|
||||||
|
const isWrongNetwork = ref(false);
|
||||||
|
const targetNetworkName = computed(() => network.value.name);
|
||||||
|
|
||||||
|
const checkNetwork = () => {
|
||||||
|
if (connectedWallet.value) {
|
||||||
|
const chainId = connectedWallet.value.chains[0].id;
|
||||||
|
isWrongNetwork.value = Number(chainId) !== network.value.id;
|
||||||
|
} else {
|
||||||
|
isWrongNetwork.value = false; // No wallet connected yet
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const switchNetwork = async () => {
|
||||||
|
try {
|
||||||
|
if (connectedWallet.value && connectedWallet.value.provider) {
|
||||||
|
let chainId = network.value.id.toString(16);
|
||||||
|
await connectedWallet.value.provider.request({
|
||||||
|
method: "wallet_switchEthereumChain",
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
chainId: `0x${chainId}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to switch network:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(checkNetwork);
|
||||||
|
watch(connectedWallet, checkNetwork);
|
||||||
|
watch(network, checkNetwork, { immediate: true });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<transition name="slide-up" appear>
|
||||||
|
<div
|
||||||
|
v-if="isWrongNetwork"
|
||||||
|
class="fixed bottom-0 left-0 right-0 bg-red-500 text-white p-4 flex justify-between items-center z-50"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span class="font-bold">Wrong network!</span>
|
||||||
|
<span> Please switch to {{ targetNetworkName }}.</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="switchNetwork"
|
||||||
|
class="bg-white text-red-500 px-4 py-2 rounded font-bold hover:bg-gray-100 transition-colors"
|
||||||
|
>
|
||||||
|
Switch Network
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.slide-up-enter-active,
|
||||||
|
.slide-up-leave-active {
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-from,
|
||||||
|
.slide-up-leave-to {
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-up-enter-to,
|
||||||
|
.slide-up-leave-from {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { TokenEnum } from "@/model/NetworkEnum";
|
||||||
|
import { getTokenImage } from "@/utils/imagesPath";
|
||||||
|
import Dropdown, { type DropdownItem } from "./Dropdown.vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
modelValue: TokenEnum;
|
||||||
|
disabled?: boolean;
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
disabled: false,
|
||||||
|
size: "md",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"update:modelValue": [value: TokenEnum];
|
||||||
|
change: [value: TokenEnum];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const tokenItems = computed((): DropdownItem<TokenEnum>[] => {
|
||||||
|
return Object.values(TokenEnum).map((token) => ({
|
||||||
|
value: token,
|
||||||
|
label: token,
|
||||||
|
icon: getTokenImage(token),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (value: TokenEnum) => {
|
||||||
|
emit("update:modelValue", value);
|
||||||
|
emit("change", value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dropdown
|
||||||
|
:model-value="modelValue"
|
||||||
|
:items="tokenItems"
|
||||||
|
:disabled="disabled"
|
||||||
|
:size="size"
|
||||||
|
:show-icon="true"
|
||||||
|
@update:model-value="handleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { onClickOutside } from "@vueuse/core";
|
||||||
|
import CustomButton from "./CustomButton.vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
walletAddress: string | null;
|
||||||
|
variant?: "primary" | "secondary" | "outline";
|
||||||
|
showMenu?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
variant: "primary",
|
||||||
|
showMenu: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
connect: [];
|
||||||
|
disconnect: [];
|
||||||
|
viewTransactions: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const menuOpen = ref(false);
|
||||||
|
const menuRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const isConnected = computed(() => {
|
||||||
|
return !!props.walletAddress;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formattedAddress = computed(() => {
|
||||||
|
if (!props.walletAddress) return "";
|
||||||
|
|
||||||
|
const address = props.walletAddress;
|
||||||
|
const length = address.length;
|
||||||
|
const start = address.substring(0, 5);
|
||||||
|
const end = address.substring(length - 4, length);
|
||||||
|
|
||||||
|
return `${start}...${end}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleConnect = () => {
|
||||||
|
emit("connect");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisconnect = () => {
|
||||||
|
menuOpen.value = false;
|
||||||
|
emit("disconnect");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleViewTransactions = () => {
|
||||||
|
menuOpen.value = false;
|
||||||
|
emit("viewTransactions");
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleMenu = () => {
|
||||||
|
if (isConnected.value && props.showMenu) {
|
||||||
|
menuOpen.value = !menuOpen.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onClickOutside(menuRef, () => {
|
||||||
|
menuOpen.value = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="wallet-connect-container">
|
||||||
|
<CustomButton
|
||||||
|
v-if="!isConnected"
|
||||||
|
text="Conectar carteira"
|
||||||
|
:variant="variant"
|
||||||
|
@button-clicked="handleConnect"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-else ref="menuRef" class="wallet-connected">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="wallet-button"
|
||||||
|
@click="toggleMenu"
|
||||||
|
>
|
||||||
|
<span class="wallet-address">{{ formattedAddress }}</span>
|
||||||
|
<div class="wallet-indicator"></div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<transition name="menu-fade">
|
||||||
|
<div v-if="menuOpen && showMenu" class="wallet-menu">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="menu-item"
|
||||||
|
@click="handleViewTransactions"
|
||||||
|
>
|
||||||
|
<span>Ver transações</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="menu-item disconnect"
|
||||||
|
@click="handleDisconnect"
|
||||||
|
>
|
||||||
|
<span>Desconectar</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.wallet-connect-container {
|
||||||
|
@apply relative inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-connected {
|
||||||
|
@apply relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-button {
|
||||||
|
@apply flex items-center gap-3 px-4 py-2 bg-white border-2 border-amber-400 rounded-lg hover:bg-amber-50 transition-colors cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-address {
|
||||||
|
@apply text-gray-900 font-semibold text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-indicator {
|
||||||
|
@apply w-2 h-2 bg-emerald-500 rounded-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-menu {
|
||||||
|
@apply absolute top-full right-0 mt-2 bg-white rounded-lg border border-gray-300 shadow-lg z-50 min-w-[200px] overflow-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
@apply w-full px-4 py-3 text-left text-gray-900 font-medium text-sm hover:bg-gray-100 transition-colors cursor-pointer border-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item.disconnect {
|
||||||
|
@apply text-red-500 hover:bg-red-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animação */
|
||||||
|
.menu-fade-enter-active,
|
||||||
|
.menu-fade-leave-active {
|
||||||
|
@apply transition-all duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-fade-enter-from,
|
||||||
|
.menu-fade-leave-to {
|
||||||
|
@apply opacity-0 -translate-y-2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
|
import type { Participant } from "../utils/bbPay";
|
||||||
|
import type { Address } from "viem"
|
||||||
|
import { DEFAULT_NETWORK, Networks } from "@/config/networks";
|
||||||
|
import { TokenEnum, NetworkConfig } from "@/model/NetworkEnum"
|
||||||
|
|
||||||
|
const walletAddress = ref<Address | null>(null);
|
||||||
|
const balance = ref("");
|
||||||
|
const network = ref(DEFAULT_NETWORK);
|
||||||
|
const selectedToken = ref<TokenEnum>(TokenEnum.BRZ);
|
||||||
|
const loadingLock = ref(false);
|
||||||
|
const sellerView = ref(false);
|
||||||
|
const depositsValidList = ref<ValidDeposit[]>([]);
|
||||||
|
const loadingWalletTransactions = ref(false);
|
||||||
|
const loadingNetworkLiquidity = ref(false);
|
||||||
|
const seller = ref<Participant>({} as Participant);
|
||||||
|
const sellerId = ref("");
|
||||||
|
|
||||||
|
export function useUser() {
|
||||||
|
// Actions become regular functions
|
||||||
|
const setWalletAddress = (address: Address | null) => {
|
||||||
|
walletAddress.value = address;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setBalance = (newBalance: string) => {
|
||||||
|
balance.value = newBalance;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSelectedToken = (token: TokenEnum) => {
|
||||||
|
selectedToken.value = token;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setNetwork = (chain: NetworkConfig) => {
|
||||||
|
network.value = chain;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setNetworkById = (id: string | number) => {
|
||||||
|
let chainId: number;
|
||||||
|
|
||||||
|
if (typeof id === 'string') {
|
||||||
|
// Parse hex string or number string to number
|
||||||
|
if (id.startsWith('0x')) {
|
||||||
|
chainId = parseInt(id, 16);
|
||||||
|
} else {
|
||||||
|
chainId = parseInt(id, 10);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chainId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find network by chain ID
|
||||||
|
const chain = Object.values(Networks).find(n => n.id === chainId);
|
||||||
|
if (chain) {
|
||||||
|
network.value = chain;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLoadingLock = (isLoading: boolean) => {
|
||||||
|
loadingLock.value = isLoading;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSellerView = (view: boolean) => {
|
||||||
|
sellerView.value = view;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setDepositsValidList = (deposits: ValidDeposit[]) => {
|
||||||
|
depositsValidList.value = deposits;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLoadingWalletTransactions = (isLoading: boolean) => {
|
||||||
|
loadingWalletTransactions.value = isLoading;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLoadingNetworkLiquidity = (isLoading: boolean) => {
|
||||||
|
loadingNetworkLiquidity.value = isLoading;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSeller = (newSeller: Participant) => {
|
||||||
|
seller.value = newSeller;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSellerId = (id: string) => {
|
||||||
|
sellerId.value = id;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Getters become computed or regular functions
|
||||||
|
const getValidDepositByWalletAddress = (address: string) => {
|
||||||
|
return depositsValidList.value
|
||||||
|
.filter((deposit) => deposit.seller == address)
|
||||||
|
.sort((a, b) => b.blockNumber - a.blockNumber);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State
|
||||||
|
walletAddress,
|
||||||
|
balance,
|
||||||
|
network,
|
||||||
|
selectedToken,
|
||||||
|
loadingLock,
|
||||||
|
sellerView,
|
||||||
|
depositsValidList,
|
||||||
|
loadingWalletTransactions,
|
||||||
|
loadingNetworkLiquidity,
|
||||||
|
seller,
|
||||||
|
sellerId,
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
setWalletAddress,
|
||||||
|
setBalance,
|
||||||
|
setSelectedToken,
|
||||||
|
setNetwork,
|
||||||
|
setNetworkById,
|
||||||
|
setLoadingLock,
|
||||||
|
setSellerView,
|
||||||
|
setDepositsValidList,
|
||||||
|
setLoadingWalletTransactions,
|
||||||
|
setLoadingNetworkLiquidity,
|
||||||
|
setSeller,
|
||||||
|
setSellerId,
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
getValidDepositByWalletAddress,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { sepolia, rootstockTestnet } from "viem/chains";
|
||||||
|
import { NetworkConfig } from "@/model/NetworkEnum"
|
||||||
|
// TODO: import addresses from p2pix-smart-contracts deployments
|
||||||
|
|
||||||
|
export const Networks: {[key:string]: NetworkConfig} = {
|
||||||
|
sepolia: { ...sepolia,
|
||||||
|
rpcUrls: { default: { http: [import.meta.env.VITE_SEPOLIA_API_URL]}},
|
||||||
|
contracts: { ...sepolia.contracts,
|
||||||
|
p2pix: {address:"0xb7cD135F5eFD9760981e02E2a898790b688939fe"} },
|
||||||
|
tokens: {
|
||||||
|
BRZ: {address:"0x3eBE67A2C7bdB2081CBd34ba3281E90377462289"} },
|
||||||
|
subgraphUrls: [import.meta.env.VITE_SEPOLIA_SUBGRAPH_URL]
|
||||||
|
},
|
||||||
|
rootstockTestnet: { ...rootstockTestnet,
|
||||||
|
rpcUrls: { default: { http: [import.meta.env.VITE_RSK_API_URL]}},
|
||||||
|
contracts: { ...rootstockTestnet.contracts,
|
||||||
|
p2pix: {address:"0x57Dcba05980761169508886eEdc6f5E7EC0411Dc"} },
|
||||||
|
tokens: {
|
||||||
|
BRZ: {address:"0xfE841c74250e57640390f46d914C88d22C51e82e"} },
|
||||||
|
subgraphUrls: [import.meta.env.VITE_RSK_SUBGRAPH_URL]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_NETWORK = Networks.sepolia;
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
import { createPinia } from "pinia";
|
|
||||||
|
|
||||||
import "./assets/main.css";
|
import "./assets/main.css";
|
||||||
|
import "./assets/transitions.css";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(createPinia());
|
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface Bank {
|
||||||
|
COMPE: string;
|
||||||
|
ISPB: string;
|
||||||
|
longName: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import type { Address } from "viem";
|
||||||
|
|
||||||
|
export enum LockStatus { // from DataTypes.sol
|
||||||
|
Inexistent = 0, // Uninitialized Lock
|
||||||
|
Active = 1, // Valid Lock
|
||||||
|
Expired = 2, // Expired Lock
|
||||||
|
Released = 3 // Already released Lock
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
export enum NetworkEnum {
|
import type { Chain, ChainContract } from "viem";
|
||||||
ethereum = "Ethereum",
|
|
||||||
polygon = "Polygon",
|
export enum TokenEnum {
|
||||||
rootstock = "Rootstock",
|
BRZ = 'BRZ',
|
||||||
|
// BRX = 'BRX'
|
||||||
}
|
}
|
||||||
|
export type NetworkConfig = Chain & {
|
||||||
|
tokens: Record<TokenEnum, ChainContract>,
|
||||||
|
subgraphUrls: string[]
|
||||||
|
};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { Pix } from "./Pix";
|
import { Address } from "viem";
|
||||||
|
|
||||||
export type UnreleasedLock = {
|
export type UnreleasedLock = {
|
||||||
lockID: string;
|
lockID: bigint;
|
||||||
pix: Pix;
|
sellerAddress: Address;
|
||||||
|
tokenAddress: Address;
|
||||||
|
amount: number;
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
|
import type { Address } from "viem";
|
||||||
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
|
|
||||||
export type ValidDeposit = {
|
export type ValidDeposit = {
|
||||||
token: string;
|
token: Address;
|
||||||
blockNumber: number;
|
blockNumber: number;
|
||||||
remaining: number;
|
remaining: number;
|
||||||
seller: string;
|
seller: Address;
|
||||||
pixKey: number;
|
participantID: string;
|
||||||
|
network: NetworkConfig;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
};
|
};
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
|
import type { LockStatus } from "@/model/LockStatus"
|
||||||
|
import type { Address } from "viem"
|
||||||
|
|
||||||
export type WalletTransaction = {
|
export type WalletTransaction = {
|
||||||
token: string;
|
token?: Address;
|
||||||
blockNumber: number;
|
blockNumber: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
seller: string;
|
seller: string;
|
||||||
buyer: string;
|
buyer: string;
|
||||||
event: string;
|
event: string;
|
||||||
lockStatus: number;
|
lockStatus?: LockStatus;
|
||||||
transactionHash: string;
|
transactionHash: string;
|
||||||
transactionID?: string;
|
transactionID?: string;
|
||||||
};
|
};
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
import type { Event } from "ethers";
|
|
||||||
import { vi } from "vitest";
|
|
||||||
|
|
||||||
export const MockEvents: Event[] = [
|
|
||||||
{
|
|
||||||
blockNumber: 1,
|
|
||||||
blockHash: "0x8",
|
|
||||||
transactionIndex: 1,
|
|
||||||
removed: false,
|
|
||||||
address: "0x0",
|
|
||||||
data: "0x0",
|
|
||||||
topics: ["0x0", "0x0"],
|
|
||||||
transactionHash: "0x0",
|
|
||||||
logIndex: 1,
|
|
||||||
event: "DepositAdded",
|
|
||||||
eventSignature: "DepositAdded(address,uint256,address,uint256)",
|
|
||||||
args: [
|
|
||||||
"0x0",
|
|
||||||
{
|
|
||||||
type: "BigNumber",
|
|
||||||
hex: "0x00",
|
|
||||||
},
|
|
||||||
"0x0",
|
|
||||||
{
|
|
||||||
type: "BigNumber",
|
|
||||||
hex: "0x6c6b935b8bbd400000",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
getBlock: vi.fn(),
|
|
||||||
removeListener: vi.fn(),
|
|
||||||
getTransaction: vi.fn(),
|
|
||||||
getTransactionReceipt: vi.fn(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 2,
|
|
||||||
blockHash: "0x8",
|
|
||||||
transactionIndex: 2,
|
|
||||||
removed: false,
|
|
||||||
address: "0x0",
|
|
||||||
data: "0x0",
|
|
||||||
topics: ["0x0", "0x0"],
|
|
||||||
transactionHash: "0x0",
|
|
||||||
logIndex: 2,
|
|
||||||
event: "LockAdded",
|
|
||||||
eventSignature: "LockAdded(address,uint256,address,uint256)",
|
|
||||||
args: [
|
|
||||||
"0x0",
|
|
||||||
{
|
|
||||||
type: "BigNumber",
|
|
||||||
hex: "0x00",
|
|
||||||
},
|
|
||||||
"0x0",
|
|
||||||
{
|
|
||||||
type: "BigNumber",
|
|
||||||
hex: "0x6c6b935b8bbd400000",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
getBlock: vi.fn(),
|
|
||||||
removeListener: vi.fn(),
|
|
||||||
getTransaction: vi.fn(),
|
|
||||||
getTransactionReceipt: vi.fn(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 3,
|
|
||||||
blockHash: "0x8",
|
|
||||||
transactionIndex: 3,
|
|
||||||
removed: false,
|
|
||||||
address: "0x0",
|
|
||||||
data: "0x0",
|
|
||||||
topics: ["0x0", "0x0"],
|
|
||||||
transactionHash: "0x0",
|
|
||||||
logIndex: 3,
|
|
||||||
event: "LockReleased",
|
|
||||||
eventSignature: "LockReleased(address,uint256,address,uint256)",
|
|
||||||
args: [
|
|
||||||
"0x0",
|
|
||||||
{
|
|
||||||
type: "BigNumber",
|
|
||||||
hex: "0x00",
|
|
||||||
},
|
|
||||||
"0x0",
|
|
||||||
{
|
|
||||||
type: "BigNumber",
|
|
||||||
hex: "0x6c6b935b8bbd400000",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
getBlock: vi.fn(),
|
|
||||||
removeListener: vi.fn(),
|
|
||||||
getTransaction: vi.fn(),
|
|
||||||
getTransactionReceipt: vi.fn(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 4,
|
|
||||||
blockHash: "0x8",
|
|
||||||
transactionIndex: 4,
|
|
||||||
removed: false,
|
|
||||||
address: "0x0",
|
|
||||||
data: "0x0",
|
|
||||||
topics: ["0x0", "0x0"],
|
|
||||||
transactionHash: "0x0",
|
|
||||||
logIndex: 4,
|
|
||||||
event: "LockReleased",
|
|
||||||
eventSignature: "LockReleased(address,uint256,address,uint256)",
|
|
||||||
args: [
|
|
||||||
"0x0",
|
|
||||||
{
|
|
||||||
type: "BigNumber",
|
|
||||||
hex: "0x00",
|
|
||||||
},
|
|
||||||
"0x0",
|
|
||||||
{
|
|
||||||
type: "BigNumber",
|
|
||||||
hex: "0x6c6b935b8bbd400000",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
getBlock: vi.fn(),
|
|
||||||
removeListener: vi.fn(),
|
|
||||||
getTransaction: vi.fn(),
|
|
||||||
getTransactionReceipt: vi.fn(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import type { ValidDeposit } from "../ValidDeposit";
|
|
||||||
|
|
||||||
export const MockValidDeposits: ValidDeposit[] = [
|
|
||||||
{
|
|
||||||
blockNumber: 1,
|
|
||||||
token: "1",
|
|
||||||
remaining: 70,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
pixKey: 123456789,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 2,
|
|
||||||
token: "2",
|
|
||||||
remaining: 200,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
pixKey: 123456789,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 3,
|
|
||||||
token: "3",
|
|
||||||
remaining: 1250,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
pixKey: 123456789,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 4,
|
|
||||||
token: "4",
|
|
||||||
remaining: 4000,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
pixKey: 123456789,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 5,
|
|
||||||
token: "5",
|
|
||||||
remaining: 2000,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
pixKey: 123456789,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import type { WalletTransaction } from "../WalletTransaction";
|
|
||||||
|
|
||||||
export const MockWalletTransactions: WalletTransaction[] = [
|
|
||||||
{
|
|
||||||
blockNumber: 1,
|
|
||||||
token: "1",
|
|
||||||
amount: 70,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
buyer: "mockedBuyerAddress",
|
|
||||||
event: "Deposit",
|
|
||||||
lockStatus: 0,
|
|
||||||
transactionHash: "1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 2,
|
|
||||||
token: "2",
|
|
||||||
amount: 200,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
buyer: "mockedBuyerAddress",
|
|
||||||
event: "Lock",
|
|
||||||
lockStatus: 1,
|
|
||||||
transactionHash: "2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 3,
|
|
||||||
token: "3",
|
|
||||||
amount: 1250,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
buyer: "mockedBuyerAddress",
|
|
||||||
event: "Release",
|
|
||||||
lockStatus: 2,
|
|
||||||
transactionHash: "3",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 4,
|
|
||||||
token: "4",
|
|
||||||
amount: 4000,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
buyer: "mockedBuyerAddress",
|
|
||||||
event: "Deposit",
|
|
||||||
lockStatus: 0,
|
|
||||||
transactionHash: "4",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
blockNumber: 5,
|
|
||||||
token: "5",
|
|
||||||
amount: 2000,
|
|
||||||
seller: "mockedSellerAddress",
|
|
||||||
buyer: "mockedBuyerAddress",
|
|
||||||
event: "Deposit",
|
|
||||||
lockStatus: 3,
|
|
||||||
transactionHash: "5",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
const defaultConfig = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
};
|
|
||||||
|
|
||||||
const api = axios.create({
|
|
||||||
...defaultConfig,
|
|
||||||
baseURL: import.meta.env.VITE_API_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default api;
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
|
||||||
import { defineStore } from "pinia";
|
|
||||||
|
|
||||||
export const useEtherStore = defineStore("ether", {
|
|
||||||
state: () => ({
|
|
||||||
walletAddress: "",
|
|
||||||
balance: "",
|
|
||||||
networkName: NetworkEnum.ethereum,
|
|
||||||
loadingLock: false,
|
|
||||||
sellerView: false,
|
|
||||||
// Depósitos válidos para compra GOERLI
|
|
||||||
depositsValidListGoerli: [] as ValidDeposit[],
|
|
||||||
// Depósitos válidos para compra MUMBAI
|
|
||||||
depositsValidListMumbai: [] as ValidDeposit[],
|
|
||||||
// Depósitos válidos para compra ROOTSTOCK
|
|
||||||
depositsValidListRootstock: [] as ValidDeposit[],
|
|
||||||
loadingWalletTransactions: false,
|
|
||||||
loadingNetworkLiquidity: false,
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
setWalletAddress(walletAddress: string) {
|
|
||||||
this.walletAddress = walletAddress;
|
|
||||||
},
|
|
||||||
setBalance(balance: string) {
|
|
||||||
this.balance = balance;
|
|
||||||
},
|
|
||||||
setNetworkName(networkName: NetworkEnum) {
|
|
||||||
this.networkName = networkName;
|
|
||||||
},
|
|
||||||
setLoadingLock(isLoadingLock: boolean) {
|
|
||||||
this.loadingLock = isLoadingLock;
|
|
||||||
},
|
|
||||||
setSellerView(sellerView: boolean) {
|
|
||||||
this.sellerView = sellerView;
|
|
||||||
},
|
|
||||||
setDepositsValidListGoerli(depositsValidList: ValidDeposit[]) {
|
|
||||||
this.depositsValidListGoerli = depositsValidList;
|
|
||||||
},
|
|
||||||
setDepositsValidListMumbai(depositsValidList: ValidDeposit[]) {
|
|
||||||
this.depositsValidListMumbai = depositsValidList;
|
|
||||||
},
|
|
||||||
setDepositsValidListRootstock(depositsValidList: ValidDeposit[]) {
|
|
||||||
this.depositsValidListRootstock = depositsValidList;
|
|
||||||
},
|
|
||||||
setLoadingWalletTransactions(isLoadingWalletTransactions: boolean) {
|
|
||||||
this.loadingWalletTransactions = isLoadingWalletTransactions;
|
|
||||||
},
|
|
||||||
setLoadingNetworkLiquidity(isLoadingNetworkLiquidity: boolean) {
|
|
||||||
this.loadingNetworkLiquidity = isLoadingNetworkLiquidity;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// Alterar para integrar com mumbai
|
|
||||||
getters: {
|
|
||||||
getValidDepositByWalletAddress: (state) => {
|
|
||||||
return (walletAddress: string) =>
|
|
||||||
state.depositsValidListGoerli
|
|
||||||
.filter((deposit) => deposit.seller == walletAddress)
|
|
||||||
.sort((a, b) => {
|
|
||||||
return b.blockNumber - a.blockNumber;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import qrcode from "qrcode";
|
|
||||||
import type { QRCodeToDataURLOptions } from "qrcode";
|
|
||||||
import { crc16ccitt } from "crc";
|
|
||||||
import type { Pix } from "@/model/Pix";
|
|
||||||
|
|
||||||
const pix = ({
|
|
||||||
pixKey,
|
|
||||||
merchantCity = "city",
|
|
||||||
merchantName = "name",
|
|
||||||
value,
|
|
||||||
message,
|
|
||||||
cep,
|
|
||||||
transactionId = "***",
|
|
||||||
currency = 986,
|
|
||||||
countryCode = "BR",
|
|
||||||
}: Pix) => {
|
|
||||||
const payloadKeyString = generatePixKey(pixKey, message);
|
|
||||||
|
|
||||||
const payload: string[] = [
|
|
||||||
formatEMV("00", "01"), //Payload Format Indicator
|
|
||||||
formatEMV("26", payloadKeyString), // Merchant Account Information
|
|
||||||
formatEMV("52", "0000"), //Merchant Category Code
|
|
||||||
formatEMV("53", String(currency)), // Transaction Currency
|
|
||||||
];
|
|
||||||
|
|
||||||
if (String(value) === "0") {
|
|
||||||
value = undefined;
|
|
||||||
}
|
|
||||||
if (value) {
|
|
||||||
payload.push(formatEMV("54", value.toFixed(2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.push(formatEMV("58", countryCode.toUpperCase())); // Country Code
|
|
||||||
payload.push(formatEMV("59", merchantName)); // Merchant Name
|
|
||||||
payload.push(formatEMV("60", merchantCity)); // Merchant City
|
|
||||||
|
|
||||||
if (cep) {
|
|
||||||
payload.push(formatEMV("61", cep)); // Postal Code
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.push(formatEMV("62", formatEMV("05", transactionId))); // Additional Data Field Template
|
|
||||||
|
|
||||||
payload.push("6304");
|
|
||||||
|
|
||||||
const stringPayload = payload.join("");
|
|
||||||
const crcResult = crc16ccitt(stringPayload)
|
|
||||||
.toString(16)
|
|
||||||
.toUpperCase()
|
|
||||||
.padStart(4, "0");
|
|
||||||
|
|
||||||
const payloadPIX = `${stringPayload}${crcResult}`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
payload: (): string => payloadPIX,
|
|
||||||
base64QrCode: (options?: QRCodeToDataURLOptions): Promise<string> =>
|
|
||||||
qrcode.toDataURL(payloadPIX, options),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const generatePixKey = (pixKey: string, message?: string): string => {
|
|
||||||
const payload: string[] = [
|
|
||||||
formatEMV("00", "BR.GOV.BCB.PIX"),
|
|
||||||
formatEMV("01", pixKey),
|
|
||||||
];
|
|
||||||
if (message) {
|
|
||||||
payload.push(formatEMV("02", message));
|
|
||||||
}
|
|
||||||
return payload.join("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatEMV = (id: string, param: string): string => {
|
|
||||||
const len = param?.length?.toString().padStart(2, "0");
|
|
||||||
return `${id}${len}${param}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { pix };
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { it, expect, vi, type Mock } from "vitest";
|
|
||||||
import { debounce } from "../debounce";
|
|
||||||
|
|
||||||
vi.useFakeTimers();
|
|
||||||
|
|
||||||
describe("debounce function test", () => {
|
|
||||||
let mockFunction: Mock;
|
|
||||||
let debounceFunction: Function;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockFunction = vi.fn();
|
|
||||||
debounceFunction = debounce(mockFunction, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("debounce function will be executed just once", () => {
|
|
||||||
for (let i = 0; i < 100; i++) {
|
|
||||||
debounceFunction();
|
|
||||||
}
|
|
||||||
|
|
||||||
vi.runAllTimers();
|
|
||||||
|
|
||||||
expect(mockFunction).toBeCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { it, expect } from "vitest";
|
|
||||||
import { decimalCount } from "../decimalCount";
|
|
||||||
|
|
||||||
describe("decimalCount function test", () => {
|
|
||||||
it("decimalCount should return length 1 of decimal", () => {
|
|
||||||
expect(decimalCount("4.1")).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("decimalCount should return length 0 because no decimal found", () => {
|
|
||||||
expect(decimalCount("5")).toEqual(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { MockValidDeposits } from "@/model/mock/ValidDepositMock";
|
|
||||||
import { it, expect, vi } from "vitest";
|
|
||||||
import { verifyNetworkLiquidity } from "../networkLiquidity";
|
|
||||||
|
|
||||||
vi.useFakeTimers();
|
|
||||||
|
|
||||||
describe("verifyNetworkLiquidity function test", () => {
|
|
||||||
it("verifyNetworkLiquidity should return an element from valid deposit list when searching for other deposits", () => {
|
|
||||||
const liquidityElement = verifyNetworkLiquidity(
|
|
||||||
MockValidDeposits[0].remaining,
|
|
||||||
"strangeWalletAddress",
|
|
||||||
MockValidDeposits
|
|
||||||
);
|
|
||||||
expect(liquidityElement).toEqual(MockValidDeposits[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("verifyNetworkLiquidity should return undefined when all deposits on valid deposit list match connected wallet addres", () => {
|
|
||||||
const liquidityElement = verifyNetworkLiquidity(
|
|
||||||
MockValidDeposits[0].remaining,
|
|
||||||
MockValidDeposits[0].seller,
|
|
||||||
[MockValidDeposits[0]]
|
|
||||||
);
|
|
||||||
expect(liquidityElement).toEqual(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
export interface Participant {
|
||||||
|
offer: string;
|
||||||
|
chainID: number;
|
||||||
|
identification: string;
|
||||||
|
bankIspb?: string;
|
||||||
|
accountType: string;
|
||||||
|
account: string;
|
||||||
|
branch: string;
|
||||||
|
savingsVariation?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParticipantWithID extends Participant {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Offer {
|
||||||
|
amount: number;
|
||||||
|
sellerId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specs for BB Pay Sandbox
|
||||||
|
// https://apoio.developers.bb.com.br/sandbox/spec/665797498bb48200130fc32c
|
||||||
|
|
||||||
|
export const createParticipant = async (participant: Participant) => {
|
||||||
|
const response = await fetch(`${import.meta.env.VITE_APP_API_URL}/register`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
chainID: participant.chainID,
|
||||||
|
tipoDocumento: 1,
|
||||||
|
numeroDocumento: participant.identification,
|
||||||
|
numeroConta: participant.account,
|
||||||
|
numeroAgencia: participant.branch,
|
||||||
|
tipoConta: participant.accountType,
|
||||||
|
codigoIspb: participant.bankIspb,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error creating participant: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.errors || data.erros) {
|
||||||
|
throw new Error(`Error creating participant: ${JSON.stringify(data)}`);
|
||||||
|
}
|
||||||
|
return { ...participant, id: data.numeroParticipante } as ParticipantWithID;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSolicitation = async (offer: Offer) => {
|
||||||
|
const response = await fetch(`${import.meta.env.VITE_APP_API_URL}/request`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
amount: offer.amount,
|
||||||
|
pixTarget: offer.sellerId.split("-").pop(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSolicitation = async (id: bigint): Promise<{pixTimestamp: `0x${string}`, signature: `0x${string}`}> => {
|
||||||
|
const response = await fetch(
|
||||||
|
`${import.meta.env.VITE_APP_API_URL}/release/${id}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const obj = await response.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
pixTimestamp: obj.pixTimestamp,
|
||||||
|
signature: obj.signature,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import type { TokenEnum } from "@/model/NetworkEnum";
|
||||||
|
import { Networks } from "@/config/networks";
|
||||||
|
|
||||||
|
export const getNetworkImage = (networkName: string): string => {
|
||||||
|
const normalizedName = networkName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
||||||
|
return new URL(`../assets/networks/${normalizedName}.svg`, import.meta.url).href;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTokenImage = (tokenName: TokenEnum): string => {
|
||||||
|
return new URL(`../assets/tokens/${tokenName.toLowerCase()}.svg`, import.meta.url).href;
|
||||||
|
};
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
|
import type { Address } from "viem";
|
||||||
|
|
||||||
const verifyNetworkLiquidity = (
|
const verifyNetworkLiquidity = (
|
||||||
tokenValue: number,
|
tokenValue: number,
|
||||||
walletAddress: string,
|
walletAddress: Address,
|
||||||
validDepositList: ValidDeposit[]
|
validDepositList: ValidDeposit[]
|
||||||
): ValidDeposit | undefined => {
|
): ValidDeposit[] => {
|
||||||
const element = validDepositList.find((element) => {
|
const filteredDepositList = validDepositList
|
||||||
|
.filter((element) => {
|
||||||
const remaining = element.remaining;
|
const remaining = element.remaining;
|
||||||
if (
|
if (
|
||||||
tokenValue!! <= remaining &&
|
tokenValue!! <= remaining &&
|
||||||
@@ -15,9 +17,24 @@ const verifyNetworkLiquidity = (
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
return b.remaining - a.remaining;
|
||||||
});
|
});
|
||||||
|
|
||||||
return element;
|
const uniqueNetworkDeposits = filteredDepositList.reduce(
|
||||||
|
(acc: ValidDeposit[], current) => {
|
||||||
|
const existingNetwork = acc.find(
|
||||||
|
(deposit) => deposit.network === current.network
|
||||||
|
);
|
||||||
|
if (!existingNetwork) {
|
||||||
|
acc.push(current);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
return uniqueNetworkDeposits;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { verifyNetworkLiquidity };
|
export { verifyNetworkLiquidity };
|
||||||
@@ -1,337 +0,0 @@
|
|||||||
{
|
|
||||||
"_format": "hh-sol-artifact-1",
|
|
||||||
"contractName": "MockToken",
|
|
||||||
"sourceName": "contracts/lib/mock/mockToken.sol",
|
|
||||||
"abi": [
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "supply",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "constructor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "spender",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "amount",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "Approval",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"anonymous": false,
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "from",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": true,
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"indexed": false,
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "amount",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "Transfer",
|
|
||||||
"type": "event"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "DOMAIN_SEPARATOR",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bytes32",
|
|
||||||
"name": "",
|
|
||||||
"type": "bytes32"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "allowance",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "spender",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "amount",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "approve",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "balanceOf",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "decimals",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint8",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint8"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address[]",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address[]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "value",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "mint",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "name",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "string",
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "",
|
|
||||||
"type": "address"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "nonces",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "owner",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "spender",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "value",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "deadline",
|
|
||||||
"type": "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint8",
|
|
||||||
"name": "v",
|
|
||||||
"type": "uint8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "bytes32",
|
|
||||||
"name": "r",
|
|
||||||
"type": "bytes32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "bytes32",
|
|
||||||
"name": "s",
|
|
||||||
"type": "bytes32"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "permit",
|
|
||||||
"outputs": [],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "symbol",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "string",
|
|
||||||
"name": "",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [],
|
|
||||||
"name": "totalSupply",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "view",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "amount",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "transfer",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "from",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "address",
|
|
||||||
"name": "to",
|
|
||||||
"type": "address"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"internalType": "uint256",
|
|
||||||
"name": "amount",
|
|
||||||
"type": "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "transferFrom",
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"internalType": "bool",
|
|
||||||
"name": "",
|
|
||||||
"type": "bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"stateMutability": "nonpayable",
|
|
||||||
"type": "function"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"bytecode": "0x60e06040523480156200001157600080fd5b506040516200129938038062001299833981016040819052620000349162000279565b6040805180820182526007815266135bd8dad0949360ca1b6020808301918252835180850190945260048452631350949360e21b908401528151919291601291620000839160009190620001d3565b50815162000099906001906020850190620001d3565b5060ff81166080524660a052620000af620000ca565b60c05250620000c391503390508262000166565b506200039b565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051620000fe9190620002d0565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546200017a919062000374565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b828054620001e19062000293565b90600052602060002090601f01602090048101928262000205576000855562000250565b82601f106200022057805160ff191683800117855562000250565b8280016001018555821562000250579182015b828111156200025057825182559160200191906001019062000233565b506200025e92915062000262565b5090565b5b808211156200025e576000815560010162000263565b6000602082840312156200028c57600080fd5b5051919050565b600181811c90821680620002a857607f821691505b60208210811415620002ca57634e487b7160e01b600052602260045260246000fd5b50919050565b600080835481600182811c915080831680620002ed57607f831692505b60208084108214156200030e57634e487b7160e01b86526022600452602486fd5b818015620003255760018114620003375762000366565b60ff1986168952848901965062000366565b60008a81526020902060005b868110156200035e5781548b82015290850190830162000343565b505084890196505b509498975050505050505050565b600082198211156200039657634e487b7160e01b600052601160045260246000fd5b500190565b60805160a05160c051610ece620003cb60003960006104c20152600061048d015260006101540152610ece6000f3fe608060405234801561001057600080fd5b50600436106100df5760003560e01c806370a082311161008c578063a9059cbb11610066578063a9059cbb146101d8578063d505accf146101eb578063dd62ed3e14610200578063ea66696c1461022b57600080fd5b806370a08231146101905780637ecebe00146101b057806395d89b41146101d057600080fd5b806323b872dd116100bd57806323b872dd1461013c578063313ce5671461014f5780633644e5151461018857600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec61023e565b6040516100f991906109f2565b60405180910390f35b610115610110366004610a8e565b6102cc565b60405190151581526020016100f9565b61012e60025481565b6040519081526020016100f9565b61011561014a366004610ab8565b610345565b6101767f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100f9565b61012e610489565b61012e61019e366004610af4565b60036020526000908152604090205481565b61012e6101be366004610af4565b60056020526000908152604090205481565b6100ec6104e4565b6101156101e6366004610a8e565b6104f1565b6101fe6101f9366004610b16565b610576565b005b61012e61020e366004610b89565b600460209081526000928352604080842090915290825290205481565b6101fe610239366004610beb565b61089a565b6000805461024b90610cd4565b80601f016020809104026020016040519081016040528092919081815260200182805461027790610cd4565b80156102c45780601f10610299576101008083540402835291602001916102c4565b820191906000526020600020905b8154815290600101906020018083116102a757829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103349086815260200190565b60405180910390a350600192915050565b73ffffffffffffffffffffffffffffffffffffffff831660009081526004602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146103d9576103a78382610d57565b73ffffffffffffffffffffffffffffffffffffffff861660009081526004602090815260408083203384529091529020555b73ffffffffffffffffffffffffffffffffffffffff85166000908152600360205260408120805485929061040e908490610d57565b909155505073ffffffffffffffffffffffffffffffffffffffff808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104769087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104bf576104ba6108e0565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b6001805461024b90610cd4565b33600090815260036020526040812080548391908390610512908490610d57565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103349086815260200190565b428410156105e5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b600060016105f1610489Line truncated
|
|
||||||
"deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100df5760003560e01c806370a082311161008c578063a9059cbb11610066578063a9059cbb146101d8578063d505accf146101eb578063dd62ed3e14610200578063ea66696c1461022b57600080fd5b806370a08231146101905780637ecebe00146101b057806395d89b41146101d057600080fd5b806323b872dd116100bd57806323b872dd1461013c578063313ce5671461014f5780633644e5151461018857600080fd5b806306fdde03146100e4578063095ea7b31461010257806318160ddd14610125575b600080fd5b6100ec61023e565b6040516100f991906109f2565b60405180910390f35b610115610110366004610a8e565b6102cc565b60405190151581526020016100f9565b61012e60025481565b6040519081526020016100f9565b61011561014a366004610ab8565b610345565b6101767f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100f9565b61012e610489565b61012e61019e366004610af4565b60036020526000908152604090205481565b61012e6101be366004610af4565b60056020526000908152604090205481565b6100ec6104e4565b6101156101e6366004610a8e565b6104f1565b6101fe6101f9366004610b16565b610576565b005b61012e61020e366004610b89565b600460209081526000928352604080842090915290825290205481565b6101fe610239366004610beb565b61089a565b6000805461024b90610cd4565b80601f016020809104026020016040519081016040528092919081815260200182805461027790610cd4565b80156102c45780601f10610299576101008083540402835291602001916102c4565b820191906000526020600020905b8154815290600101906020018083116102a757829003601f168201915b505050505081565b33600081815260046020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103349086815260200190565b60405180910390a350600192915050565b73ffffffffffffffffffffffffffffffffffffffff831660009081526004602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146103d9576103a78382610d57565b73ffffffffffffffffffffffffffffffffffffffff861660009081526004602090815260408083203384529091529020555b73ffffffffffffffffffffffffffffffffffffffff85166000908152600360205260408120805485929061040e908490610d57565b909155505073ffffffffffffffffffffffffffffffffffffffff808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104769087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104bf576104ba6108e0565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b6001805461024b90610cd4565b33600090815260036020526040812080548391908390610512908490610d57565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103349086815260200190565b428410156105e5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b600060016105f1610489565b73ffffffffffffffffffffffffffffffffffffffff8a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e0830190915280519201919091207f190100000000000000000000000000000000000000000000000000000000000061010083015261010282019290925261012281019190915261014201604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610743573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff8116158015906107be57508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b610824576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f494e56414c49445f5349474e455200000000000000000000000000000000000060448201526064016105dc565b73ffffffffffffffffffffffffffffffffffffffff90811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b815160005b818110156108da576108ca8482815181106108bc576108bc610d6e565b60200260200101518461097a565b6108d381610d9d565b905061089f565b50505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516109129190610dd6565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b806002600082825461098c9190610ea9565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152600360209081526040Line truncated
|
|
||||||
"linkReferences": {},
|
|
||||||
"deployedLinkReferences": {}
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,6 @@ const openItem = (index: number) => {
|
|||||||
faq.value[selectedSection.value].items[index].content = marked(
|
faq.value[selectedSection.value].items[index].content = marked(
|
||||||
faq.value[selectedSection.value].items[index].content
|
faq.value[selectedSection.value].items[index].content
|
||||||
);
|
);
|
||||||
console.log(marked(faq.value[selectedSection.value].items[index].content));
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ const openItem = (index: number) => {
|
|||||||
<span class="text font-extrabold text-5xl max-w-[50rem]"
|
<span class="text font-extrabold text-5xl max-w-[50rem]"
|
||||||
>Perguntas Frequentes
|
>Perguntas Frequentes
|
||||||
</span>
|
</span>
|
||||||
<span class="text text-xl font-medium text-base max-w-[40rem]"
|
<span class="text font-medium text-base max-w-[40rem]"
|
||||||
>Não conseguiu uma resposta para sua dúvida? Acesse a comunidade do
|
>Não conseguiu uma resposta para sua dúvida? Acesse a comunidade do
|
||||||
Discord para falar diretamente conosco.</span
|
Discord para falar diretamente conosco.</span
|
||||||
>
|
>
|
||||||
@@ -61,13 +60,13 @@ const openItem = (index: number) => {
|
|||||||
<div class="flex cursor-pointer" @click="openItem(index)">
|
<div class="flex cursor-pointer" @click="openItem(index)">
|
||||||
<img
|
<img
|
||||||
alt="plus"
|
alt="plus"
|
||||||
src="@/assets/plus.svg"
|
src="@/assets/plus.svg?url"
|
||||||
class="mr-3"
|
class="mr-3"
|
||||||
v-if="!item.isOpen"
|
v-if="!item.isOpen"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
alt="plus"
|
alt="plus"
|
||||||
src="@/assets/minus.svg"
|
src="@/assets/minus.svg?url"
|
||||||
class="mr-3"
|
class="mr-3"
|
||||||
v-if="item.isOpen"
|
v-if="item.isOpen"
|
||||||
/>
|
/>
|
||||||
@@ -134,11 +133,4 @@ h4 {
|
|||||||
.text {
|
.text {
|
||||||
@apply text-white text-center;
|
@apply text-white text-center;
|
||||||
}
|
}
|
||||||
.blur-container-row {
|
|
||||||
@apply flex flex-row justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.blur-container {
|
|
||||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SearchComponent from "@/components/SearchComponent.vue";
|
import SearchComponent from "@/components/BuyerSteps/BuyerSearchComponent.vue";
|
||||||
import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue";
|
import LoadingComponent from "@/components/ui/LoadingComponent.vue";
|
||||||
import BuyConfirmedComponent from "@/components/BuyConfirmedComponent/BuyConfirmedComponent.vue";
|
import BuyConfirmedComponent from "@/components/BuyerSteps/BuyConfirmedComponent.vue";
|
||||||
import { ref, onMounted, watch } from "vue";
|
import { ref, onMounted, watch } from "vue";
|
||||||
import { useEtherStore } from "@/store/ether";
|
import { useUser } from "@/composables/useUser";
|
||||||
import QrCodeComponent from "@/components/QrCodeComponent.vue";
|
import QrCodeComponent from "@/components/BuyerSteps/QrCodeComponent.vue";
|
||||||
import { storeToRefs } from "pinia";
|
|
||||||
import { addLock, releaseLock } from "@/blockchain/buyerMethods";
|
import { addLock, releaseLock } from "@/blockchain/buyerMethods";
|
||||||
import { updateWalletStatus, checkUnreleasedLock } from "@/blockchain/wallet";
|
import { updateWalletStatus, checkUnreleasedLock } from "@/blockchain/wallet";
|
||||||
import { getNetworksLiquidity } from "@/blockchain/events";
|
import { getNetworksLiquidity } from "@/blockchain/events";
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
import { getUnreleasedLockById } from "@/blockchain/events";
|
import { getUnreleasedLockById } from "@/blockchain/events";
|
||||||
import CustomAlert from "@/components/CustomAlert/CustomAlert.vue";
|
import CustomAlert from "@/components/ui/CustomAlert.vue";
|
||||||
|
import { getSolicitation } from "@/utils/bbPay";
|
||||||
|
import type { Address } from "viem";
|
||||||
|
|
||||||
enum Step {
|
enum Step {
|
||||||
Search,
|
Search,
|
||||||
@@ -19,13 +20,14 @@ enum Step {
|
|||||||
List,
|
List,
|
||||||
}
|
}
|
||||||
|
|
||||||
const etherStore = useEtherStore();
|
const user = useUser();
|
||||||
etherStore.setSellerView(false);
|
user.setSellerView(false);
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const { loadingLock, walletAddress, networkName } = storeToRefs(etherStore);
|
const { loadingLock, walletAddress, network } = user;
|
||||||
const flowStep = ref<Step>(Step.Search);
|
const flowStep = ref<Step>(Step.Search);
|
||||||
const pixTarget = ref<number>();
|
const participantID = ref<string>();
|
||||||
|
const sellerAddress = ref<Address>();
|
||||||
const tokenAmount = ref<number>();
|
const tokenAmount = ref<number>();
|
||||||
const lockID = ref<string>("");
|
const lockID = ref<string>("");
|
||||||
const loadingRelease = ref<boolean>(false);
|
const loadingRelease = ref<boolean>(false);
|
||||||
@@ -37,53 +39,48 @@ const confirmBuyClick = async (
|
|||||||
selectedDeposit: ValidDeposit,
|
selectedDeposit: ValidDeposit,
|
||||||
tokenValue: number
|
tokenValue: number
|
||||||
) => {
|
) => {
|
||||||
// finish buy screen
|
participantID.value = selectedDeposit.participantID;
|
||||||
pixTarget.value = selectedDeposit.pixKey;
|
|
||||||
tokenAmount.value = tokenValue;
|
tokenAmount.value = tokenValue;
|
||||||
|
|
||||||
// Makes lock with deposit ID and the Amount
|
|
||||||
if (selectedDeposit) {
|
if (selectedDeposit) {
|
||||||
flowStep.value = Step.Buy;
|
flowStep.value = Step.Buy;
|
||||||
etherStore.setLoadingLock(true);
|
user.setLoadingLock(true);
|
||||||
|
|
||||||
await addLock(selectedDeposit.seller, selectedDeposit.token, tokenValue)
|
await addLock(selectedDeposit.seller, selectedDeposit.token, tokenValue)
|
||||||
.then((_lockID) => {
|
.then((_lockID) => {
|
||||||
lockID.value = _lockID;
|
lockID.value = String(_lockID);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
flowStep.value = Step.Search;
|
flowStep.value = Step.Search;
|
||||||
});
|
});
|
||||||
|
|
||||||
etherStore.setLoadingLock(false);
|
user.setLoadingLock(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const releaseTransaction = async (e2eId: string) => {
|
const releaseTransaction = async (params: {
|
||||||
|
pixTimestamp: `0x${string}`&{lenght:34},
|
||||||
|
signature: `0x${string}`,
|
||||||
|
}) => {
|
||||||
flowStep.value = Step.List;
|
flowStep.value = Step.List;
|
||||||
showBuyAlert.value = true;
|
showBuyAlert.value = true;
|
||||||
loadingRelease.value = true;
|
loadingRelease.value = true;
|
||||||
|
|
||||||
if (lockID.value && tokenAmount.value && pixTarget.value) {
|
const release = await releaseLock(BigInt(lockID.value), params.pixTimestamp, params.signature);
|
||||||
const release = await releaseLock(
|
|
||||||
pixTarget.value,
|
|
||||||
tokenAmount.value,
|
|
||||||
e2eId,
|
|
||||||
lockID.value
|
|
||||||
);
|
|
||||||
await release.wait();
|
|
||||||
|
|
||||||
await updateWalletStatus();
|
await updateWalletStatus();
|
||||||
loadingRelease.value = false;
|
loadingRelease.value = false;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkForUnreleasedLocks = async (): Promise<void> => {
|
const checkForUnreleasedLocks = async (): Promise<void> => {
|
||||||
const walletLocks = await checkUnreleasedLock(walletAddress.value);
|
if (!walletAddress.value)
|
||||||
if (walletLocks) {
|
throw new Error("Wallet not connected");
|
||||||
lockID.value = walletLocks.lockID;
|
const lock = await checkUnreleasedLock(walletAddress.value);
|
||||||
tokenAmount.value = walletLocks.pix.value;
|
if (lock) {
|
||||||
pixTarget.value = Number(walletLocks.pix.pixKey);
|
lockID.value = String(lock.lockID);
|
||||||
|
tokenAmount.value = lock.amount;
|
||||||
|
sellerAddress.value = lock.sellerAddress;
|
||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
} else {
|
} else {
|
||||||
flowStep.value = Step.Search;
|
flowStep.value = Step.Search;
|
||||||
@@ -92,11 +89,11 @@ const checkForUnreleasedLocks = async (): Promise<void> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (paramLockID) {
|
if (paramLockID) {
|
||||||
const lockToRedirect = await getUnreleasedLockById(paramLockID as string);
|
const lockToRedirect = await getUnreleasedLockById(paramLockID);
|
||||||
if (lockToRedirect) {
|
if (lockToRedirect) {
|
||||||
lockID.value = lockToRedirect.lockID;
|
lockID.value = String(lockToRedirect.lockID);
|
||||||
tokenAmount.value = lockToRedirect.pix.value;
|
tokenAmount.value = lockToRedirect.amount;
|
||||||
pixTarget.value = Number(lockToRedirect.pix.pixKey);
|
sellerAddress.value = lockToRedirect.sellerAddress;
|
||||||
flowStep.value = Step.Buy;
|
flowStep.value = Step.Buy;
|
||||||
} else {
|
} else {
|
||||||
flowStep.value = Step.Search;
|
flowStep.value = Step.Search;
|
||||||
@@ -106,7 +103,7 @@ if (paramLockID) {
|
|||||||
await checkForUnreleasedLocks();
|
await checkForUnreleasedLocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(networkName, async () => {
|
watch(network, async () => {
|
||||||
if (walletAddress.value) await checkForUnreleasedLocks();
|
if (walletAddress.value) await checkForUnreleasedLocks();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -119,6 +116,7 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<SearchComponent
|
<SearchComponent
|
||||||
v-if="flowStep == Step.Search"
|
v-if="flowStep == Step.Search"
|
||||||
@token-buy="confirmBuyClick"
|
@token-buy="confirmBuyClick"
|
||||||
@@ -138,8 +136,7 @@ onMounted(async () => {
|
|||||||
/>
|
/>
|
||||||
<div v-if="flowStep == Step.Buy">
|
<div v-if="flowStep == Step.Buy">
|
||||||
<QrCodeComponent
|
<QrCodeComponent
|
||||||
:pixTarget="String(pixTarget)"
|
:lockID="lockID"
|
||||||
:tokenValue="tokenAmount"
|
|
||||||
@pix-validated="releaseTransaction"
|
@pix-validated="releaseTransaction"
|
||||||
v-if="!loadingLock"
|
v-if="!loadingLock"
|
||||||
/>
|
/>
|
||||||
@@ -161,4 +158,5 @@ onMounted(async () => {
|
|||||||
:message="'A transação está sendo enviada para a rede. Em breve os tokens serão depositados em sua carteira.'"
|
:message="'A transação está sendo enviada para a rede. Em breve os tokens serão depositados em sua carteira.'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useEtherStore } from "@/store/ether";
|
import { ref, onMounted, watch } from "vue";
|
||||||
import { storeToRefs } from "pinia";
|
import { useUser } from "@/composables/useUser";
|
||||||
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
||||||
import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue";
|
import LoadingComponent from "@/components/ui/LoadingComponent.vue";
|
||||||
import CustomAlert from "@/components/CustomAlert/CustomAlert.vue";
|
import CustomAlert from "@/components/ui/CustomAlert.vue";
|
||||||
import { ref, watch, onMounted } from "vue";
|
|
||||||
import {
|
import {
|
||||||
listValidDepositTransactionsByWalletAddress,
|
listValidDepositTransactionsByWalletAddress,
|
||||||
listAllTransactionByWalletAddress,
|
listAllTransactionByWalletAddress,
|
||||||
@@ -16,9 +15,8 @@ import type { WalletTransaction } from "@/model/WalletTransaction";
|
|||||||
|
|
||||||
import router from "@/router/index";
|
import router from "@/router/index";
|
||||||
|
|
||||||
const etherStore = useEtherStore();
|
const user = useUser();
|
||||||
|
const { walletAddress, network, selectedToken } = user;
|
||||||
const { walletAddress, networkName } = storeToRefs(etherStore);
|
|
||||||
const loadingWithdraw = ref<boolean>(false);
|
const loadingWithdraw = ref<boolean>(false);
|
||||||
const showAlert = ref<boolean>(false);
|
const showAlert = ref<boolean>(false);
|
||||||
|
|
||||||
@@ -31,7 +29,9 @@ const callWithdraw = async (amount: string) => {
|
|||||||
loadingWithdraw.value = true;
|
loadingWithdraw.value = true;
|
||||||
let withdraw;
|
let withdraw;
|
||||||
try {
|
try {
|
||||||
withdraw = await withdrawDeposit(amount);
|
withdraw = await withdrawDeposit(
|
||||||
|
amount,
|
||||||
|
network.value.tokens[selectedToken.value].address);
|
||||||
} catch {
|
} catch {
|
||||||
loadingWithdraw.value = false;
|
loadingWithdraw.value = false;
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ const callWithdraw = async (amount: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getWalletTransactions = async () => {
|
const getWalletTransactions = async () => {
|
||||||
etherStore.setLoadingWalletTransactions(true);
|
user.setLoadingWalletTransactions(true);
|
||||||
if (walletAddress.value) {
|
if (walletAddress.value) {
|
||||||
const walletDeposits = await listValidDepositTransactionsByWalletAddress(
|
const walletDeposits = await listValidDepositTransactionsByWalletAddress(
|
||||||
walletAddress.value
|
walletAddress.value
|
||||||
@@ -67,7 +67,7 @@ const getWalletTransactions = async () => {
|
|||||||
transactionsList.value = allUserTransactions;
|
transactionsList.value = allUserTransactions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
etherStore.setLoadingWalletTransactions(false);
|
user.setLoadingWalletTransactions(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -81,12 +81,13 @@ watch(walletAddress, async () => {
|
|||||||
await getWalletTransactions();
|
await getWalletTransactions();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(networkName, async () => {
|
watch(network, async () => {
|
||||||
await getWalletTransactions();
|
await getWalletTransactions();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<CustomAlert
|
<CustomAlert
|
||||||
v-if="showAlert"
|
v-if="showAlert"
|
||||||
:type="'withdraw'"
|
:type="'withdraw'"
|
||||||
@@ -99,7 +100,7 @@ watch(networkName, async () => {
|
|||||||
<div class="header" v-if="!loadingWithdraw && walletAddress">
|
<div class="header" v-if="!loadingWithdraw && walletAddress">
|
||||||
Gerenciar Ofertas
|
Gerenciar Ofertas
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full max-w-4xl">
|
<div class="w-full max-w-4xl flex justify-center">
|
||||||
<ListingComponent
|
<ListingComponent
|
||||||
v-if="!loadingWithdraw && walletAddress"
|
v-if="!loadingWithdraw && walletAddress"
|
||||||
:valid-deposits="depositList"
|
:valid-deposits="depositList"
|
||||||
@@ -113,6 +114,7 @@ watch(networkName, async () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import WantSellComponent from "@/components/SellerSteps/WantSellComponent.vue";
|
|
||||||
import SendNetwork from "@/components/SellerSteps/SendNetwork.vue";
|
|
||||||
import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue";
|
|
||||||
import { approveTokens, addDeposit } from "@/blockchain/sellerMethods";
|
|
||||||
|
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { useEtherStore } from "@/store/ether";
|
|
||||||
import CustomAlert from "@/components/CustomAlert/CustomAlert.vue";
|
import SellerComponent from "@/components/SellerSteps/SellerComponent.vue";
|
||||||
|
import SendNetwork from "@/components/SellerSteps/SendNetwork.vue";
|
||||||
|
import LoadingComponent from "@/components/ui/LoadingComponent.vue";
|
||||||
|
import { useUser } from "@/composables/useUser";
|
||||||
|
import { approveTokens, addDeposit } from "@/blockchain/sellerMethods";
|
||||||
|
import CustomAlert from "@/components/ui/CustomAlert.vue";
|
||||||
|
import type { Participant } from "@/utils/bbPay";
|
||||||
|
|
||||||
enum Step {
|
enum Step {
|
||||||
Search,
|
Search,
|
||||||
@@ -14,26 +15,18 @@ enum Step {
|
|||||||
Network,
|
Network,
|
||||||
}
|
}
|
||||||
|
|
||||||
const etherStore = useEtherStore();
|
const user = useUser();
|
||||||
etherStore.setSellerView(true);
|
user.setSellerView(true);
|
||||||
|
|
||||||
const flowStep = ref<Step>(Step.Sell);
|
const flowStep = ref<Step>(Step.Sell);
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
|
|
||||||
const offerValue = ref<string>("");
|
|
||||||
const pixKeyBuyer = ref<string>("");
|
|
||||||
const showAlert = ref<boolean>(false);
|
const showAlert = ref<boolean>(false);
|
||||||
|
|
||||||
// Verificar tipagem
|
// Verificar tipagem
|
||||||
const approveOffer = async (args: {
|
const approveOffer = async (args: Participant) => {
|
||||||
offer: string;
|
|
||||||
postProcessedPixKey: string;
|
|
||||||
}) => {
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
offerValue.value = args.offer;
|
await approveTokens(args);
|
||||||
pixKeyBuyer.value = args.postProcessedPixKey;
|
|
||||||
await approveTokens(args.offer);
|
|
||||||
flowStep.value = Step.Network;
|
flowStep.value = Step.Network;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -46,8 +39,8 @@ const approveOffer = async (args: {
|
|||||||
const sendNetwork = async () => {
|
const sendNetwork = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
if (offerValue.value && pixKeyBuyer.value) {
|
if (user.seller.value) {
|
||||||
await addDeposit(String(offerValue.value), pixKeyBuyer.value);
|
await addDeposit();
|
||||||
flowStep.value = Step.Sell;
|
flowStep.value = Step.Sell;
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
showAlert.value = true;
|
showAlert.value = true;
|
||||||
@@ -61,8 +54,9 @@ const sendNetwork = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<div v-if="flowStep == Step.Sell">
|
<div v-if="flowStep == Step.Sell">
|
||||||
<WantSellComponent v-if="!loading" @approve-tokens="approveOffer" />
|
<SellerComponent v-if="!loading" @approve-tokens="approveOffer" />
|
||||||
<LoadingComponent
|
<LoadingComponent
|
||||||
v-if="loading"
|
v-if="loading"
|
||||||
:message="'A transação está sendo enviada para a rede.'"
|
:message="'A transação está sendo enviada para a rede.'"
|
||||||
@@ -75,8 +69,9 @@ const sendNetwork = async () => {
|
|||||||
/>
|
/>
|
||||||
<div v-if="flowStep == Step.Network">
|
<div v-if="flowStep == Step.Network">
|
||||||
<SendNetwork
|
<SendNetwork
|
||||||
:pixKey="pixKeyBuyer"
|
:sellerId="Number(user.sellerId.value)"
|
||||||
:offer="Number(offerValue)"
|
:offer="Number(user.seller.value.offer)"
|
||||||
|
:selected-token="user.selectedToken.value"
|
||||||
v-if="!loading"
|
v-if="!loading"
|
||||||
@send-network="sendNetwork"
|
@send-network="sendNetwork"
|
||||||
/>
|
/>
|
||||||
@@ -85,4 +80,5 @@ const sendNetwork = async () => {
|
|||||||
:message="'A transação está sendo enviada para a rede.'"
|
:message="'A transação está sendo enviada para a rede.'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||||
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"types": ["node"]
|
"types": [
|
||||||
|
"node"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||