Compare commits
24
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 | ||
|
|
b956c8ec2b | ||
|
|
1d429f039a |
No files matched your search
Generated
+12285
File diff suppressed because it is too large.
Load diff
+10
-19
@@ -1,34 +1,25 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute } from "vue-router";
|
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/ToasterComponent.vue";
|
import ToasterComponent from "@/components/ui/ToasterComponent.vue";
|
||||||
import { init, useOnboard } from "@web3-onboard/vue";
|
import { init, useOnboard } from "@web3-onboard/vue";
|
||||||
import injectedModule from "@web3-onboard/injected-wallets";
|
import injectedModule from "@web3-onboard/injected-wallets";
|
||||||
import { Networks } from "./model/Networks";
|
import { Networks, DEFAULT_NETWORK } from "@/config/networks";
|
||||||
import { NetworkEnum } from "./model/NetworkEnum";
|
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const injected = injectedModule();
|
const injected = injectedModule();
|
||||||
const targetNetwork = ref(NetworkEnum.sepolia);
|
const targetNetwork = ref(DEFAULT_NETWORK);
|
||||||
|
|
||||||
const web3Onboard = init({
|
const web3Onboard = init({
|
||||||
wallets: [injected],
|
wallets: [injected],
|
||||||
chains: [
|
chains: Object.values(Networks).map((network) => ({
|
||||||
{
|
id: network.id,
|
||||||
id: Networks[NetworkEnum.sepolia].chainId,
|
token: network.nativeCurrency.symbol,
|
||||||
token: "ETH",
|
label: network.name,
|
||||||
label: "Sepolia",
|
rpcUrl: network.rpcUrls.default.http[0],
|
||||||
rpcUrl: import.meta.env.VITE_SEPOLIA_API_URL,
|
})),
|
||||||
},
|
|
||||||
{
|
|
||||||
id: Networks[NetworkEnum.rootstock].chainId,
|
|
||||||
token: "tRBTC",
|
|
||||||
label: "Rootstock Testnet",
|
|
||||||
rpcUrl: import.meta.env.VITE_ROOTSTOCK_API_URL,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
connect: {
|
connect: {
|
||||||
autoConnectLastWallet: true,
|
autoConnectLastWallet: true,
|
||||||
},
|
},
|
||||||
|
|||||||
File renamed without changes.
File renamed without changes.
@@ -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 |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,70 +0,0 @@
|
|||||||
import { useUser } from "@/composables/useUser";
|
|
||||||
import { NetworkEnum, TokenEnum } from "@/model/NetworkEnum";
|
|
||||||
import { createPublicClient, http, type Address } from "viem";
|
|
||||||
import { sepolia, rootstock } from "viem/chains";
|
|
||||||
|
|
||||||
const Tokens: { [key in NetworkEnum]: { [key in TokenEnum]: Address } } = {
|
|
||||||
[NetworkEnum.sepolia]: {
|
|
||||||
BRZ: "0x3eBE67A2C7bdB2081CBd34ba3281E90377462289",
|
|
||||||
// BRX: "0x3eBE67A2C7bdB2081CBd34ba3281E90377462289",
|
|
||||||
},
|
|
||||||
[NetworkEnum.rootstock]: {
|
|
||||||
BRZ: "0xfE841c74250e57640390f46d914C88d22C51e82e",
|
|
||||||
// BRX: "0xfE841c74250e57640390f46d914C88d22C51e82e",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTokenByAddress = (address: Address) => {
|
|
||||||
const user = useUser();
|
|
||||||
const networksTokens = Tokens[user.networkName.value];
|
|
||||||
for (const [token, tokenAddress] of Object.entries(networksTokens)) {
|
|
||||||
if (tokenAddress.toLowerCase() === address.toLowerCase()) {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTokenAddress = (
|
|
||||||
token: TokenEnum,
|
|
||||||
network?: NetworkEnum
|
|
||||||
): Address => {
|
|
||||||
const user = useUser();
|
|
||||||
return Tokens[network ? network : user.networkName.value][
|
|
||||||
token
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getP2PixAddress = (network?: NetworkEnum): Address => {
|
|
||||||
const user = useUser();
|
|
||||||
const possibleP2PixAddresses: { [key in NetworkEnum]: Address } = {
|
|
||||||
[NetworkEnum.sepolia]: "0xb7cD135F5eFD9760981e02E2a898790b688939fe",
|
|
||||||
[NetworkEnum.rootstock]: "0x98ba35eb14b38D6Aa709338283af3e922476dE34",
|
|
||||||
};
|
|
||||||
|
|
||||||
return possibleP2PixAddresses[
|
|
||||||
network ? network : user.networkName.value
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getProviderUrl = (network?: NetworkEnum): string => {
|
|
||||||
const user = useUser();
|
|
||||||
const possibleProvidersUrls: { [key in NetworkEnum]: string } = {
|
|
||||||
[NetworkEnum.sepolia]: import.meta.env.VITE_SEPOLIA_API_URL,
|
|
||||||
[NetworkEnum.rootstock]: import.meta.env.VITE_RSK_API_URL,
|
|
||||||
};
|
|
||||||
|
|
||||||
return possibleProvidersUrls[network || user.networkName.value];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getProviderByNetwork = (network: NetworkEnum) => {
|
|
||||||
const chain = network === NetworkEnum.sepolia ? sepolia : rootstock;
|
|
||||||
return createPublicClient({
|
|
||||||
chain,
|
|
||||||
transport: http(getProviderUrl(network)),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isPossibleNetwork = (networkChain: NetworkEnum): boolean => {
|
|
||||||
return Number(networkChain) in NetworkEnum;
|
|
||||||
};
|
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
import { getContract } from "./provider";
|
import { getContract } from "./provider";
|
||||||
import { getTokenAddress } from "./addresses";
|
import { ChainContract } from "viem";
|
||||||
import {
|
import {
|
||||||
bytesToHex,
|
|
||||||
encodeAbiParameters,
|
|
||||||
keccak256,
|
|
||||||
parseAbiParameters,
|
|
||||||
parseEther,
|
parseEther,
|
||||||
stringToBytes,
|
|
||||||
stringToHex,
|
|
||||||
toBytes,
|
|
||||||
type Address,
|
type Address,
|
||||||
|
type TransactionReceipt,
|
||||||
} from "viem";
|
} from "viem";
|
||||||
import type { TokenEnum } from "@/model/NetworkEnum";
|
|
||||||
|
|
||||||
export const addLock = async (
|
export const addLock = async (
|
||||||
sellerAddress: Address,
|
sellerAddress: Address,
|
||||||
@@ -43,7 +36,7 @@ export const addLock = async (
|
|||||||
|
|
||||||
export const withdrawDeposit = async (
|
export const withdrawDeposit = async (
|
||||||
amount: string,
|
amount: string,
|
||||||
token: TokenEnum
|
token: Address
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
const { address, abi, wallet, client, account } = await getContract();
|
const { address, abi, wallet, client, account } = await getContract();
|
||||||
|
|
||||||
@@ -51,13 +44,11 @@ export const withdrawDeposit = async (
|
|||||||
throw new Error("Wallet not connected");
|
throw new Error("Wallet not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenAddress = getTokenAddress(token);
|
|
||||||
|
|
||||||
const { request } = await client.simulateContract({
|
const { request } = await client.simulateContract({
|
||||||
address,
|
address,
|
||||||
abi,
|
abi,
|
||||||
functionName: "withdraw",
|
functionName: "withdraw",
|
||||||
args: [tokenAddress, parseEther(amount), []],
|
args: [token, parseEther(amount), []],
|
||||||
account
|
account
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -69,24 +60,20 @@ export const withdrawDeposit = async (
|
|||||||
|
|
||||||
export const releaseLock = async (
|
export const releaseLock = async (
|
||||||
lockID: bigint,
|
lockID: bigint,
|
||||||
pixtarget: string,
|
pixTimestamp: `0x${string}`&{lenght:34},
|
||||||
signature: string
|
signature: `0x${string}`
|
||||||
): Promise<any> => {
|
): Promise<TransactionReceipt> => {
|
||||||
const { address, abi, wallet, client, account } = await getContract();
|
const { address, abi, wallet, client, account } = await getContract();
|
||||||
|
|
||||||
console.log("Releasing lock", { lockID, pixtarget, signature });
|
|
||||||
if (!wallet) {
|
if (!wallet) {
|
||||||
throw new Error("Wallet not connected");
|
throw new Error("Wallet not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert pixtarget to bytes32
|
|
||||||
const pixTimestamp = keccak256(stringToHex(pixtarget, { size: 32 }) );
|
|
||||||
|
|
||||||
const { request } = await client.simulateContract({
|
const { request } = await client.simulateContract({
|
||||||
address,
|
address,
|
||||||
abi,
|
abi,
|
||||||
functionName: "release",
|
functionName: "release",
|
||||||
args: [BigInt(lockID), pixTimestamp, stringToHex(signature)],
|
args: [BigInt(lockID), pixTimestamp, signature],
|
||||||
account
|
account
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+11
-17
@@ -1,14 +1,13 @@
|
|||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
import { formatEther, toHex, stringToHex } from "viem";
|
import { formatEther, toHex, stringToHex } from "viem";
|
||||||
import type { PublicClient, Address } from "viem";
|
import type { PublicClient, Address } from "viem";
|
||||||
|
import { Networks } from "@/config/networks";
|
||||||
import { getContract } from "./provider";
|
import { getContract } from "./provider";
|
||||||
import { getP2PixAddress, getTokenAddress } from "./addresses";
|
|
||||||
import { p2PixAbi } from "./abi"
|
import { p2PixAbi } from "./abi"
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
import { getNetworkSubgraphURL, NetworkEnum, TokenEnum } from "@/model/NetworkEnum";
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
import type { UnreleasedLock } from "@/model/UnreleasedLock";
|
import type { UnreleasedLock } from "@/model/UnreleasedLock";
|
||||||
import type { LockStatus } from "@/model/LockStatus"
|
import { ChainContract } from "viem";
|
||||||
|
|
||||||
const getNetworksLiquidity = async (): Promise<void> => {
|
const getNetworksLiquidity = async (): Promise<void> => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
@@ -16,12 +15,10 @@ const getNetworksLiquidity = async (): Promise<void> => {
|
|||||||
|
|
||||||
const depositLists: ValidDeposit[][] = [];
|
const depositLists: ValidDeposit[][] = [];
|
||||||
|
|
||||||
for (const network of Object.values(NetworkEnum).filter(
|
for (const network of Object.values(Networks)) {
|
||||||
(v) => !isNaN(Number(v))
|
|
||||||
)) {
|
|
||||||
const deposits = await getValidDeposits(
|
const deposits = await getValidDeposits(
|
||||||
getTokenAddress(user.selectedToken.value),
|
user.network.value.tokens[user.selectedToken.value].address,
|
||||||
Number(network)
|
network
|
||||||
);
|
);
|
||||||
if (deposits) depositLists.push(deposits);
|
if (deposits) depositLists.push(deposits);
|
||||||
}
|
}
|
||||||
@@ -32,8 +29,8 @@ const getNetworksLiquidity = async (): Promise<void> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getParticipantID = async (
|
const getParticipantID = async (
|
||||||
seller: string,
|
seller: Address,
|
||||||
token: string
|
token: Address
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const { address, abi, client } = await getContract();
|
const { address, abi, client } = await getContract();
|
||||||
|
|
||||||
@@ -62,7 +59,7 @@ const getParticipantID = async (
|
|||||||
|
|
||||||
const getValidDeposits = async (
|
const getValidDeposits = async (
|
||||||
token: Address,
|
token: Address,
|
||||||
network: NetworkEnum,
|
network: NetworkConfig,
|
||||||
contractInfo?: { client: PublicClient; address: Address }
|
contractInfo?: { client: PublicClient; address: Address }
|
||||||
): Promise<ValidDeposit[]> => {
|
): Promise<ValidDeposit[]> => {
|
||||||
let client: PublicClient, abi;
|
let client: PublicClient, abi;
|
||||||
@@ -74,9 +71,6 @@ const getValidDeposits = async (
|
|||||||
({ abi, client } = await getContract(true));
|
({ abi, client } = await getContract(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove this once we have a subgraph for rootstock
|
|
||||||
if (network === NetworkEnum.rootstock) return [];
|
|
||||||
|
|
||||||
const body = {
|
const body = {
|
||||||
query: `
|
query: `
|
||||||
{
|
{
|
||||||
@@ -90,7 +84,7 @@ const getValidDeposits = async (
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const depositLogs = await fetch(getNetworkSubgraphURL(network), {
|
const depositLogs = await fetch( network.subgraphUrls[0], {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -119,7 +113,7 @@ const getValidDeposits = async (
|
|||||||
const sellersList = Object.keys(uniqueSellers) as Address[];
|
const sellersList = Object.keys(uniqueSellers) as Address[];
|
||||||
// Use multicall to batch all getBalance requests
|
// Use multicall to batch all getBalance requests
|
||||||
const balanceCalls = sellersList.map((seller) => ({
|
const balanceCalls = sellersList.map((seller) => ({
|
||||||
address: getP2PixAddress(network),
|
address: (network.contracts?.p2pix as ChainContract).address,
|
||||||
abi,
|
abi,
|
||||||
functionName: "getBalance",
|
functionName: "getBalance",
|
||||||
args: [seller, token],
|
args: [seller, token],
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { p2PixAbi } from "./abi";
|
import { p2PixAbi } from "./abi";
|
||||||
import { updateWalletStatus } from "./wallet";
|
import { updateWalletStatus } from "./wallet";
|
||||||
import { getProviderUrl, getP2PixAddress } from "./addresses";
|
|
||||||
import {
|
import {
|
||||||
createPublicClient,
|
createPublicClient,
|
||||||
createWalletClient,
|
createWalletClient,
|
||||||
@@ -9,17 +8,19 @@ import {
|
|||||||
PublicClient,
|
PublicClient,
|
||||||
WalletClient,
|
WalletClient,
|
||||||
} from "viem";
|
} from "viem";
|
||||||
import { sepolia, rootstock } from "viem/chains";
|
|
||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
|
import type { ChainContract } from "viem";
|
||||||
|
|
||||||
let walletClient: WalletClient | null = null;
|
let walletClient: WalletClient | null = null;
|
||||||
|
|
||||||
const getPublicClient = (): PublicClient => {
|
const getPublicClient = (): PublicClient => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const rpcUrl = getProviderUrl();
|
const rpcUrl = (user.network.value as NetworkConfig).rpcUrls.default.http[0];
|
||||||
|
const chain = user.network.value;
|
||||||
|
|
||||||
return createPublicClient({
|
return createPublicClient({
|
||||||
chain:
|
chain,
|
||||||
Number(user.networkName.value) === sepolia.id ? sepolia : rootstock,
|
|
||||||
transport: http(rpcUrl),
|
transport: http(rpcUrl),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -30,7 +31,8 @@ const getWalletClient = (): WalletClient | null => {
|
|||||||
|
|
||||||
const getContract = async (onlyRpcProvider = false) => {
|
const getContract = async (onlyRpcProvider = false) => {
|
||||||
const client = getPublicClient();
|
const client = getPublicClient();
|
||||||
const address = getP2PixAddress();
|
const user = useUser();
|
||||||
|
const address = (user.network.value.contracts?.p2pix as ChainContract).address;
|
||||||
const abi = p2PixAbi;
|
const abi = p2PixAbi;
|
||||||
const wallet = onlyRpcProvider ? null : getWalletClient();
|
const wallet = onlyRpcProvider ? null : getWalletClient();
|
||||||
|
|
||||||
@@ -45,8 +47,7 @@ const getContract = async (onlyRpcProvider = false) => {
|
|||||||
|
|
||||||
const connectProvider = async (p: any): Promise<void> => {
|
const connectProvider = async (p: any): Promise<void> => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const chain =
|
const chain = user.network.value;
|
||||||
Number(user.networkName.value) === sepolia.id ? sepolia : rootstock;
|
|
||||||
|
|
||||||
const [account] = await p!.request({ method: "eth_requestAccounts" });
|
const [account] = await p!.request({ method: "eth_requestAccounts" });
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { getContract, getPublicClient, getWalletClient } from "./provider";
|
import { getContract, getPublicClient, getWalletClient } from "./provider";
|
||||||
import { getTokenAddress, getP2PixAddress } from "./addresses";
|
import { parseEther, toHex, ChainContract } from "viem";
|
||||||
import { parseEther, toHex } from "viem";
|
|
||||||
import { sepolia, rootstock } from "viem/chains";
|
|
||||||
|
|
||||||
import { mockTokenAbi } from "./abi";
|
import { mockTokenAbi } from "./abi";
|
||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
import { createParticipant } from "@/utils/bbPay";
|
import { createParticipant } from "@/utils/bbPay";
|
||||||
import type { Participant } from "@/utils/bbPay";
|
import type { Participant } from "@/utils/bbPay";
|
||||||
|
import type { Address } from "viem";
|
||||||
|
|
||||||
|
const getP2PixAddress = (): Address => {
|
||||||
|
const user = useUser();
|
||||||
|
return (user.network.value.contracts?.p2pix as ChainContract).address;
|
||||||
|
};
|
||||||
|
|
||||||
const approveTokens = async (participant: Participant): Promise<any> => {
|
const approveTokens = async (participant: Participant): Promise<any> => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
@@ -21,7 +24,7 @@ const approveTokens = async (participant: Participant): Promise<any> => {
|
|||||||
const [account] = await walletClient.getAddresses();
|
const [account] = await walletClient.getAddresses();
|
||||||
|
|
||||||
// Get token address
|
// Get token address
|
||||||
const tokenAddress = getTokenAddress(user.selectedToken.value);
|
const tokenAddress = user.network.value.tokens[user.selectedToken.value].address;
|
||||||
|
|
||||||
// Check if the token is already approved
|
// Check if the token is already approved
|
||||||
const allowance = await publicClient.readContract({
|
const allowance = await publicClient.readContract({
|
||||||
@@ -33,7 +36,7 @@ const approveTokens = async (participant: Participant): Promise<any> => {
|
|||||||
|
|
||||||
if ( allowance < parseEther(participant.offer.toString()) ) {
|
if ( allowance < parseEther(participant.offer.toString()) ) {
|
||||||
// Approve tokens
|
// Approve tokens
|
||||||
const chain = user.networkId.value === sepolia.id ? sepolia : rootstock;
|
const chain = user.network.value;
|
||||||
const hash = await walletClient.writeContract({
|
const hash = await walletClient.writeContract({
|
||||||
address: tokenAddress,
|
address: tokenAddress,
|
||||||
abi: mockTokenAbi,
|
abi: mockTokenAbi,
|
||||||
@@ -65,15 +68,15 @@ const addDeposit = async (): Promise<any> => {
|
|||||||
if (!sellerId.id) {
|
if (!sellerId.id) {
|
||||||
throw new Error("Failed to create participant");
|
throw new Error("Failed to create participant");
|
||||||
}
|
}
|
||||||
const chain = user.networkId.value === sepolia.id ? sepolia : rootstock;
|
const chain = user.network.value;
|
||||||
const hash = await walletClient.writeContract({
|
const hash = await walletClient.writeContract({
|
||||||
address,
|
address,
|
||||||
abi,
|
abi,
|
||||||
functionName: "deposit",
|
functionName: "deposit",
|
||||||
args: [
|
args: [
|
||||||
user.networkId.value + "-" + sellerId.id,
|
user.network.value.id + "-" + sellerId.id,
|
||||||
toHex("", { size: 32 }),
|
toHex("", { size: 32 }),
|
||||||
getTokenAddress(user.selectedToken.value),
|
user.network.value.tokens[user.selectedToken.value].address,
|
||||||
parseEther(user.seller.value.offer.toString()),
|
parseEther(user.seller.value.offer.toString()),
|
||||||
true,
|
true,
|
||||||
],
|
],
|
||||||
|
|||||||
+11
-28
@@ -1,8 +1,7 @@
|
|||||||
import { formatEther, hexToString, type Address } from "viem";
|
import { formatEther, type Address } from "viem";
|
||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
|
|
||||||
import { getPublicClient, getWalletClient, getContract } from "./provider";
|
import { getPublicClient, getWalletClient, getContract } from "./provider";
|
||||||
import { getTokenAddress } from "./addresses";
|
|
||||||
|
|
||||||
import { getValidDeposits, getUnreleasedLockById } from "./events";
|
import { getValidDeposits, getUnreleasedLockById } from "./events";
|
||||||
|
|
||||||
@@ -10,7 +9,6 @@ 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 { LockStatus } from "@/model/LockStatus";
|
import { LockStatus } from "@/model/LockStatus";
|
||||||
import { getNetworkSubgraphURL } from "@/model/NetworkEnum";
|
|
||||||
|
|
||||||
export const updateWalletStatus = async (): Promise<void> => {
|
export const updateWalletStatus = async (): Promise<void> => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
@@ -36,8 +34,8 @@ export const listValidDepositTransactionsByWalletAddress = async (
|
|||||||
): Promise<ValidDeposit[]> => {
|
): Promise<ValidDeposit[]> => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const walletDeposits = await getValidDeposits(
|
const walletDeposits = await getValidDeposits(
|
||||||
getTokenAddress(user.selectedToken.value),
|
user.network.value.tokens[user.selectedToken.value].address,
|
||||||
user.networkName.value
|
user.network.value
|
||||||
);
|
);
|
||||||
if (walletDeposits) {
|
if (walletDeposits) {
|
||||||
return walletDeposits
|
return walletDeposits
|
||||||
@@ -67,7 +65,7 @@ export const listAllTransactionByWalletAddress = async (
|
|||||||
const user = useUser();
|
const user = useUser();
|
||||||
|
|
||||||
// Get the current network for the subgraph URL
|
// Get the current network for the subgraph URL
|
||||||
const network = user.networkName.value;
|
const network = user.network.value;
|
||||||
|
|
||||||
// Query subgraph for all relevant transactions
|
// Query subgraph for all relevant transactions
|
||||||
const subgraphQuery = {
|
const subgraphQuery = {
|
||||||
@@ -110,7 +108,7 @@ export const listAllTransactionByWalletAddress = async (
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(getNetworkSubgraphURL(network), {
|
const response = await fetch(network.subgraphUrls[0], {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -200,7 +198,7 @@ export const listReleaseTransactionByWalletAddress = async (
|
|||||||
walletAddress: Address
|
walletAddress: Address
|
||||||
) => {
|
) => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const network = user.networkName.value;
|
const network = user.network.value;
|
||||||
|
|
||||||
// Query subgraph for release transactions
|
// Query subgraph for release transactions
|
||||||
const subgraphQuery = {
|
const subgraphQuery = {
|
||||||
@@ -219,7 +217,7 @@ export const listReleaseTransactionByWalletAddress = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Fetch data from subgraph
|
// Fetch data from subgraph
|
||||||
const response = await fetch(getNetworkSubgraphURL(network), {
|
const response = await fetch(network.subgraphUrls[0], {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -263,7 +261,7 @@ export const listReleaseTransactionByWalletAddress = async (
|
|||||||
|
|
||||||
const listLockTransactionByWalletAddress = async (walletAddress: Address) => {
|
const listLockTransactionByWalletAddress = async (walletAddress: Address) => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const network = user.networkName.value;
|
const network = user.network.value;
|
||||||
|
|
||||||
// Query subgraph for lock added transactions
|
// Query subgraph for lock added transactions
|
||||||
const subgraphQuery = {
|
const subgraphQuery = {
|
||||||
@@ -284,7 +282,7 @@ const listLockTransactionByWalletAddress = async (walletAddress: Address) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch data from subgraph
|
// Fetch data from subgraph
|
||||||
const response = await fetch(getNetworkSubgraphURL(network), {
|
const response = await fetch(network.subgraphUrls[0], {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -332,7 +330,7 @@ const listLockTransactionByWalletAddress = async (walletAddress: Address) => {
|
|||||||
|
|
||||||
const listLockTransactionBySellerAddress = async (sellerAddress: Address) => {
|
const listLockTransactionBySellerAddress = async (sellerAddress: Address) => {
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const network = user.networkName.value;
|
const network = user.network.value;
|
||||||
|
|
||||||
// Query subgraph for lock added transactions where seller matches
|
// Query subgraph for lock added transactions where seller matches
|
||||||
const subgraphQuery = {
|
const subgraphQuery = {
|
||||||
@@ -354,7 +352,7 @@ const listLockTransactionBySellerAddress = async (sellerAddress: Address) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch data from subgraph
|
// Fetch data from subgraph
|
||||||
const response = await fetch(getNetworkSubgraphURL(network), {
|
const response = await fetch(network.subgraphUrls[0], {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -463,18 +461,3 @@ export const getActiveLockAmount = async (
|
|||||||
return total;
|
return total;
|
||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSellerParticipantId = async (
|
|
||||||
sellerAddress: Address,
|
|
||||||
tokenAddress: Address
|
|
||||||
): Promise<string> => {
|
|
||||||
const { address, abi, client } = await getContract();
|
|
||||||
|
|
||||||
const participantId = await client.readContract({
|
|
||||||
address,
|
|
||||||
abi,
|
|
||||||
functionName: "getPixTarget",
|
|
||||||
args: [sellerAddress, tokenAddress],
|
|
||||||
});
|
|
||||||
return hexToString(participantId);
|
|
||||||
};
|
|
||||||
+3
-3
@@ -5,12 +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 { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
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<{
|
||||||
@@ -53,7 +53,7 @@ const getWalletTransactions = async () => {
|
|||||||
const callWithdraw = async (amount: string) => {
|
const callWithdraw = async (amount: string) => {
|
||||||
if (amount) {
|
if (amount) {
|
||||||
user.setLoadingWalletTransactions(true);
|
user.setLoadingWalletTransactions(true);
|
||||||
const withdraw = await withdrawDeposit(amount, user.selectedToken.value);
|
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();
|
||||||
+10
-11
@@ -1,16 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
import SpinnerComponent from "@/components/SpinnerComponent.vue";
|
import SpinnerComponent from "@/components/ui/SpinnerComponent.vue";
|
||||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
import CustomButton from "@/components/ui/CustomButton.vue";
|
||||||
import { debounce } from "@/utils/debounce";
|
import { debounce } from "@/utils/debounce";
|
||||||
import { verifyNetworkLiquidity } from "@/utils/networkLiquidity";
|
import { verifyNetworkLiquidity } from "@/utils/networkLiquidity";
|
||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
import { decimalCount } from "@/utils/decimalCount";
|
import { decimalCount } from "@/utils/decimalCount";
|
||||||
import { getTokenImage } from "@/utils/imagesPath";
|
import { getTokenImage } from "@/utils/imagesPath";
|
||||||
import { onClickOutside } from "@vueuse/core";
|
import { onClickOutside } from "@vueuse/core";
|
||||||
|
import { Networks } from "@/config/networks";
|
||||||
import { TokenEnum } from "@/model/NetworkEnum";
|
import { TokenEnum } from "@/model/NetworkEnum";
|
||||||
|
|
||||||
// Store reference
|
// Store reference
|
||||||
@@ -19,7 +18,7 @@ const selectTokenToggle = ref<boolean>(false);
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
walletAddress,
|
walletAddress,
|
||||||
networkName,
|
network,
|
||||||
selectedToken,
|
selectedToken,
|
||||||
depositsValidList,
|
depositsValidList,
|
||||||
loadingNetworkLiquidity,
|
loadingNetworkLiquidity,
|
||||||
@@ -51,7 +50,7 @@ const connectAccount = async (): Promise<void> => {
|
|||||||
|
|
||||||
const emitConfirmButton = async (): Promise<void> => {
|
const emitConfirmButton = async (): Promise<void> => {
|
||||||
const deposit = selectedDeposits.value?.find(
|
const deposit = selectedDeposits.value?.find(
|
||||||
(d) => d.network === Number(networkName.value)
|
(d) => d.network === network.value
|
||||||
);
|
);
|
||||||
if (!deposit) return;
|
if (!deposit) return;
|
||||||
deposit.participantID = await getParticipantID(deposit.seller, deposit.token);
|
deposit.participantID = await getParticipantID(deposit.seller, deposit.token);
|
||||||
@@ -99,7 +98,7 @@ const verifyLiquidity = (): void => {
|
|||||||
);
|
);
|
||||||
selectedDeposits.value = selDeposits;
|
selectedDeposits.value = selDeposits;
|
||||||
hasLiquidity.value = !!selDeposits.find(
|
hasLiquidity.value = !!selDeposits.find(
|
||||||
(d) => d.network === Number(networkName.value)
|
(d) => d.network === network.value
|
||||||
);
|
);
|
||||||
enableOrDisableConfirmButton();
|
enableOrDisableConfirmButton();
|
||||||
};
|
};
|
||||||
@@ -110,7 +109,7 @@ const enableOrDisableConfirmButton = (): void => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedDeposits.value.find((d) => d.network === networkName.value)) {
|
if (!selectedDeposits.value.find((d) => d.network === network.value)) {
|
||||||
enableConfirmButton.value = false;
|
enableConfirmButton.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -118,7 +117,7 @@ const enableOrDisableConfirmButton = (): void => {
|
|||||||
enableConfirmButton.value = true;
|
enableConfirmButton.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(networkName, (): void => {
|
watch(network, (): void => {
|
||||||
verifyLiquidity();
|
verifyLiquidity();
|
||||||
enableOrDisableConfirmButton();
|
enableOrDisableConfirmButton();
|
||||||
});
|
});
|
||||||
@@ -239,7 +238,7 @@ const handleSubmit = async (e: Event): Promise<void> => {
|
|||||||
height="24"
|
height="24"
|
||||||
v-if="
|
v-if="
|
||||||
selectedDeposits &&
|
selectedDeposits &&
|
||||||
selectedDeposits.find((d) => d.network == NetworkEnum.rootstock)
|
selectedDeposits.find((d) => d.network == Networks.rootstockTestnet)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
@@ -249,7 +248,7 @@ const handleSubmit = async (e: Event): Promise<void> => {
|
|||||||
height="24"
|
height="24"
|
||||||
v-if="
|
v-if="
|
||||||
selectedDeposits &&
|
selectedDeposits &&
|
||||||
selectedDeposits.find((d) => d.network == NetworkEnum.sepolia)
|
selectedDeposits.find((d) => d.network == Networks.sepolia)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
import CustomButton from "@/components/ui/CustomButton.vue";
|
||||||
import CustomModal from "@/components//CustomModal/CustomModal.vue";
|
import CustomModal from "@/components/ui/CustomModal.vue";
|
||||||
import SpinnerComponent from "@/components/SpinnerComponent.vue";
|
import SpinnerComponent from "@/components/ui/SpinnerComponent.vue";
|
||||||
import { createSolicitation, getSolicitation, type Offer } from "@/utils/bbPay";
|
import { createSolicitation, getSolicitation, type Offer } from "@/utils/bbPay";
|
||||||
import { getSellerParticipantId } from "@/blockchain/wallet";
|
import { getParticipantID } from "@/blockchain/events";
|
||||||
import { getUnreleasedLockById } from "@/blockchain/events";
|
import { getUnreleasedLockById } from "@/blockchain/events";
|
||||||
import QRCode from "qrcode";
|
import QRCode from "qrcode";
|
||||||
|
|
||||||
@@ -18,10 +18,12 @@ const props = defineProps<Props>();
|
|||||||
const qrCode = ref<string>("");
|
const qrCode = ref<string>("");
|
||||||
const qrCodeSvg = ref<string>("");
|
const qrCodeSvg = ref<string>("");
|
||||||
const showWarnModal = ref<boolean>(true);
|
const showWarnModal = ref<boolean>(true);
|
||||||
const pixTarget = ref<string>("");
|
const pixTimestamp = ref<string>("");
|
||||||
const releaseSignature = ref<string>("");
|
const releaseSignature = ref<string>("");
|
||||||
const solicitationData = ref<any>(null);
|
const solicitationData = ref<any>(null);
|
||||||
const pollingInterval = ref<NodeJS.Timeout | null>(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
|
// Function to generate QR code SVG
|
||||||
const generateQrCodeSvg = async (text: string) => {
|
const generateQrCodeSvg = async (text: string) => {
|
||||||
@@ -56,7 +58,7 @@ const checkSolicitationStatus = async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (response.signature) {
|
if (response.signature) {
|
||||||
pixTarget.value = response.pixTarget;
|
pixTimestamp.value = response.pixTimestamp;
|
||||||
releaseSignature.value = response.signature;
|
releaseSignature.value = response.signature;
|
||||||
// Stop polling when payment is confirmed
|
// Stop polling when payment is confirmed
|
||||||
if (pollingInterval.value) {
|
if (pollingInterval.value) {
|
||||||
@@ -80,13 +82,36 @@ const startPolling = () => {
|
|||||||
pollingInterval.value = setInterval(checkSolicitationStatus, 10000);
|
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 () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
const { tokenAddress, sellerAddress, amount } = await getUnreleasedLockById(
|
const { tokenAddress, sellerAddress, amount } = await getUnreleasedLockById(
|
||||||
BigInt(props.lockID)
|
BigInt(props.lockID)
|
||||||
);
|
);
|
||||||
|
|
||||||
const participantId = await getSellerParticipantId(
|
const participantId = await getParticipantID(
|
||||||
sellerAddress,
|
sellerAddress,
|
||||||
tokenAddress
|
tokenAddress
|
||||||
);
|
);
|
||||||
@@ -119,6 +144,10 @@ onUnmounted(() => {
|
|||||||
clearInterval(pollingInterval.value);
|
clearInterval(pollingInterval.value);
|
||||||
pollingInterval.value = null;
|
pollingInterval.value = null;
|
||||||
}
|
}
|
||||||
|
if (copyFeedbackTimeout.value) {
|
||||||
|
clearTimeout(copyFeedbackTimeout.value);
|
||||||
|
copyFeedbackTimeout.value = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -156,13 +185,24 @@ onUnmounted(() => {
|
|||||||
{{ qrCode }}
|
{{ qrCode }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-col items-center gap-1">
|
||||||
<img
|
<img
|
||||||
alt="Copy PIX code"
|
alt="Copy PIX code"
|
||||||
src="@/assets/copyPix.svg?url"
|
src="@/assets/copyPix.svg?url"
|
||||||
width="16"
|
width="16"
|
||||||
height="16"
|
height="16"
|
||||||
class="pt-2 lg:mb-5 cursor-pointer"
|
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>
|
</div>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
:is-disabled="releaseSignature === ''"
|
:is-disabled="releaseSignature === ''"
|
||||||
@@ -170,7 +210,7 @@ onUnmounted(() => {
|
|||||||
releaseSignature ? 'Enviar para a rede' : 'Validando pagamento...'
|
releaseSignature ? 'Enviar para a rede' : 'Validando pagamento...'
|
||||||
"
|
"
|
||||||
@button-clicked="
|
@button-clicked="
|
||||||
emit('pixValidated', { pixTarget, signature: releaseSignature })
|
emit('pixValidated', { pixTimestamp, signature: releaseSignature })
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,4 +280,15 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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>
|
</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>
|
|
||||||
@@ -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,13 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
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 { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
import { ref, watch, onMounted } from "vue";
|
import { ref, watch } from "vue";
|
||||||
import SpinnerComponent from "../SpinnerComponent.vue";
|
import SpinnerComponent from "../ui/SpinnerComponent.vue";
|
||||||
import { decimalCount } from "@/utils/decimalCount";
|
import BalanceCard from "./BalanceCard.vue";
|
||||||
import { debounce } from "@/utils/debounce";
|
import TransactionCard from "./TransactionCard.vue";
|
||||||
import { useFloating, arrow, offset, flip, shift } from "@floating-ui/vue";
|
|
||||||
|
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
|
|
||||||
@@ -22,80 +20,10 @@ const emit = defineEmits(["depositWithdrawn"]);
|
|||||||
|
|
||||||
const { loadingWalletTransactions } = user;
|
const { loadingWalletTransactions } = user;
|
||||||
|
|
||||||
const remaining = ref<number>(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.length > 0) {
|
|
||||||
// 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 user.networkName.value == NetworkEnum.sepolia
|
|
||||||
? "Etherscan"
|
|
||||||
: "Polygonscan";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const showInitialItems = (): void => {
|
const showInitialItems = (): void => {
|
||||||
@@ -103,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;
|
||||||
user.networkName.value == NetworkEnum.sepolia
|
|
||||||
? "sepolia.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");
|
||||||
};
|
};
|
||||||
@@ -118,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;
|
||||||
@@ -167,179 +67,23 @@ showInitialItems();
|
|||||||
<SpinnerComponent width="8" height="8"></SpinnerComponent>
|
<SpinnerComponent width="8" height="8"></SpinnerComponent>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-container max-w-md" v-else>
|
<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() }} {{ user.selectedToken.value }}
|
|
||||||
</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)} ${
|
|
||||||
user.selectedToken.value
|
|
||||||
} 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">
|
|
||||||
<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?url"
|
|
||||||
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?url"
|
|
||||||
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 }}
|
|
||||||
<!-- {{ getTokenByAddress(item.token) }} -->
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col items-center justify-center">
|
|
||||||
<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?url"
|
|
||||||
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="
|
||||||
@@ -349,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>
|
||||||
|
|
||||||
@@ -367,63 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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"] {
|
|
||||||
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>
|
</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,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
import CustomButton from "@/components/ui/CustomButton.vue";
|
||||||
import { postProcessKey } from "@/utils/pixKeyFormat";
|
import { postProcessKey } from "@/utils/pixKeyFormat";
|
||||||
import { TokenEnum } from "@/model/NetworkEnum";
|
import { TokenEnum } from "@/model/NetworkEnum";
|
||||||
import { getTokenImage } from "@/utils/imagesPath";
|
import { getTokenImage } from "@/utils/imagesPath";
|
||||||
@@ -71,7 +71,7 @@ const handleSubmit = (e: Event): void => {
|
|||||||
|
|
||||||
const data: Participant = {
|
const data: Participant = {
|
||||||
offer: offer.value,
|
offer: offer.value,
|
||||||
chainID: user.networkId.value,
|
chainID: user.network.value.id,
|
||||||
identification: processedIdentification,
|
identification: processedIdentification,
|
||||||
bankIspb: selectedBank.value?.ISPB,
|
bankIspb: selectedBank.value?.ISPB,
|
||||||
accountType: accountType.value,
|
accountType: accountType.value,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
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 { getTokenImage } from "@/utils/imagesPath";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<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"]);
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
import { onClickOutside } from "@vueuse/core";
|
import { onClickOutside } from "@vueuse/core";
|
||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
|
||||||
import { getNetworkImage } from "@/utils/imagesPath";
|
import { getNetworkImage } from "@/utils/imagesPath";
|
||||||
import { Networks } from "@/model/Networks";
|
import { Networks } from "@/config/networks";
|
||||||
|
|
||||||
import { useOnboard } from "@web3-onboard/vue";
|
import { useOnboard } from "@web3-onboard/vue";
|
||||||
|
|
||||||
import ChevronDown from "@/assets/chevronDown.svg";
|
import ChevronDown from "@/assets/chevronDown.svg";
|
||||||
@@ -13,10 +11,12 @@ import TwitterIcon from "@/assets/twitterIcon.svg";
|
|||||||
import LinkedinIcon from "@/assets/linkedinIcon.svg";
|
import LinkedinIcon from "@/assets/linkedinIcon.svg";
|
||||||
import GithubIcon from "@/assets/githubIcon.svg";
|
import GithubIcon from "@/assets/githubIcon.svg";
|
||||||
import { connectProvider } from "@/blockchain/provider";
|
import { connectProvider } from "@/blockchain/provider";
|
||||||
|
import { DEFAULT_NETWORK } from "@/config/networks";
|
||||||
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
|
|
||||||
// Use the new composable
|
// Use the new composable
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const { walletAddress, sellerView, networkId } = user;
|
const { walletAddress, sellerView, network } = user;
|
||||||
|
|
||||||
const menuOpenToggle = ref<boolean>(false);
|
const menuOpenToggle = ref<boolean>(false);
|
||||||
const infoMenuOpenToggle = ref<boolean>(false);
|
const infoMenuOpenToggle = ref<boolean>(false);
|
||||||
@@ -40,20 +40,20 @@ watch(connectedWallet, async (newVal: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
watch(connectedChain, (newVal: any) => {
|
watch(connectedChain, (newVal: any) => {
|
||||||
// Check if connected chain is valid, otherwise default to Sepolia (NetworkEnum.SEPOLIA)
|
// Check if connected chain is valid, otherwise default to Sepolia
|
||||||
if (
|
if (
|
||||||
!newVal ||
|
!newVal ||
|
||||||
!Object.values(Networks).some(
|
!Object.values(Networks).some(
|
||||||
(network) => Number(network.chainId) === Number(newVal.id)
|
(network) => network.id === Number(newVal.id)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
"Invalid or unsupported network detected, defaulting to Sepolia"
|
"Invalid or unsupported network detected, defaulting to Sepolia"
|
||||||
);
|
);
|
||||||
user.setNetworkId(user.networkId.value);
|
user.setNetwork(DEFAULT_NETWORK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
user.setNetworkId(newVal?.id);
|
user.setNetworkById(newVal?.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
const formatWalletAddress = (): string => {
|
const formatWalletAddress = (): string => {
|
||||||
@@ -78,14 +78,15 @@ 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 chainId = network.id.toString(16)
|
||||||
try {
|
try {
|
||||||
await setChain({
|
await setChain({
|
||||||
chainId: Networks[network].chainId,
|
chainId: `0x${chainId}`,
|
||||||
wallet: connectedWallet.value?.label || "",
|
wallet: connectedWallet.value?.label || "",
|
||||||
});
|
});
|
||||||
user.setNetworkId(network);
|
user.setNetwork(network);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error changing network", error);
|
console.log("Error changing network", error);
|
||||||
}
|
}
|
||||||
@@ -258,7 +259,7 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Choosed network image"
|
alt="Choosed network image"
|
||||||
:src="getNetworkImage(NetworkEnum[user.networkName.value])"
|
:src="getNetworkImage(network.name)"
|
||||||
height="24"
|
height="24"
|
||||||
width="24"
|
width="24"
|
||||||
/>
|
/>
|
||||||
@@ -267,9 +268,7 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
:class="{ '!text-gray-900': currencyMenuOpenToggle }"
|
:class="{ '!text-gray-900': currencyMenuOpenToggle }"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
Networks[user.networkName.value]
|
user.network.value.name || "Invalid Chain"
|
||||||
? Networks[user.networkName.value].chainName
|
|
||||||
: "Invalid Chain"
|
|
||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
@@ -292,20 +291,20 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
class="mt-2 bg-white rounded-md border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
class="mt-2 bg-white rounded-md border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(chainData, network) in Networks"
|
v-for="network in Networks"
|
||||||
:key="network"
|
: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"
|
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)"
|
@click="networkChange(network)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:alt="chainData.chainName + ' image'"
|
:alt="network.name + ' image'"
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
:src="getNetworkImage(NetworkEnum[network])"
|
:src="getNetworkImage(network.name)"
|
||||||
class="mr-2 ml-1"
|
class="mr-2 ml-1"
|
||||||
/>
|
/>
|
||||||
<span class="text-gray-900 font-semibold text-sm">
|
<span class="text-gray-900 font-semibold text-sm">
|
||||||
{{ chainData.chainName }}
|
{{ network.name }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
@@ -462,19 +461,19 @@ onClickOutside(infoMenuRef, () => {
|
|||||||
<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
|
||||||
v-for="(chainData, network) in Networks"
|
v-for="network in Networks"
|
||||||
:key="network"
|
:key="network.id"
|
||||||
class="menu-button gap-2 sm:px-4 rounded-md cursor-pointer py-2 px-4"
|
class="menu-button gap-2 sm:px-4 rounded-md cursor-pointer py-2 px-4"
|
||||||
@click="networkChange(network)"
|
@click="networkChange(network)"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:alt="chainData.chainName + 'image'"
|
:alt="network.name + 'image'"
|
||||||
width="20"
|
width="20"
|
||||||
height="20"
|
height="20"
|
||||||
:src="getNetworkImage(NetworkEnum[network])"
|
: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">
|
||||||
{{ chainData.chainName }}
|
{{ network.name }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
File renamed without changes.
@@ -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>
|
||||||
File renamed without changes.
@@ -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>
|
||||||
|
|
||||||
File renamed without changes.
@@ -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>
|
||||||
|
|
||||||
File renamed without changes.
@@ -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>
|
||||||
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, onMounted } from "vue";
|
import { ref, computed, watch, onMounted } from "vue";
|
||||||
import { useOnboard } from "@web3-onboard/vue";
|
import { useOnboard } from "@web3-onboard/vue";
|
||||||
import { Networks } from "../model/Networks";
|
import { Networks } from "@/config/networks";
|
||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
|
|
||||||
const { connectedWallet } = useOnboard();
|
const { connectedWallet } = useOnboard();
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const { networkId, networkName } = user;
|
const { network } = user;
|
||||||
|
|
||||||
const isWrongNetwork = ref(false);
|
const isWrongNetwork = ref(false);
|
||||||
const targetNetworkName = computed(() => Networks[networkName.value].chainName);
|
const targetNetworkName = computed(() => network.value.name);
|
||||||
|
|
||||||
const checkNetwork = () => {
|
const checkNetwork = () => {
|
||||||
if (connectedWallet.value) {
|
if (connectedWallet.value) {
|
||||||
const chainId = connectedWallet.value.chains[0].id;
|
const chainId = connectedWallet.value.chains[0].id;
|
||||||
isWrongNetwork.value = Number(chainId) !== networkId.value;
|
isWrongNetwork.value = Number(chainId) !== network.value.id;
|
||||||
} else {
|
} else {
|
||||||
isWrongNetwork.value = false; // No wallet connected yet
|
isWrongNetwork.value = false; // No wallet connected yet
|
||||||
}
|
}
|
||||||
@@ -23,11 +23,12 @@ const checkNetwork = () => {
|
|||||||
const switchNetwork = async () => {
|
const switchNetwork = async () => {
|
||||||
try {
|
try {
|
||||||
if (connectedWallet.value && connectedWallet.value.provider) {
|
if (connectedWallet.value && connectedWallet.value.provider) {
|
||||||
|
let chainId = network.value.id.toString(16);
|
||||||
await connectedWallet.value.provider.request({
|
await connectedWallet.value.provider.request({
|
||||||
method: "wallet_switchEthereumChain",
|
method: "wallet_switchEthereumChain",
|
||||||
params: [
|
params: [
|
||||||
{
|
{
|
||||||
chainId: Networks[networkName.value].chainId,
|
chainId: `0x${chainId}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -39,7 +40,7 @@ const switchNetwork = async () => {
|
|||||||
|
|
||||||
onMounted(checkNetwork);
|
onMounted(checkNetwork);
|
||||||
watch(connectedWallet, checkNetwork);
|
watch(connectedWallet, checkNetwork);
|
||||||
watch(networkId, checkNetwork, { immediate: true });
|
watch(network, checkNetwork, { immediate: true });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -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>
|
||||||
|
|
||||||
+30
-11
@@ -1,15 +1,14 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { NetworkEnum, TokenEnum } from "../model/NetworkEnum";
|
|
||||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||||
import type { Participant } from "../utils/bbPay";
|
import type { Participant } from "../utils/bbPay";
|
||||||
import { NetworkById } from "@/model/Networks";
|
|
||||||
import type { Address } from "viem"
|
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 walletAddress = ref<Address | null>(null);
|
||||||
const balance = ref("");
|
const balance = ref("");
|
||||||
const networkId = ref(11155111);
|
const network = ref(DEFAULT_NETWORK);
|
||||||
const networkName = ref(NetworkEnum.sepolia);
|
const selectedToken = ref<TokenEnum>(TokenEnum.BRZ);
|
||||||
const selectedToken = ref(TokenEnum.BRZ);
|
|
||||||
const loadingLock = ref(false);
|
const loadingLock = ref(false);
|
||||||
const sellerView = ref(false);
|
const sellerView = ref(false);
|
||||||
const depositsValidList = ref<ValidDeposit[]>([]);
|
const depositsValidList = ref<ValidDeposit[]>([]);
|
||||||
@@ -32,9 +31,29 @@ export function useUser() {
|
|||||||
selectedToken.value = token;
|
selectedToken.value = token;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setNetworkId = (network: string | number) => {
|
const setNetwork = (chain: NetworkConfig) => {
|
||||||
networkName.value = NetworkById(network) || NetworkEnum.sepolia;
|
network.value = chain;
|
||||||
networkId.value = Number(network);
|
};
|
||||||
|
|
||||||
|
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) => {
|
const setLoadingLock = (isLoading: boolean) => {
|
||||||
@@ -76,8 +95,7 @@ export function useUser() {
|
|||||||
// State
|
// State
|
||||||
walletAddress,
|
walletAddress,
|
||||||
balance,
|
balance,
|
||||||
networkId,
|
network,
|
||||||
networkName,
|
|
||||||
selectedToken,
|
selectedToken,
|
||||||
loadingLock,
|
loadingLock,
|
||||||
sellerView,
|
sellerView,
|
||||||
@@ -91,7 +109,8 @@ export function useUser() {
|
|||||||
setWalletAddress,
|
setWalletAddress,
|
||||||
setBalance,
|
setBalance,
|
||||||
setSelectedToken,
|
setSelectedToken,
|
||||||
setNetworkId,
|
setNetwork,
|
||||||
|
setNetworkById,
|
||||||
setLoadingLock,
|
setLoadingLock,
|
||||||
setSellerView,
|
setSellerView,
|
||||||
setDepositsValidList,
|
setDepositsValidList,
|
||||||
|
|||||||
@@ -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,18 +1,10 @@
|
|||||||
export enum NetworkEnum {
|
import type { Chain, ChainContract } from "viem";
|
||||||
sepolia = 11155111,
|
|
||||||
rootstock = 31,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getNetworkSubgraphURL = (network: NetworkEnum) => {
|
|
||||||
const networkMap: Record<NetworkEnum, string> = {
|
|
||||||
[NetworkEnum.sepolia]: import.meta.env.VITE_SEPOLIA_SUBGRAPH_URL || "",
|
|
||||||
[NetworkEnum.rootstock]: import.meta.env.VITE_RSK_SUBGRAPH_URL || "",
|
|
||||||
};
|
|
||||||
|
|
||||||
return networkMap[network] || "";
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum TokenEnum {
|
export enum TokenEnum {
|
||||||
BRZ = "BRZ",
|
BRZ = 'BRZ',
|
||||||
// BRX = 'BRX'
|
// BRX = 'BRX'
|
||||||
}
|
}
|
||||||
|
export type NetworkConfig = Chain & {
|
||||||
|
tokens: Record<TokenEnum, ChainContract>,
|
||||||
|
subgraphUrls: string[]
|
||||||
|
};
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
|
||||||
|
|
||||||
export const NetworkById = (
|
|
||||||
chainId: string | number
|
|
||||||
): NetworkEnum | undefined => {
|
|
||||||
const normalizedChainId =
|
|
||||||
typeof chainId === "number" ? chainId : Number(chainId);
|
|
||||||
|
|
||||||
for (const [network, details] of Object.entries(Networks)) {
|
|
||||||
if (Number(details.chainId) === normalizedChainId) {
|
|
||||||
return network as unknown as NetworkEnum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Networks = {
|
|
||||||
[NetworkEnum.sepolia]: {
|
|
||||||
chainId: "0xAA36A7",
|
|
||||||
chainName: "Sepolia Testnet",
|
|
||||||
},
|
|
||||||
[NetworkEnum.rootstock]: {
|
|
||||||
chainId: "0x1F",
|
|
||||||
chainName: "Rootstock Testnet",
|
|
||||||
rpcUrls: ["https://public-node.testnet.rsk.co/"],
|
|
||||||
iconUrls: [
|
|
||||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAPOSURBVHgBxVhNUhpBFH6vGdxp4S4LoSYnEE8gnEA4AbpMJUQ4gXgCRJK1egLxBOIJJCdwJElVllMu49Cd1z04zD/dY1H5qihmut/M93VPv59uhHdAXFaPAaEDgA2/BaeA4hq/zG+gIBAKQoyr9yshid4Jdn+2oQAYFIC4rA2zxUhgS3yrDqEAjGdIDD/YYG09aRl7L7vYd10wgPkMlcoNfdvtFhjCXJBAeyO2S5gLQuFo25bEIxjCCt8oN2Z46I+Mu4A4SbjwojQBi1+BDl5LP+JNYlhtQRmPsjjQN1ILldwY7JTXOuD9bWL/jxO8dFy7oL9TyMcIu/PeSghxlLduQUA9jwPXiAk98HLw5jFiaFfAEjRLImPR0qi7z+2VmArZ7zzqcDAS01ljCKqf7QSjxb7jKkIhTohu6rOCq64RjsNiFEo7x7ocSNMvlddhPWb0CQ6gAAw4HKZpKGFDcWhzSEG6kbQCm4dLbi9m+XlpBTHea2D31zTSNtxrAGMNdcP5FPuxfhlKdCHgASUJxcd7zUcobkAPXvkzWGyf7uVCt2M2DtkMljaHSxu92WWLAz8OjWsD+juD/4tzcpqBSh3yQrmwoNFFMZNuDB7bJRsp/hzMMQqeT+NQ96KtNEBK+SG+23XgHgUyy8FPjpPozy3M4sZwh1/nLRMOK26Mn50Z5IHjA6XkBugJSn1XHkeBbK8dJsxsl0jMEOUpm0o9+gkX+7+TI0E+0x6Hsk0ijyNYQ/4OAqWn2aF+5cLxEoRq6idqtyEPtFhp/XyMNI2p9ADFUc/iYL5h7YzEXEEyptj04mvVHxkGP4F8MS4sWDsqRr4DbyGZRiIcqCKtpRMYeTMcpVVAFewqMVPSjUkMVQTBp6BPVKeiTqN65E0qP1AvIArWC98qcQsms39oDeBEtoXFKFgLbQ76ZKiXiRH2E01UF9Go+kGDh32/LWHZAD2OQ7mGdLO4ndrqWaHZyNyD6XJUWEq6yIQqReOweCe49ivD2DNUIutjJgXpHwyUtyPbY/IMWehfBA0IZxQSQoW9rKXL+ltq0oKqYC+RB6yLKys4xEw/Idde5R02cTGOcgh1LSNnid+nihIqcN0tr48MhL89L2uoG+Dqv5Px/IwqAhkqnEi296M1OyLPqVCgdKhcuKNjlUnQL4X78cRk1E1JlMkBME1sFE0gRrRJZGs3iT44bRZP5z0wQJHzIZMMbpztN1t+FDhsMBe0YNfatimHDetgLGiZGkYapqPwYt6YIAWPDYI9fSrETfjkwwSFT2EVrV/USY+r+/GGNp2I7zoW/gdR9aOdZ/lPGgAAAABJRU5ErkJggg==",
|
|
||||||
],
|
|
||||||
nativeCurrency: {
|
|
||||||
name: "tRBTC",
|
|
||||||
symbol: "tRBTC",
|
|
||||||
decimals: 18,
|
|
||||||
},
|
|
||||||
blockExplorerUrls: ["https://explorer.testnet.rootstock.io/"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NetworkEnum } from "./NetworkEnum";
|
|
||||||
import type { Address } from "viem";
|
import type { Address } from "viem";
|
||||||
|
import type { NetworkConfig } from "@/model/NetworkEnum";
|
||||||
|
|
||||||
export type ValidDeposit = {
|
export type ValidDeposit = {
|
||||||
token: Address;
|
token: Address;
|
||||||
@@ -7,6 +7,6 @@ export type ValidDeposit = {
|
|||||||
remaining: number;
|
remaining: number;
|
||||||
seller: Address;
|
seller: Address;
|
||||||
participantID: string;
|
participantID: string;
|
||||||
network: NetworkEnum;
|
network: NetworkConfig;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
};
|
};
|
||||||
+3
-3
@@ -61,15 +61,15 @@ export const createSolicitation = async (offer: Offer) => {
|
|||||||
return response.json();
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSolicitation = async (id: string) => {
|
export const getSolicitation = async (id: bigint): Promise<{pixTimestamp: `0x${string}`, signature: `0x${string}`}> => {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${import.meta.env.VITE_APP_API_URL}/release/${id}`
|
`${import.meta.env.VITE_APP_API_URL}/release/${id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const obj: any = await response.json();
|
const obj = await response.json();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pixTarget: obj.pixTarget,
|
pixTimestamp: obj.pixTimestamp,
|
||||||
signature: obj.signature,
|
signature: obj.signature,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
+4
-23
@@ -1,30 +1,11 @@
|
|||||||
import type { TokenEnum } from "@/model/NetworkEnum";
|
import type { TokenEnum } from "@/model/NetworkEnum";
|
||||||
|
import { Networks } from "@/config/networks";
|
||||||
export const imagesPath = import.meta.glob<string>("@/assets/*.{png,svg}", {
|
|
||||||
eager: true,
|
|
||||||
query: "?url",
|
|
||||||
import: "default",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getNetworkImage = (networkName: string): string => {
|
export const getNetworkImage = (networkName: string): string => {
|
||||||
try {
|
const normalizedName = networkName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
||||||
const path = Object.keys(imagesPath).find((key) =>
|
return new URL(`../assets/networks/${normalizedName}.svg`, import.meta.url).href;
|
||||||
key.endsWith(`${networkName.toLowerCase()}.svg`)
|
|
||||||
);
|
|
||||||
return path ? imagesPath[path] : "";
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching network image");
|
|
||||||
const path = Object.keys(imagesPath).find((key) =>
|
|
||||||
key.endsWith(`invalidIcon.svg`)
|
|
||||||
);
|
|
||||||
return path ? imagesPath[path] : "";
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTokenImage = (tokenName: TokenEnum): string => {
|
export const getTokenImage = (tokenName: TokenEnum): string => {
|
||||||
const path = Object.keys(imagesPath).find((key) =>
|
return new URL(`../assets/tokens/${tokenName.toLowerCase()}.svg`, import.meta.url).href;
|
||||||
key.endsWith(`${tokenName.toLowerCase()}.svg`)
|
|
||||||
);
|
|
||||||
return path ? imagesPath[path] : "";
|
|
||||||
};
|
};
|
||||||
+11
-15
@@ -1,16 +1,16 @@
|
|||||||
<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 { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
import QrCodeComponent from "@/components/QrCodeComponent.vue";
|
import QrCodeComponent from "@/components/BuyerSteps/QrCodeComponent.vue";
|
||||||
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 { getSolicitation } from "@/utils/bbPay";
|
||||||
import type { Address } from "viem";
|
import type { Address } from "viem";
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ const user = useUser();
|
|||||||
user.setSellerView(false);
|
user.setSellerView(false);
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const { loadingLock, walletAddress, networkName } = user;
|
const { loadingLock, walletAddress, network } = user;
|
||||||
const flowStep = ref<Step>(Step.Search);
|
const flowStep = ref<Step>(Step.Search);
|
||||||
const participantID = ref<string>();
|
const participantID = ref<string>();
|
||||||
const sellerAddress = ref<Address>();
|
const sellerAddress = ref<Address>();
|
||||||
@@ -59,19 +59,15 @@ const confirmBuyClick = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const releaseTransaction = async ({
|
const releaseTransaction = async (params: {
|
||||||
pixTarget,
|
pixTimestamp: `0x${string}`&{lenght:34},
|
||||||
signature,
|
signature: `0x${string}`,
|
||||||
}: {
|
|
||||||
pixTarget: string;
|
|
||||||
signature: string;
|
|
||||||
}) => {
|
}) => {
|
||||||
flowStep.value = Step.List;
|
flowStep.value = Step.List;
|
||||||
showBuyAlert.value = true;
|
showBuyAlert.value = true;
|
||||||
loadingRelease.value = true;
|
loadingRelease.value = true;
|
||||||
|
|
||||||
const release = await releaseLock(BigInt(lockID.value), pixTarget, signature);
|
const release = await releaseLock(BigInt(lockID.value), params.pixTimestamp, params.signature);
|
||||||
await release.wait();
|
|
||||||
|
|
||||||
await updateWalletStatus();
|
await updateWalletStatus();
|
||||||
loadingRelease.value = false;
|
loadingRelease.value = false;
|
||||||
@@ -107,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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import { ref, onMounted, watch } from "vue";
|
import { ref, onMounted, watch } from "vue";
|
||||||
import { useUser } from "@/composables/useUser";
|
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 {
|
import {
|
||||||
listValidDepositTransactionsByWalletAddress,
|
listValidDepositTransactionsByWalletAddress,
|
||||||
listAllTransactionByWalletAddress,
|
listAllTransactionByWalletAddress,
|
||||||
@@ -16,7 +16,7 @@ import type { WalletTransaction } from "@/model/WalletTransaction";
|
|||||||
import router from "@/router/index";
|
import router from "@/router/index";
|
||||||
|
|
||||||
const user = useUser();
|
const user = useUser();
|
||||||
const { walletAddress, networkName, selectedToken } = user;
|
const { walletAddress, network, selectedToken } = user;
|
||||||
const loadingWithdraw = ref<boolean>(false);
|
const loadingWithdraw = ref<boolean>(false);
|
||||||
const showAlert = ref<boolean>(false);
|
const showAlert = ref<boolean>(false);
|
||||||
|
|
||||||
@@ -29,7 +29,9 @@ const callWithdraw = async (amount: string) => {
|
|||||||
loadingWithdraw.value = true;
|
loadingWithdraw.value = true;
|
||||||
let withdraw;
|
let withdraw;
|
||||||
try {
|
try {
|
||||||
withdraw = await withdrawDeposit(amount, selectedToken.value);
|
withdraw = await withdrawDeposit(
|
||||||
|
amount,
|
||||||
|
network.value.tokens[selectedToken.value].address);
|
||||||
} catch {
|
} catch {
|
||||||
loadingWithdraw.value = false;
|
loadingWithdraw.value = false;
|
||||||
}
|
}
|
||||||
@@ -79,7 +81,7 @@ watch(walletAddress, async () => {
|
|||||||
await getWalletTransactions();
|
await getWalletTransactions();
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(networkName, async () => {
|
watch(network, async () => {
|
||||||
await getWalletTransactions();
|
await getWalletTransactions();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { ref } from "vue";
|
|||||||
|
|
||||||
import SellerComponent from "@/components/SellerSteps/SellerComponent.vue";
|
import SellerComponent from "@/components/SellerSteps/SellerComponent.vue";
|
||||||
import SendNetwork from "@/components/SellerSteps/SendNetwork.vue";
|
import SendNetwork from "@/components/SellerSteps/SendNetwork.vue";
|
||||||
import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue";
|
import LoadingComponent from "@/components/ui/LoadingComponent.vue";
|
||||||
import { useUser } from "@/composables/useUser";
|
import { useUser } from "@/composables/useUser";
|
||||||
import { approveTokens, addDeposit } from "@/blockchain/sellerMethods";
|
import { approveTokens, addDeposit } from "@/blockchain/sellerMethods";
|
||||||
import CustomAlert from "@/components/CustomAlert/CustomAlert.vue";
|
import CustomAlert from "@/components/ui/CustomAlert.vue";
|
||||||
import type { Participant } from "@/utils/bbPay";
|
import type { Participant } from "@/utils/bbPay";
|
||||||
|
|
||||||
enum Step {
|
enum Step {
|
||||||
@@ -69,7 +69,7 @@ const sendNetwork = async () => {
|
|||||||
/>
|
/>
|
||||||
<div v-if="flowStep == Step.Network">
|
<div v-if="flowStep == Step.Network">
|
||||||
<SendNetwork
|
<SendNetwork
|
||||||
:sellerId="user.sellerId.value"
|
:sellerId="Number(user.sellerId.value)"
|
||||||
:offer="Number(user.seller.value.offer)"
|
:offer="Number(user.seller.value.offer)"
|
||||||
:selected-token="user.selectedToken.value"
|
:selected-token="user.selectedToken.value"
|
||||||
v-if="!loading"
|
v-if="!loading"
|
||||||
|
|||||||
Reference in new issue
Block a user