3
.env.example
Normal file
@ -0,0 +1,3 @@
|
||||
VITE_API_URL=http://localhost:8000/
|
||||
VITE_GOERLI_API_URL={GOERLI_API_URL_ALCHEMY}
|
||||
VITE_MUMBAI_API_URL={MUMBAI_API_URL_ALCHEMY}
|
10
.github/workflows/cd.yml
vendored
@ -11,10 +11,15 @@ jobs:
|
||||
env:
|
||||
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID_STAGING }}
|
||||
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_STAGING }}
|
||||
ENV_VARIABLE: ${{ secrets.ENV_STAGING }}
|
||||
|
||||
steps:
|
||||
- name: 🏗 Setup repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 🏗 Config .env
|
||||
run: echo "$ENV_VARIABLE" > .env
|
||||
|
||||
- name: 🏗 Install Vercel CLI
|
||||
run: npm install --global vercel@latest
|
||||
|
||||
@ -33,10 +38,15 @@ jobs:
|
||||
env:
|
||||
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
|
||||
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||
ENV_VARIABLE: ${{ secrets.ENV_PROD }}
|
||||
|
||||
steps:
|
||||
- name: 🏗 Setup repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 🏗 Config .env
|
||||
run: echo "$ENV_VARIABLE" > .env
|
||||
|
||||
- name: 🏗 Install Vercel CLI
|
||||
run: npm install --global vercel@latest
|
||||
|
||||
|
49
.github/workflows/ci.yml
vendored
@ -1,6 +1,10 @@
|
||||
name: CI script
|
||||
|
||||
on: push
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main, develop ]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@ -13,29 +17,48 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
cache: 'npm'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: 🏗 Install dependencies
|
||||
run: npm ci
|
||||
run: yarn
|
||||
|
||||
- name: 📦 Lint with eslint
|
||||
run: npm run lint
|
||||
run: yarn lint
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID_STAGING }}
|
||||
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_STAGING }}
|
||||
steps:
|
||||
- name: 🏗 Setup repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 📦 Build docker image
|
||||
run: |
|
||||
docker build -t p2pix:$GITHUB_SHA .
|
||||
docker save -o image_$GITHUB_SHA p2pix:$GITHUB_SHA
|
||||
- name: 🏗 Install Vercel CLI
|
||||
run: npm install --global vercel@latest
|
||||
|
||||
- name: 📦 Put docker image in cache
|
||||
uses: actions/cache@v3
|
||||
- name: 🏗 Pull staging app from vercel environment
|
||||
run: vercel pull --yes --token=${{ secrets.VERCEL_AUTH_TOKEN }}
|
||||
|
||||
- name: 📦 Build staging app artifacts
|
||||
run: vercel build --token=${{ secrets.VERCEL_AUTH_TOKEN }}
|
||||
|
||||
test-coverage:
|
||||
name: SonarCloud
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
key: p2pix
|
||||
path: image_${{ github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
# test job
|
||||
- name: 🏗 Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: 📦 Test and coverage
|
||||
run: yarn coverage
|
||||
|
||||
- name: 📦 SonarCloud Scan
|
||||
uses: SonarSource/sonarcloud-github-action@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
2
.gitignore
vendored
@ -27,3 +27,5 @@ coverage
|
||||
*.sln
|
||||
*.sw?
|
||||
.vercel
|
||||
|
||||
.env
|
@ -2,9 +2,9 @@ FROM node:lts-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn
|
||||
COPY ./ ./
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "run", "start"]
|
||||
CMD ["yarn", "start"]
|
97
README.md
@ -1,12 +1,47 @@
|
||||
# P2Pix-Front-End
|
||||
<p align="center">
|
||||
<img src="./src/assets/colored_logo.svg" alt="Logo P2Pix" width="40%"/>
|
||||
</p>
|
||||
<br />
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
<center>
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=liftlearning_P2Pix-Front-End)
|
||||
[](https://sonarcloud.io/summary/new_code?id=liftlearning_P2Pix-Front-End)
|
||||
|
||||
</center>
|
||||
|
||||
This application aims to create a democratic and secure solution for the purchase and sale of ERC20 tokens, through the PIX, integrating the functionalities of smart contracts (smart contracts) of the blockchain with a receipt by digital signature. Allowing the integration of national financial system transactions to public blockchains, dispensing with custody through intermediaries.
|
||||
|
||||
# Table of Contents
|
||||
* [Metamask Tutorial](#metamask-tutorial)
|
||||
* [Recommended IDE Setup](#recommended-ide-setup)
|
||||
* [Dependencies](#dependencies)
|
||||
* [Build Setup](#build-setup)
|
||||
## Metamask Tutorial
|
||||
### Installation
|
||||
|
||||
Install the Metamask extension at https://metamask.io/download/
|
||||
|
||||
### Enable Testnets on Metamask
|
||||
|
||||
Go to Settings -> Advanced -> Show Testnets
|
||||
|
||||
Now you can select the Goerli testnet.
|
||||
|
||||
### Add Polygon Mumbai to your Metamask
|
||||
|
||||
To add the Mumbai network, follow the instructions at:
|
||||
https://www.youtube.com/watch?v=Jegmru0Q0j4
|
||||
|
||||
### Import the MBRL token
|
||||
|
||||
Go to Import Tokens and paste the following address: `0x294003F602c321627152c6b7DED3EAb5bEa853Ee`
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
### Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
@ -17,41 +52,53 @@ If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has a
|
||||
2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
|
||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
|
||||
|
||||
## Customize configuration
|
||||
### Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||
|
||||
## Project Setup (with yarn)
|
||||
|
||||
## Dependencies
|
||||
|
||||
### API
|
||||
For full operation of the application, it is necessary to correctly configure the variable that points to the api in the .env file, in the repository there is an .env.example file, just rename it to just .env and modify the variable `VITE_API_URL`. The api can be run locally see [https://github.com/liftlearning/Pix-Explorer-Back-End](https://github.com/liftlearning/Pix-Explorer-Back-End), or it can be pointed to just her staging address: [https://p2pix-block-explorer-api-staging.vercel.app/](https://p2pix-block-explorer-api-staging.vercel.app/)
|
||||
|
||||
### Alchemy Keys
|
||||
In the .env file, set `VITE_GOERLI_API_URL=https://eth-goerli.g.alchemy.com/v2/Zu9m4b2U_EzVU_zd-vgZDOleY8OF1DNP` and `VITE_MUMBAI_API_URL=https://polygon-mumbai.g.alchemy.com/v2/ZANeCqfj6VsXGpOH6gWAP6SIVIgD9Pwv`
|
||||
|
||||
You can also replace it with your own Alchemy Keys if you have one.
|
||||
|
||||
## Build Setup
|
||||
|
||||
The application can be tested by its trial version [https://p2pix-staging.vercel.app/](https://p2pix-staging.vercel.app/), the only requirement is to be running the smart contract of local way. To run the application locally, there are two different ways:
|
||||
|
||||
### Run with yarn
|
||||
```sh
|
||||
yarn
|
||||
```
|
||||
# Clone the repo
|
||||
git clone https://github.com/liftlearning/P2Pix-Front-End
|
||||
cd P2Pix-Front-End
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
# Install dependencies with yarn
|
||||
yarn install
|
||||
|
||||
```sh
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
# Type-Check, Compile and Minify for Production
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
# Compile and Hot-Reload for Development (port 3000)
|
||||
yarn start
|
||||
|
||||
```sh
|
||||
# Lint with [ESLint](https://eslint.org/)
|
||||
yarn lint
|
||||
```
|
||||
## Project Setup (with docker-compose)
|
||||
### Dependencies
|
||||
|
||||
1. Install [Docker](https://docs.docker.com/install/linux/docker-ce/ubuntu/);
|
||||
2. Install [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
### In the main project folder, build and start the application with the command:
|
||||
### Run with docker-compose
|
||||
|
||||
```sh
|
||||
# Clone the repo
|
||||
git clone https://github.com/liftlearning/P2Pix-Front-End
|
||||
cd P2Pix-Front-End
|
||||
|
||||
#1. Install [Docker](https://docs.docker.com/install/linux/docker-ce/ubuntu/);
|
||||
#2. Install [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
# Run docker-compose up command
|
||||
docker-compose up
|
||||
```
|
||||
|
18
babel.config.js
Normal file
@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
modules: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
env: {
|
||||
test: {
|
||||
presets: [
|
||||
["@babel/preset-env", { targets: { node: "current" } }],
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
7928
package-lock.json
generated
28
package.json
@ -1,39 +1,61 @@
|
||||
{
|
||||
"name": "p2pix-front-end",
|
||||
"version": "0.0.0",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"start": "vite --host=0.0.0.0 --port 3000",
|
||||
"build": "run-p type-check build-only",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"serve": "vue-cli-service serve",
|
||||
"coverage": "vitest run --coverage",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore --fix"
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
|
||||
"lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.3",
|
||||
"@heroicons/vue": "^2.0.12",
|
||||
"alchemy-sdk": "^2.3.0",
|
||||
"axios": "^1.2.1",
|
||||
"crc": "^3.8.0",
|
||||
"marked": "^4.2.12",
|
||||
"pinia": "^2.0.23",
|
||||
"qrcode": "^1.5.1",
|
||||
"vue": "^3.2.41",
|
||||
"vue-markdown": "^2.2.4",
|
||||
"vue-router": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@pinia/testing": "^0.0.14",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@types/crc": "^3.8.0",
|
||||
"@types/jest": "^27.0.0",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/node": "^16.11.68",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/vue-markdown": "^2.2.1",
|
||||
"@vitejs/plugin-vue": "^3.1.2",
|
||||
"@vitejs/plugin-vue-jsx": "^2.0.1",
|
||||
"@vitest/coverage-c8": "^0.28.2",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"@vue/test-utils": "^2.2.7",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"ethers": "^5.7.2",
|
||||
"jsdom": "^21.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.18",
|
||||
"prettier": "^2.7.1",
|
||||
"tailwindcss": "^3.2.1",
|
||||
"typescript": "~4.7.4",
|
||||
"vite": "^3.1.8",
|
||||
"vitest": "^0.28.1",
|
||||
"vue-tsc": "^1.0.8"
|
||||
}
|
||||
}
|
||||
|
8
sonar-project.properties
Normal file
@ -0,0 +1,8 @@
|
||||
sonar.organization=liftlearning
|
||||
sonar.projectKey=liftlearning_P2Pix-Front-End
|
||||
sonar.sources=src/
|
||||
|
||||
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
||||
|
||||
sonar.exclusions=dist/**
|
||||
sonar.coverage.exclusions=**/*.spec.*
|
95
src/App.vue
@ -1,91 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink, RouterView } from "vue-router";
|
||||
import HelloWorld from "./components/HelloWorld.vue";
|
||||
import TopBar from "./components/TopBar/TopBar.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<img
|
||||
alt="Vue logo"
|
||||
class="logo"
|
||||
src="@/assets/logo.svg"
|
||||
width="125"
|
||||
height="125"
|
||||
/>
|
||||
|
||||
<div class="wrapper">
|
||||
<HelloWorld msg="You did it!" />
|
||||
|
||||
<nav>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/about">About</RouterLink>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<RouterView />
|
||||
<TopBar />
|
||||
<RouterView v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense>
|
||||
<component :is="Component"></component>
|
||||
</Suspense>
|
||||
</template>
|
||||
</RouterView>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
line-height: 1.5;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: inline-block;
|
||||
padding: 0 1rem;
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
nav a:first-of-type {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
padding-right: calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0 2rem 0 0;
|
||||
}
|
||||
|
||||
header .wrapper {
|
||||
display: flex;
|
||||
place-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav {
|
||||
text-align: left;
|
||||
margin-left: -1rem;
|
||||
font-size: 1rem;
|
||||
|
||||
padding: 1rem 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
3
src/assets/account.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.5 0C4.67157 0 4 0.671573 4 1.5V3H5V1.5C5 1.22386 5.22386 1 5.5 1H12.5C12.7761 1 13 1.22386 13 1.5V8.5C13 8.77614 12.7761 9 12.5 9H5.5C5.22386 9 5 8.77614 5 8.5V7H4V8.5C4 9.32843 4.67157 10 5.5 10H12.5C13.3284 10 14 9.32843 14 8.5V1.5C14 0.671573 13.3284 0 12.5 0H5.5ZM9 12.5C9 12.7761 8.77614 13 8.5 13L1.5 13C1.22386 13 1 12.7761 1 12.5L1 5.5C1 5.22386 1.22386 5 1.5 5L8.5 5C8.77614 5 9 5.22386 9 5.5V8H10V5.5C10 4.67157 9.32843 4 8.5 4L1.5 4C0.671574 4 0 4.67157 0 5.5V12.5C0 13.3284 0.671573 14 1.5 14L8.5 14C9.32843 14 10 13.3284 10 12.5V12H9V12.5Z" fill="#F59E0B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 726 B |
3
src/assets/arrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 1.5C4 0.947715 4.44772 0.5 5 0.5C5.55229 0.5 6 0.947715 6 1.5V12.0858L8.29289 9.79289C8.68342 9.40237 9.31658 9.40237 9.70711 9.79289C10.0976 10.1834 10.0976 10.8166 9.70711 11.2071L5.70711 15.2071C5.31658 15.5976 4.68342 15.5976 4.29289 15.2071L0.292893 11.2071C-0.0976311 10.8166 -0.0976311 10.1834 0.292893 9.79289C0.683417 9.40237 1.31658 9.40237 1.70711 9.79289L4 12.0858V1.5Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 553 B |
@ -19,13 +19,11 @@
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
--color-background-indigo: var(--vt-c-indigo);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
34
src/assets/bg.svg
Normal file
After Width: | Height: | Size: 744 KiB |
18
src/assets/brz.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_257_714)">
|
||||
<path d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32Z" fill="#170636"/>
|
||||
<path d="M24.269 9.72809L20.1404 13.8568L24.269 17.9854L28.3977 13.8568L24.269 9.72809Z" fill="#14B23D"/>
|
||||
<path d="M7.76489 9.68823L3.63623 13.8169L7.76489 17.9456L11.8936 13.8169L7.76489 9.68823Z" fill="#FFC100"/>
|
||||
<path d="M16.0261 17.9628L11.8975 22.0915L16.0261 26.2202L20.1548 22.0915L16.0261 17.9628Z" fill="#14B23D"/>
|
||||
<path d="M7.76196 9.69141L16.0274 17.9568H24.2549L15.9895 9.69141H7.76196Z" fill="url(#paint0_linear_257_714)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_257_714" x1="9.81965" y1="7.64005" x2="13.4843" y2="22.2149" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFDC00"/>
|
||||
<stop offset="1" stop-color="#07E58A"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_257_714">
|
||||
<rect width="32" height="32" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
6
src/assets/cancel.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8Z" fill="#1F2937" stroke="#1F2937" stroke-width="2"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.6115 4.81333C11.7288 4.696 11.7288 4.50577 11.6115 4.38844C11.4942 4.27111 11.304 4.27111 11.1866 4.38844L8 7.57509L4.81337 4.38844C4.69604 4.27111 4.50581 4.27111 4.38849 4.38844C4.27116 4.50577 4.27116 4.696 4.38849 4.81333L7.57511 7.99998L4.38844 11.1867C4.27111 11.304 4.27111 11.4942 4.38844 11.6116C4.50577 11.7289 4.69599 11.7289 4.81332 11.6116L8 8.42487L11.1867 11.6116C11.304 11.7289 11.4942 11.7289 11.6116 11.6116C11.7289 11.4942 11.7289 11.304 11.6116 11.1867L8.42489 7.99998L11.6115 4.81333Z" fill="#F9FAFB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.17604 4.17599C4.4107 3.94134 4.79116 3.94133 5.02582 4.17599L8 7.1502L10.9742 4.176C11.2088 3.94133 11.5893 3.94133 11.824 4.176C12.0586 4.41066 12.0586 4.79111 11.824 5.02577L8.84977 7.99998L11.824 10.9742C12.0587 11.2089 12.0587 11.5893 11.824 11.824C11.5893 12.0587 11.2089 12.0587 10.9742 11.824L8 8.84975L5.02577 11.824C4.79111 12.0587 4.41065 12.0587 4.17599 11.824C3.94134 11.5893 3.94134 11.2089 4.17599 10.9742L7.15023 7.99998L4.17604 5.02577C3.94138 4.79111 3.94138 4.41065 4.17604 4.17599Z" fill="#F9FAFB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.99926 3.99922C4.33155 3.66693 4.8703 3.66693 5.20259 3.99922L8 6.79664L10.7974 3.99922C11.1297 3.66693 11.6684 3.66693 12.0007 3.99922C12.333 4.33151 12.333 4.87026 12.0007 5.20255L9.20333 7.99998L12.0008 10.7974C12.3331 11.1297 12.3331 11.6685 12.0008 12.0008C11.6685 12.3331 11.1297 12.3331 10.7975 12.0008L8 9.20331L5.20254 12.0008C4.87025 12.3331 4.33151 12.3331 3.99922 12.0008C3.66693 11.6685 3.66693 11.1297 3.99922 10.7974L6.79667 7.99998L3.99926 5.20255C3.66698 4.87026 3.66698 4.33151 3.99926 3.99922ZM4.6366 4.56522C4.6169 4.54552 4.58496 4.54552 4.56526 4.56522C4.54556 4.58491 4.54556 4.61685 4.56526 4.63655L7.92866 7.99998L4.56521 11.3634C4.54552 11.3831 4.54552 11.4151 4.56521 11.4348C4.58491 11.4545 4.61685 11.4545 4.63655 11.4348L8 8.07131L11.3635 11.4348C11.3832 11.4545 11.4151 11.4545 11.4348 11.4348C11.4545 11.4151 11.4545 11.3831 11.4348 11.3634L8.07133 7.99998L11.4347 4.63655C11.4544 4.61685 11.4544 4.58491 11.4347 4.56522C11.415 4.54552 11.3831 4.54552 11.3634 4.56522L8 7.92864L4.6366 4.56522Z" fill="#F9FAFB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
3
src/assets/chevronDown.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 484 B |
3
src/assets/chevronDownBlack.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 484 B |
3
src/assets/chevronUp.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3536 7.35355C13.1583 7.54882 12.8417 7.54882 12.6464 7.35355L7 1.70711L1.35355 7.35355C1.15829 7.54881 0.841709 7.54881 0.646446 7.35355C0.451184 7.15829 0.451184 6.84171 0.646446 6.64645L6.64645 0.646446C6.84171 0.451184 7.15829 0.451184 7.35355 0.646446L13.3536 6.64645C13.5488 6.84171 13.5488 7.15829 13.3536 7.35355Z" fill="#111827"/>
|
||||
</svg>
|
After Width: | Height: | Size: 493 B |
10
src/assets/colored_logo.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="259" height="64" viewBox="0 0 259 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M127.675 31.868C127.675 42.1048 121.155 49.3558 112.198 49.3558C106.958 49.3558 102.936 46.7966 100.499 43.0188V63.8579H93.6135V15.1114H100.499V20.8392C102.936 17.0004 106.958 14.3802 112.259 14.3802C121.033 14.3802 127.675 21.6313 127.675 31.868ZM120.729 31.868C120.729 25.4701 116.585 20.961 110.736 20.961C104.886 20.961 100.438 25.4701 100.438 31.868C100.438 38.3269 104.886 42.775 110.736 42.775C116.524 42.775 120.729 38.3269 120.729 31.868Z" fill="#14B8A6"/>
|
||||
<path d="M165.454 42.0438V48.6246H133.098V43.689L145.468 32.8429C155.034 24.4951 157.471 22.3624 157.471 18.5846C157.471 14.8677 154.607 11.821 149.367 11.821C144.188 11.821 141.324 14.8067 140.41 19.6814H133.464C134.439 10.7851 140.837 5.24023 149.672 5.24023C158.751 5.24023 164.418 11.0898 164.418 18.2799C164.418 24.7388 159.909 28.5776 151.195 36.1942L144.493 41.9829L144.371 42.0438H165.454Z" fill="#F59E0B"/>
|
||||
<path d="M206.34 31.868C206.34 42.1048 199.82 49.3558 190.863 49.3558C185.622 49.3558 181.601 46.7966 179.163 43.0188V63.8579H172.278V15.1114H179.163V20.8392C181.601 17.0004 185.622 14.3802 190.924 14.3802C199.698 14.3802 206.34 21.6313 206.34 31.868ZM199.393 31.868C199.393 25.4701 195.25 20.961 189.4 20.961C183.551 20.961 179.103 25.4701 179.103 31.868C179.103 38.3269 183.551 42.775 189.4 42.775C195.189 42.775 199.393 38.3269 199.393 31.868Z" fill="#14B8A6"/>
|
||||
<path d="M221.207 4.20438C221.207 6.70263 219.318 8.53062 216.698 8.53062C214.2 8.53062 212.25 6.70263 212.25 4.20438C212.25 1.76706 214.2 0 216.698 0C219.318 0 221.207 1.76706 221.207 4.20438ZM213.286 15.1114H220.171V48.6246H213.286V15.1114Z" fill="#14B8A6"/>
|
||||
<path d="M249.785 48.6246L241.681 36.6208L233.881 48.6246H225.594L237.598 31.3806L226.265 15.1115H234.491L241.924 26.3231L249.115 15.1115H257.401L246.068 31.5024L258.011 48.6246H249.785Z" fill="#14B8A6"/>
|
||||
<path d="M8.11425 8.12439C11.7804 8.12439 14.8778 10.5617 15.8832 13.9029C19.7423 14.1771 23.4389 15.8527 26.1707 18.6049L40.9165 33.3405C40.9673 33.3913 41.0282 33.4421 41.079 33.503L57.2466 49.6604C59.2168 51.6407 61.9384 52.768 64.721 52.768C64.7312 52.768 64.7413 52.768 64.7515 52.768H65.6452C66.8639 49.8331 69.7582 47.7715 73.14 47.7715C77.6185 47.7715 81.2542 51.4072 81.2542 55.8857C81.2542 60.3643 77.6185 64 73.14 64C69.5246 64 66.4779 61.6439 65.4218 58.3738H64.7617C64.7515 58.3738 64.7312 58.3738 64.721 58.3738C60.4659 58.3738 56.3021 56.6474 53.3063 53.6312L38.4995 38.8245C38.4386 38.7737 38.3675 38.7229 38.3066 38.662L22.1898 22.5452C20.4227 20.768 18.0362 19.6814 15.5379 19.4782C14.2888 22.3421 11.4351 24.3427 8.10409 24.3427C3.63567 24.3427 0 20.7172 0 16.2386C0 11.7601 3.63567 8.12439 8.11425 8.12439Z" fill="#F59E0B"/>
|
||||
<path d="M8.11425 47.7715C11.496 47.7715 14.3802 49.8331 15.609 52.768H16.5027C16.5129 52.768 16.523 52.768 16.5332 52.768C19.3259 52.768 22.0476 51.6408 24.0076 49.6605L33.6553 40.0127L37.6058 43.9632L27.948 53.6211C24.9419 56.6373 20.7883 58.3637 16.5332 58.3637C16.523 58.3637 16.5027 58.3637 16.4925 58.3637H15.8324C14.7864 61.6236 11.7296 63.9899 8.11425 63.9899C3.63567 63.9899 0 60.3542 0 55.8756C0 51.397 3.63567 47.7715 8.11425 47.7715Z" fill="#F59E0B"/>
|
||||
<path d="M73.1298 24.3427C69.748 24.3427 66.8638 22.2812 65.635 19.3462H64.7413C64.7312 19.3462 64.721 19.3462 64.7109 19.3462C61.9181 19.3462 59.1964 20.4735 57.2364 22.4538L47.5887 32.1016L43.6382 28.1511L53.2961 18.4932C56.3021 15.477 60.4557 13.7506 64.7109 13.7506C64.721 13.7506 64.7413 13.7506 64.7515 13.7506H65.4116C66.4678 10.4805 69.5246 8.1244 73.1298 8.1244C77.6084 8.1244 81.244 11.7601 81.244 16.2387C81.244 20.7172 77.6084 24.3427 73.1298 24.3427Z" fill="#F59E0B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
3
src/assets/copyPix.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 3H3C1.89543 3 1 3.89543 1 5V17C1 18.1046 1.89543 19 3 19H13C14.1046 19 15 18.1046 15 17V16M5 3C5 4.10457 5.89543 5 7 5H9C10.1046 5 11 4.10457 11 3M5 3C5 1.89543 5.89543 1 7 1H9C10.1046 1 11 1.89543 11 3M11 3H13C14.1046 3 15 3.89543 15 5V8M17 12H7M7 12L10 9M7 12L10 15" stroke="#111827" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 467 B |
4
src/assets/ethereum.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM8.16801 2.56435C8.09081 2.43177 7.89923 2.4319 7.82221 2.56458L4.82431 7.72899C4.76983 7.82284 4.80013 7.94301 4.89259 7.99981L7.89038 9.84139C7.95451 9.88079 8.03533 9.88085 8.09952 9.84154L11.1069 7.99986C11.1996 7.94308 11.23 7.82262 11.1752 7.72866L8.16801 2.56435ZM8.08754 10.7831C8.02182 10.8253 7.93759 10.8253 7.87181 10.7833L5.51555 9.27665C5.33379 9.16043 5.1222 9.37463 5.24065 9.55495L7.8123 13.4701C7.89136 13.5905 8.06789 13.5903 8.14678 13.4699L10.7098 9.55566C10.828 9.37517 10.6161 9.16127 10.4345 9.27775L8.08754 10.7831Z" fill="#3B82F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.82232 2.56456C7.89934 2.43187 8.09092 2.43174 8.16812 2.56432L11.1754 7.72864C11.2301 7.8226 11.1997 7.94305 11.107 7.99984L8.09963 9.84152C8.03544 9.88082 7.95463 9.88077 7.89049 9.84137L4.8927 7.99979C4.80024 7.94299 4.76994 7.82282 4.82442 7.72897L7.82232 2.56456ZM7.87193 10.7833C7.9377 10.8253 8.02193 10.8253 8.08765 10.7831L10.4346 9.27773C10.6162 9.16125 10.8281 9.37515 10.7099 9.55563L8.1469 13.4698C8.06801 13.5903 7.89147 13.5904 7.81241 13.4701L5.24076 9.55492C5.12232 9.3746 5.3339 9.1604 5.51566 9.27662L7.87193 10.7833Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
3
src/assets/invalidIcon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM4.18025 4.18034C4.42057 3.94001 4.81022 3.94001 5.05055 4.18034L8.00008 7.12987L10.9496 4.18034C11.1899 3.94001 11.5796 3.94001 11.8199 4.18034C12.0602 4.42067 12.0602 4.81031 11.8199 5.05064L8.87039 8.00018L11.8198 10.9495C12.0601 11.1899 12.0601 11.5795 11.8198 11.8198C11.5794 12.0602 11.1898 12.0602 10.9495 11.8198L8.00008 8.87048L5.05072 11.8198C4.81039 12.0602 4.42074 12.0602 4.18041 11.8198C3.94009 11.5795 3.94009 11.1899 4.18041 10.9495L7.12978 8.00018L4.18025 5.05064C3.93992 4.81031 3.93992 4.42067 4.18025 4.18034Z" fill="#EF4444"/>
|
||||
</svg>
|
After Width: | Height: | Size: 808 B |
@ -1 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||
<svg width="99" height="24" viewBox="0 0 99 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M49.4002 11.9505C49.4002 15.7893 46.9553 18.5084 43.5964 18.5084C41.6313 18.5084 40.1232 17.5487 39.2092 16.132V23.9467H36.6271V5.66678H39.2092V7.81467C40.1232 6.37512 41.6313 5.39258 43.6192 5.39258C46.9096 5.39258 49.4002 8.11172 49.4002 11.9505ZM46.7953 11.9505C46.7953 9.55126 45.2415 7.86037 43.048 7.86037C40.8544 7.86037 39.1863 9.55126 39.1863 11.9505C39.1863 14.3726 40.8544 16.0406 43.048 16.0406C45.2187 16.0406 46.7953 14.3726 46.7953 11.9505Z" fill="#F9FAFB"/>
|
||||
<path d="M63.5671 15.7664V18.2342H51.4338V16.3834L56.0724 12.3161C59.6598 9.18565 60.5738 8.38591 60.5738 6.96921C60.5738 5.57537 59.4999 4.43288 57.5348 4.43288C55.5925 4.43288 54.5186 5.55252 54.1758 7.38051H51.5709C51.9365 4.04443 54.3358 1.96509 57.649 1.96509C61.0536 1.96509 63.1787 4.15868 63.1787 6.85497C63.1787 9.27705 61.4878 10.7166 58.2203 13.5728L55.7068 15.7436L55.6611 15.7664H63.5671Z" fill="#F9FAFB"/>
|
||||
<path d="M78.8994 11.9505C78.8994 15.7893 76.4544 18.5084 73.0955 18.5084C71.1304 18.5084 69.6223 17.5487 68.7083 16.132V23.9467H66.1263V5.66678H68.7083V7.81467C69.6223 6.37512 71.1304 5.39258 73.1183 5.39258C76.4087 5.39258 78.8994 8.11172 78.8994 11.9505ZM76.2945 11.9505C76.2945 9.55126 74.7407 7.86037 72.5471 7.86037C70.3535 7.86037 68.6855 9.55126 68.6855 11.9505C68.6855 14.3726 70.3535 16.0406 72.5471 16.0406C74.7178 16.0406 76.2945 14.3726 76.2945 11.9505Z" fill="#F9FAFB"/>
|
||||
<path d="M84.4747 1.57664C84.4747 2.51349 83.7664 3.19899 82.7838 3.19899C81.847 3.19899 81.1158 2.51349 81.1158 1.57664C81.1158 0.662647 81.847 0 82.7838 0C83.7664 0 84.4747 0.662647 84.4747 1.57664ZM81.5042 5.66677H84.0863V18.2342H81.5042V5.66677Z" fill="#F9FAFB"/>
|
||||
<path d="M95.1913 18.2342L92.1523 13.7328L89.2275 18.2342H86.1199L90.6214 11.7677L86.3713 5.66675H89.456L92.2437 9.87113L94.94 5.66675H98.0476L93.7975 11.8134L98.2761 18.2342H95.1913Z" fill="#F9FAFB"/>
|
||||
<path d="M3.19484 2C4.63831 2 5.85787 2.95965 6.25372 4.27517C7.77317 4.38313 9.22864 5.04289 10.3042 6.1265L16.1101 11.9284C16.1301 11.9484 16.1541 11.9684 16.1741 11.9924L22.5398 18.354C23.3155 19.1338 24.3871 19.5776 25.4827 19.5776C25.4867 19.5776 25.4907 19.5776 25.4947 19.5776H25.8466C26.3264 18.422 27.466 17.6103 28.7975 17.6103C30.5609 17.6103 31.9923 19.0418 31.9923 20.8051C31.9923 22.5685 30.5609 24 28.7975 24C27.374 24 26.1745 23.0723 25.7586 21.7848H25.4987C25.4947 21.7848 25.4867 21.7848 25.4827 21.7848C23.8073 21.7848 22.1679 21.105 20.9884 19.9175L15.1585 14.0876C15.1345 14.0676 15.1065 14.0476 15.0825 14.0236L8.73682 7.67793C8.04107 6.97819 7.10141 6.55034 6.11777 6.47037C5.62595 7.59796 4.50236 8.38567 3.19084 8.38567C1.43148 8.38567 0 6.95819 0 5.19484C0 3.43148 1.43148 2 3.19484 2Z" fill="#F9FAFB"/>
|
||||
<path d="M3.19484 17.6103C4.52635 17.6103 5.66194 18.422 6.14576 19.5776H6.49763C6.50163 19.5776 6.50563 19.5776 6.50963 19.5776C7.60923 19.5776 8.68084 19.1338 9.45255 18.354L13.2512 14.5554L14.8066 16.1109L11.004 19.9135C9.82042 21.101 8.18502 21.7808 6.50963 21.7808C6.50563 21.7808 6.49763 21.7808 6.49363 21.7808H6.23373C5.82188 23.0643 4.61832 23.996 3.19484 23.996C1.43148 23.996 0 22.5645 0 20.8011C0 19.0378 1.43148 17.6103 3.19484 17.6103Z" fill="#F9FAFB"/>
|
||||
<path d="M28.7935 8.38567C27.462 8.38567 26.3264 7.57397 25.8426 6.41839H25.4907C25.4867 6.41839 25.4827 6.41839 25.4787 6.41839C24.3791 6.41839 23.3075 6.86223 22.5358 7.64194L18.7372 11.4406L17.1817 9.88513L20.9843 6.08251C22.1679 4.89495 23.8033 4.21519 25.4787 4.21519C25.4827 4.21519 25.4907 4.21519 25.4947 4.21519H25.7546C26.1705 2.92766 27.374 2 28.7935 2C30.5569 2 31.9883 3.43148 31.9883 5.19484C31.9883 6.95819 30.5569 8.38567 28.7935 8.38567Z" fill="#F9FAFB"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 308 B After Width: | Height: | Size: 3.6 KiB |
@ -4,10 +4,12 @@
|
||||
@tailwind utilities;
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
|
||||
padding: 2rem 4rem;
|
||||
height: fit-content;
|
||||
min-height: 100vh;
|
||||
background-image: url( './bg.svg' );
|
||||
background-size: cover;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@ -23,16 +25,3 @@ a,
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
}
|
||||
|
3
src/assets/minus.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="18" height="2" viewBox="0 0 18 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.84559e-07 0L18 1.43051e-06V2L0 2L1.84559e-07 0Z" fill="#F59E0B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 219 B |
4
src/assets/plus.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 18L8 0L10 8.74228e-08L10 18H8Z" fill="#F59E0B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.84559e-07 8L18 8V10L0 10L1.84559e-07 8Z" fill="#F59E0B"/>
|
||||
</svg>
|
After Width: | Height: | Size: 314 B |
3
src/assets/polygon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 24C18.6274 24 24 18.6274 24 12C24 5.37258 18.6274 0 12 0C5.37258 0 0 5.37258 0 12C0 18.6274 5.37258 24 12 24ZM14.7232 9.76501C15.0157 9.6188 15.3446 9.6188 15.6005 9.76501L18.5614 11.483C18.8538 11.6658 19 11.9582 19 12.2507V15.7232C19 16.0522 18.8172 16.3446 18.5614 16.4909L15.6005 18.2089C15.3081 18.3551 14.9791 18.3551 14.7232 18.2089L11.7624 16.4909C11.47 16.3081 11.3238 16.0157 11.3238 15.7232V14.517L12.7128 13.7128V14.8825C12.7128 15.2115 12.8956 15.5039 13.1514 15.6501L14.7232 16.564C15.0157 16.7102 15.3446 16.7102 15.6005 16.564L17.1358 15.6501C17.4282 15.4674 17.5744 15.1749 17.5744 14.8825V13.0548C17.5744 12.7258 17.3916 12.4334 17.1358 12.2872L15.564 11.3734C15.2715 11.2272 14.9426 11.2272 14.6867 11.3734L12.6762 12.5431L11.2872 13.3473L9.27676 14.517C8.98433 14.6632 8.65535 14.6632 8.39948 14.517L5.43864 12.799C5.14621 12.6162 5 12.3238 5 12.0313V8.5953C5 8.30287 5.14621 8.01044 5.43864 7.86423L8.43603 6.10966C8.72846 5.96345 9.05744 5.96345 9.31332 6.10966L12.2376 7.82768C12.53 8.01044 12.6762 8.30287 12.6762 8.5953V9.83812L11.2872 10.6423V9.43603C11.2872 9.10705 11.1044 8.81462 10.8486 8.66841L9.27676 7.71802C8.98433 7.5718 8.65535 7.5718 8.39948 7.71802L6.82768 8.63185C6.53525 8.81462 6.38903 9.10705 6.38903 9.39948V11.2272C6.38903 11.5561 6.5718 11.8486 6.82768 11.9948L8.39948 12.9452C8.69191 13.0914 9.02089 13.0914 9.27676 12.9452L11.2872 11.7389L12.6762 10.9713L14.7232 9.76501Z" fill="#6366F1"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
3
src/assets/redirect.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.97059 0.888889C9.97059 0.397969 10.3686 0 10.8595 0H15.1111C15.602 0 16 0.397969 16 0.888889V5.23654C16 5.72746 15.602 6.12543 15.1111 6.12543C14.6202 6.12543 14.2222 5.72746 14.2222 5.23654V3.06921L8.66058 8.75646C8.31735 9.10744 7.75457 9.11373 7.40358 8.77049C7.05259 8.42726 7.04631 7.86448 7.38954 7.51349L12.9986 1.77778H10.8595C10.3686 1.77778 9.97059 1.37981 9.97059 0.888889ZM2.74997 2.67823C2.11751 2.67823 1.77778 3.11618 1.77778 3.45456V13.4459C1.77778 13.7843 2.11751 14.2222 2.74997 14.2222H12.0554C12.6878 14.2222 13.0275 13.7843 13.0275 13.4459V9.98736C13.0275 9.49644 13.4255 9.09847 13.9164 9.09847C14.4073 9.09847 14.8053 9.49644 14.8053 9.98736V13.4459C14.8053 14.9469 13.4786 16 12.0554 16H2.74997C1.32673 16 0 14.9469 0 13.4459V3.45456C0 1.9536 1.32673 0.900455 2.74997 0.900455H5.89948C6.3904 0.900455 6.78837 1.29842 6.78837 1.78934C6.78837 2.28026 6.3904 2.67823 5.89948 2.67823H2.74997Z" fill="#111827"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
3
src/assets/validIcon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 0C6.41775 0 4.87104 0.469192 3.55544 1.34824C2.23985 2.22729 1.21447 3.47672 0.608967 4.93853C0.00346629 6.40034 -0.15496 8.00887 0.153721 9.56072C0.462403 11.1126 1.22433 12.538 2.34315 13.6569C3.46197 14.7757 4.88743 15.5376 6.43928 15.8463C7.99113 16.155 9.59966 15.9965 11.0615 15.391C12.5233 14.7855 13.7727 13.7602 14.6518 12.4446C15.5308 11.129 16 9.58225 16 8C15.9975 5.87903 15.1539 3.84563 13.6541 2.34587C12.1544 0.846118 10.121 0.00247015 8 0V0ZM12.618 5.46667L8.05467 11.6593C8.0008 11.7308 7.93322 11.7908 7.85589 11.8359C7.77856 11.881 7.69303 11.9102 7.60429 11.9219C7.51554 11.9336 7.42536 11.9274 7.339 11.9039C7.25265 11.8803 7.17186 11.8398 7.10134 11.7847L3.84267 9.17933C3.7743 9.12462 3.71737 9.05697 3.67514 8.98025C3.63291 8.90353 3.6062 8.81924 3.59654 8.73221C3.57704 8.55642 3.62816 8.38009 3.73867 8.242C3.84918 8.10391 4.01001 8.01538 4.1858 7.99587C4.36158 7.97637 4.53791 8.02749 4.676 8.138L7.39334 10.312L11.5447 4.678C11.5946 4.603 11.6593 4.53892 11.7348 4.48962C11.8102 4.44032 11.8948 4.40683 11.9836 4.39117C12.0724 4.37551 12.1634 4.37801 12.2511 4.39851C12.3389 4.41902 12.4216 4.4571 12.4942 4.51047C12.5668 4.56383 12.6279 4.63136 12.6737 4.70899C12.7194 4.78661 12.749 4.87271 12.7606 4.96209C12.7722 5.05146 12.7655 5.14226 12.741 5.22898C12.7165 5.31571 12.6746 5.39656 12.618 5.46667Z" fill="#10B981"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
15
src/assets/validating.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<svg width="128" height="83" viewBox="0 0 128 83" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_354_816)">
|
||||
<path d="M61.0484 25.8089C60.6304 25.3649 60.0296 25.1298 59.4288 25.1298C58.828 25.1298 58.2272 25.3649 57.8092 25.8089L53.2378 30.3804C52.7937 30.7983 52.5586 31.3991 52.5586 32C52.5586 32.6008 52.7937 33.2016 53.2378 33.6196L57.8092 38.191C58.6974 39.0791 60.1602 39.0791 61.0484 38.191C61.9366 37.3028 61.9366 35.84 61.0484 34.9518L58.0704 32L61.0484 29.0481C61.4925 28.6302 61.7276 28.0294 61.7276 27.4285C61.7276 26.8277 61.4925 26.2269 61.0484 25.8089Z" fill="#34D399"/>
|
||||
<path d="M79.334 25.8089C78.4459 24.9208 76.983 24.9208 76.0948 25.8089C75.2067 26.6971 75.2067 28.16 76.0948 29.0481L79.0728 32L76.0948 34.9518C75.2067 35.84 75.2067 37.3028 76.0948 38.191C76.983 39.0791 78.4459 39.0791 79.334 38.191L83.9055 33.6196C84.3495 33.2016 84.5846 32.6008 84.5846 32C84.5846 31.3991 84.3495 30.7983 83.9055 30.3804L79.334 25.8089Z" fill="#34D399"/>
|
||||
<path d="M68.5714 41.6914L73.1428 23.4057C73.4563 22.1518 72.6726 20.8718 71.4188 20.5584C70.1649 20.2449 68.8849 21.0286 68.5714 22.2825L64 40.5682C63.8171 41.169 63.8955 41.7959 64.209 42.3445C64.5224 42.8931 65.0449 43.2588 65.6457 43.4155C66.2465 43.5723 66.8996 43.4678 67.422 43.1282C67.9445 42.7886 68.3102 42.2661 68.4408 41.6653L68.5714 41.6914Z" fill="#34D399"/>
|
||||
<path d="M59.4288 48.0131H54.8574C53.6035 48.0131 52.5586 49.0318 52.5586 50.3118C52.5586 51.5918 53.5774 52.6106 54.8574 52.6106H59.4288C60.6827 52.6106 61.7276 51.5918 61.7276 50.3118C61.7276 49.0318 60.6827 48.0131 59.4288 48.0131Z" fill="#34D399"/>
|
||||
<path d="M82.2855 48.0131H68.5712C67.3174 48.0131 66.2725 49.0318 66.2725 50.3118C66.2725 51.5918 67.2912 52.6106 68.5712 52.6106H82.2855C83.5394 52.6106 84.5843 51.5918 84.5843 50.3118C84.5843 49.0318 83.5394 48.0131 82.2855 48.0131Z" fill="#34D399"/>
|
||||
<path d="M126.563 32.1567L115.122 27.5853C114.573 27.3763 113.972 27.3763 113.424 27.5853L101.982 32.1567C101.12 32.4963 100.545 33.3584 100.545 34.2727V38.8441H93.7012V13.7143C93.7012 13.1135 93.4661 12.5388 93.022 12.0947C92.578 11.6506 92.0033 11.4155 91.4024 11.4155H45.7143C44.4604 11.4155 43.4155 12.4343 43.4155 13.7143V38.8702H36.5714V13.7143C36.5714 13.1135 36.3363 12.5388 35.8922 12.0947C35.4482 11.6506 34.8735 11.4155 34.2727 11.4155H27.4286V6.8702C27.4286 5.9298 26.8539 5.09388 25.9918 4.75429L14.5502 0.156735C14.0016 -0.0522449 13.4008 -0.0522449 12.8522 0.156735L1.43673 4.72816C0.574694 5.06775 0 5.9298 0 6.8702V20.5845C0 21.5249 0.574694 22.3608 1.43673 22.7004L12.8784 27.2718C13.4269 27.4808 14.0278 27.4808 14.5763 27.2718L26.018 22.7004C26.88 22.3608 27.4547 21.4988 27.4547 20.5845V16.0131H32.0261V66.2988H27.4286V61.7273C27.4286 60.7869 26.8539 59.951 25.9918 59.6114L14.5502 55.04C14.0016 54.831 13.4008 54.831 12.8522 55.04L1.43673 59.5853C0.574694 59.9249 0 60.7869 0 61.7012V75.4155C0 76.3559 0.574694 77.1918 1.43673 77.5314L12.8784 82.1029C13.4269 82.3118 14.0278 82.3118 14.5763 82.1029L26.018 77.5314C26.88 77.1918 27.4547 76.3298 27.4547 75.4155V70.8441H34.2988C34.8996 70.8441 35.4743 70.609 35.9184 70.1649C36.3624 69.7208 36.5975 69.1461 36.5975 68.5453V43.4155H43.4416V64C43.4416 65.8286 44.1731 67.5527 45.4531 68.8588C46.7331 70.1649 48.4833 70.8702 50.3118 70.8702H91.4286C93.2571 70.8702 94.9812 70.1388 96.2873 68.8588C97.5935 67.5788 98.2988 65.8286 98.2988 64V59.4286C98.2988 58.8278 98.0637 58.2531 97.6196 57.809C97.1755 57.3649 96.6008 57.1298 96 57.1298H93.7012V43.4155H100.571V47.9869C100.571 48.9273 101.146 49.7633 102.008 50.1029L113.45 54.6743C113.998 54.8833 114.599 54.8833 115.148 54.6743L126.589 50.1029C127.451 49.7633 128.026 48.9012 128.026 47.9869V34.2727C128 33.3584 127.425 32.4963 126.563 32.1567ZM11.4155 21.76L4.57143 19.0171V10.24L11.4155 12.9829V21.76ZM13.7143 8.96L8.43755 6.84408L13.7143 4.72816L18.991 6.84408L13.7143 8.96ZM22.8571 19.0171L15.9869 21.76V12.9829L22.8571 10.24V19.0171ZM11.4155 76.6171L4.57143 73.8743V65.0971L11.4155 67.84V76.6171ZM13.7143 63.8171L8.43755 61.7012L13.7143 59.5853L18.991 61.7012L13.7143 63.8171ZM22.8571 73.8743L15.9869 76.6171V67.84L22.8571 65.0971V73.8743ZM93.7012 64C93.7012 64.6008 93.4661 65.1755 93.022 65.6196C92.578 66.0637 92.0033 66.2988 91.4024 66.2988H56.738C56.9992 65.5673 57.1559 64.7837 57.1559 64V61.7012H93.7273L93.7012 64ZM54.8571 57.1559C53.6033 57.1559 52.5584 58.1747 52.5584 59.4547V64C52.5584 65.2539 51.5396 66.2988 50.2596 66.2988C48.9796 66.2988 47.9608 65.28 47.9608 64V16.0131H89.1037V57.1559H54.8571ZM111.987 49.1886L105.143 46.4457V37.6686L111.987 40.4114V49.1886ZM114.286 36.3886L109.009 34.2727L114.286 32.1567L119.562 34.2727L114.286 36.3886ZM123.429 46.4457L116.584 49.1886V40.4114L123.429 37.6686V46.4457Z" fill="#34D399"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_354_816">
|
||||
<rect width="128" height="82.2857" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
8
src/assets/withdraw.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="path-1-inside-1_968_914" fill="white">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1C0 1.55228 0.447715 2 1 2H1.5V14C1.5 15.1046 2.39543 16 3.5 16H14.5C15.6046 16 16.5 15.1046 16.5 14V2H17C17.5523 2 18 1.55228 18 1C18 0.447715 17.5523 0 17 0H14.5H3.5H1Z"/>
|
||||
</mask>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 1C0 1.55228 0.447715 2 1 2H1.5V14C1.5 15.1046 2.39543 16 3.5 16H14.5C15.6046 16 16.5 15.1046 16.5 14V2H17C17.5523 2 18 1.55228 18 1C18 0.447715 17.5523 0 17 0H14.5H3.5H1Z" fill="#1F2937"/>
|
||||
<path d="M1.5 2H3.5V0H1.5V2ZM16.5 2V0H14.5V2H16.5ZM2 1C2 1.55228 1.55228 2 1 2V-2C-0.656854 -2 -2 -0.656854 -2 1H2ZM1 0C1.55228 0 2 0.447715 2 1H-2C-2 2.65685 -0.656854 4 1 4V0ZM1.5 0H1V4H1.5V0ZM3.5 14V2H-0.5V14H3.5ZM3.5 14H-0.5C-0.5 16.2091 1.29086 18 3.5 18V14ZM14.5 14H3.5V18H14.5V14ZM14.5 14V18C16.7091 18 18.5 16.2091 18.5 14H14.5ZM14.5 2V14H18.5V2H14.5ZM17 0H16.5V4H17V0ZM16 1C16 0.447715 16.4477 0 17 0V4C18.6569 4 20 2.65685 20 1H16ZM17 2C16.4477 2 16 1.55229 16 1H20C20 -0.656855 18.6569 -2 17 -2V2ZM14.5 2H17V-2H14.5V2ZM3.5 2H14.5V-2H3.5V2ZM1 2H3.5V-2H1V2Z" fill="#1F2937" mask="url(#path-1-inside-1_968_914)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99961 3.09998C9.49667 3.09998 9.89961 3.50292 9.89961 3.99998V9.91699L11.8828 8.05048C12.2447 7.70981 12.8143 7.72707 13.155 8.08903C13.4957 8.45099 13.4784 9.02057 13.1164 9.36124L9.61644 12.6554C9.2699 12.9815 8.72933 12.9815 8.38278 12.6554L4.88278 9.36124C4.52083 9.02057 4.50357 8.45099 4.84423 8.08903C5.1849 7.72707 5.75448 7.70981 6.11644 8.05048L8.09961 9.91699V3.99998C8.09961 3.50292 8.50256 3.09998 8.99961 3.09998Z" fill="#F9FAFB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
110
src/blockchain/__tests__/addresses.spec.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { expectTypeOf, it, expect } from "vitest";
|
||||
import {
|
||||
getTokenAddress,
|
||||
getP2PixAddress,
|
||||
getProviderUrl,
|
||||
isPossibleNetwork,
|
||||
possibleChains,
|
||||
network2Chain,
|
||||
} from "../addresses";
|
||||
|
||||
import { setActivePinia, createPinia } from "pinia";
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
|
||||
describe("addresses.ts types", () => {
|
||||
it("My addresses.ts types work properly", () => {
|
||||
expectTypeOf(getTokenAddress).toBeFunction();
|
||||
expectTypeOf(getP2PixAddress).toBeFunction();
|
||||
expectTypeOf(getProviderUrl).toBeFunction();
|
||||
expectTypeOf(isPossibleNetwork).toBeFunction();
|
||||
|
||||
expectTypeOf(possibleChains).toBeObject();
|
||||
expectTypeOf(network2Chain).toBeObject();
|
||||
});
|
||||
});
|
||||
|
||||
describe("addresses.ts functions", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("getTokenAddress Ethereum", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
||||
expect(getTokenAddress()).toBe(
|
||||
"0x294003F602c321627152c6b7DED3EAb5bEa853Ee"
|
||||
);
|
||||
});
|
||||
|
||||
it("getTokenAddress Polygon", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.polygon);
|
||||
expect(getTokenAddress()).toBe(
|
||||
"0x294003F602c321627152c6b7DED3EAb5bEa853Ee"
|
||||
);
|
||||
});
|
||||
|
||||
it("getTokenAddress Default", () => {
|
||||
expect(getTokenAddress()).toBe(
|
||||
"0x294003F602c321627152c6b7DED3EAb5bEa853Ee"
|
||||
);
|
||||
});
|
||||
|
||||
it("getP2PixAddress Ethereum", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
||||
expect(getP2PixAddress()).toBe(
|
||||
"0x5f3EFA9A90532914545CEf527C530658af87e196"
|
||||
);
|
||||
});
|
||||
|
||||
it("getP2PixAddress Polygon", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.polygon);
|
||||
expect(getP2PixAddress()).toBe(
|
||||
"0x5f3EFA9A90532914545CEf527C530658af87e196"
|
||||
);
|
||||
});
|
||||
|
||||
it("getP2PixAddress Default", () => {
|
||||
expect(getP2PixAddress()).toBe(
|
||||
"0x5f3EFA9A90532914545CEf527C530658af87e196"
|
||||
);
|
||||
});
|
||||
|
||||
it("getProviderUrl Ethereum", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
||||
expect(getProviderUrl()).toBe(import.meta.env.VITE_GOERLI_API_URL);
|
||||
});
|
||||
|
||||
it("getProviderUrl Polygon", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.polygon);
|
||||
expect(getProviderUrl()).toBe(import.meta.env.VITE_MUMBAI_API_URL);
|
||||
});
|
||||
|
||||
it("getProviderUrl Default", () => {
|
||||
expect(getProviderUrl()).toBe(import.meta.env.VITE_GOERLI_API_URL);
|
||||
});
|
||||
|
||||
it("isPossibleNetwork Returns", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
||||
expect(isPossibleNetwork("0x5")).toBe(true);
|
||||
expect(isPossibleNetwork("5")).toBe(true);
|
||||
expect(isPossibleNetwork("0x13881")).toBe(true);
|
||||
expect(isPossibleNetwork("80001")).toBe(true);
|
||||
|
||||
expect(isPossibleNetwork("")).toBe(false);
|
||||
expect(isPossibleNetwork(" ")).toBe(false);
|
||||
expect(isPossibleNetwork("0x55")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addresses.ts Unset Store", () => {
|
||||
it("getProviderUrl Unset", () => {
|
||||
expect(getProviderUrl()).toBe(undefined);
|
||||
});
|
||||
});
|
64
src/blockchain/addresses.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
|
||||
const getTokenAddress = (): string => {
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const possibleTokenAddresses: { [key: string]: string } = {
|
||||
Ethereum: "0x294003F602c321627152c6b7DED3EAb5bEa853Ee",
|
||||
Polygon: "0x294003F602c321627152c6b7DED3EAb5bEa853Ee",
|
||||
};
|
||||
|
||||
return possibleTokenAddresses[etherStore.networkName];
|
||||
};
|
||||
|
||||
const getP2PixAddress = (): string => {
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const possibleP2PixAddresses: { [key: string]: string } = {
|
||||
Ethereum: "0x5f3EFA9A90532914545CEf527C530658af87e196",
|
||||
Polygon: "0x5f3EFA9A90532914545CEf527C530658af87e196",
|
||||
};
|
||||
|
||||
return possibleP2PixAddresses[etherStore.networkName];
|
||||
};
|
||||
|
||||
const getProviderUrl = (): string => {
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const possibleProvidersUrls: { [key: string]: string } = {
|
||||
Ethereum: import.meta.env.VITE_GOERLI_API_URL,
|
||||
Polygon: import.meta.env.VITE_MUMBAI_API_URL,
|
||||
};
|
||||
|
||||
return possibleProvidersUrls[etherStore.networkName];
|
||||
};
|
||||
|
||||
const possibleChains: { [key: string]: NetworkEnum } = {
|
||||
"0x5": NetworkEnum.ethereum,
|
||||
"5": NetworkEnum.ethereum,
|
||||
"0x13881": NetworkEnum.polygon,
|
||||
"80001": NetworkEnum.polygon,
|
||||
};
|
||||
|
||||
const network2Chain: { [key: string]: string } = {
|
||||
Ethereum: "0x5",
|
||||
Polygon: "0x13881",
|
||||
Localhost: "0x7a69",
|
||||
};
|
||||
|
||||
const isPossibleNetwork = (networkChain: string): boolean => {
|
||||
if (Object.keys(possibleChains).includes(networkChain)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export {
|
||||
getTokenAddress,
|
||||
getProviderUrl,
|
||||
possibleChains,
|
||||
network2Chain,
|
||||
isPossibleNetwork,
|
||||
getP2PixAddress,
|
||||
};
|
94
src/blockchain/buyerMethods.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
|
||||
import { getContract, getProvider } from "./provider";
|
||||
import { getP2PixAddress } from "./addresses";
|
||||
|
||||
import p2pix from "../utils/smart_contract_files/P2PIX.json";
|
||||
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import { parseEther } from "ethers/lib/utils";
|
||||
|
||||
const addLock = async (
|
||||
depositId: BigNumber,
|
||||
amount: number
|
||||
): Promise<string> => {
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const p2pContract = getContract();
|
||||
|
||||
const lock = await p2pContract.lock(
|
||||
depositId, // BigNumber
|
||||
etherStore.walletAddress, // String "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" (Example)
|
||||
ethers.constants.AddressZero, // String "0x0000000000000000000000000000000000000000"
|
||||
0,
|
||||
parseEther(String(amount)), // BigNumber
|
||||
[],
|
||||
[]
|
||||
);
|
||||
|
||||
const lock_rec = await lock.wait();
|
||||
const [t] = lock_rec.events;
|
||||
|
||||
return t.args.lockID;
|
||||
};
|
||||
|
||||
const releaseLock = async (
|
||||
pixKey: string,
|
||||
amount: number,
|
||||
e2eId: string,
|
||||
lockId: string
|
||||
): Promise<any> => {
|
||||
const mockBacenSigner = new ethers.Wallet(
|
||||
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
|
||||
);
|
||||
|
||||
const messageToSign = ethers.utils.solidityKeccak256(
|
||||
["string", "uint256", "bytes32"],
|
||||
[
|
||||
pixKey,
|
||||
parseEther(String(amount)),
|
||||
ethers.utils.formatBytes32String(e2eId),
|
||||
]
|
||||
);
|
||||
|
||||
const messageHashBytes = ethers.utils.arrayify(messageToSign);
|
||||
const flatSig = await mockBacenSigner.signMessage(messageHashBytes);
|
||||
const provider = getProvider();
|
||||
|
||||
const sig = ethers.utils.splitSignature(flatSig);
|
||||
|
||||
const signer = provider.getSigner();
|
||||
const p2pContract = new ethers.Contract(getP2PixAddress(), p2pix.abi, signer);
|
||||
|
||||
const release = await p2pContract.release(
|
||||
lockId,
|
||||
ethers.constants.AddressZero,
|
||||
ethers.utils.formatBytes32String(e2eId),
|
||||
sig.r,
|
||||
sig.s,
|
||||
sig.v
|
||||
);
|
||||
await release.wait();
|
||||
|
||||
return release;
|
||||
};
|
||||
|
||||
const cancelDeposit = async (depositId: BigNumber): Promise<any> => {
|
||||
const contract = getContract();
|
||||
|
||||
const cancel = await contract.cancelDeposit(depositId);
|
||||
await cancel.wait();
|
||||
|
||||
return cancel;
|
||||
};
|
||||
|
||||
const withdrawDeposit = async (depositId: BigNumber): Promise<any> => {
|
||||
const contract = getContract();
|
||||
|
||||
const withdraw = await contract.withdraw(depositId, []);
|
||||
await withdraw.wait();
|
||||
|
||||
return withdraw;
|
||||
};
|
||||
|
||||
export { cancelDeposit, withdrawDeposit, addLock, releaseLock };
|
84
src/blockchain/events.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { Contract, ethers } from "ethers";
|
||||
|
||||
import p2pix from "../utils/smart_contract_files/P2PIX.json";
|
||||
import { formatEther } from "ethers/lib/utils";
|
||||
import { getContract } from "./provider";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
|
||||
const getNetworksLiquidity = async (): Promise<void> => {
|
||||
const etherStore = useEtherStore();
|
||||
console.log("Loading events");
|
||||
|
||||
const goerliProvider = new ethers.providers.JsonRpcProvider(
|
||||
import.meta.env.VITE_GOERLI_API_URL,
|
||||
5
|
||||
); // goerli provider
|
||||
const mumbaiProvider = new ethers.providers.JsonRpcProvider(
|
||||
import.meta.env.VITE_MUMBAI_API_URL,
|
||||
80001
|
||||
); // mumbai provider
|
||||
|
||||
const p2pContractGoerli = new ethers.Contract(
|
||||
"0x5f3EFA9A90532914545CEf527C530658af87e196",
|
||||
p2pix.abi,
|
||||
goerliProvider
|
||||
);
|
||||
const p2pContractMumbai = new ethers.Contract(
|
||||
"0x5f3EFA9A90532914545CEf527C530658af87e196",
|
||||
p2pix.abi,
|
||||
mumbaiProvider
|
||||
);
|
||||
|
||||
const depositListGoerli = await getValidDeposits(p2pContractGoerli);
|
||||
|
||||
const depositListMumbai = await getValidDeposits(p2pContractMumbai);
|
||||
|
||||
etherStore.setDepositsValidListGoerli(depositListGoerli);
|
||||
console.log(depositListGoerli);
|
||||
|
||||
etherStore.setDepositsValidListMumbai(depositListMumbai);
|
||||
console.log(depositListMumbai);
|
||||
};
|
||||
|
||||
const getValidDeposits = async (
|
||||
contract?: Contract
|
||||
): Promise<ValidDeposit[]> => {
|
||||
let p2pContract: Contract;
|
||||
|
||||
if (contract) {
|
||||
p2pContract = contract;
|
||||
} else {
|
||||
p2pContract = getContract(true);
|
||||
}
|
||||
|
||||
const filterDeposits = p2pContract.filters.DepositAdded(null);
|
||||
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
|
||||
|
||||
if (!contract) p2pContract = getContract(); // get metamask provider contract
|
||||
|
||||
const depositList = await Promise.all(
|
||||
eventsDeposits.map(async (deposit) => {
|
||||
const mappedDeposit = await p2pContract.mapDeposits(
|
||||
deposit.args?.depositID
|
||||
);
|
||||
let validDeposit: ValidDeposit | null = null;
|
||||
|
||||
if (mappedDeposit.valid) {
|
||||
validDeposit = {
|
||||
blockNumber: deposit.blockNumber,
|
||||
depositID: deposit.args?.depositID,
|
||||
remaining: Number(formatEther(mappedDeposit.remaining)),
|
||||
seller: mappedDeposit.seller,
|
||||
pixKey: mappedDeposit.pixTarget,
|
||||
};
|
||||
}
|
||||
|
||||
return validDeposit;
|
||||
})
|
||||
);
|
||||
|
||||
return depositList.filter((deposit) => deposit) as ValidDeposit[];
|
||||
};
|
||||
|
||||
export { getValidDeposits, getNetworksLiquidity };
|
95
src/blockchain/provider.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
|
||||
import p2pix from "../utils/smart_contract_files/P2PIX.json";
|
||||
|
||||
import { updateWalletStatus } from "./wallet";
|
||||
import {
|
||||
getProviderUrl,
|
||||
isPossibleNetwork,
|
||||
possibleChains,
|
||||
network2Chain,
|
||||
getP2PixAddress,
|
||||
} from "./addresses";
|
||||
|
||||
import { ethers } from "ethers";
|
||||
|
||||
const getProvider = (
|
||||
onlyAlchemyProvider: boolean = false
|
||||
): ethers.providers.Web3Provider | ethers.providers.JsonRpcProvider => {
|
||||
const window_ = window as any;
|
||||
const connection = window_.ethereum;
|
||||
|
||||
if (!connection || onlyAlchemyProvider)
|
||||
return new ethers.providers.JsonRpcProvider(getProviderUrl()); // alchemy provider
|
||||
|
||||
return new ethers.providers.Web3Provider(connection); // metamask provider
|
||||
};
|
||||
|
||||
const getContract = (onlyAlchemyProvider: boolean = false) => {
|
||||
const provider = getProvider(onlyAlchemyProvider);
|
||||
const signer = provider.getSigner();
|
||||
return new ethers.Contract(getP2PixAddress(), p2pix.abi, signer);
|
||||
};
|
||||
|
||||
const connectProvider = async (): Promise<void> => {
|
||||
const window_ = window as any;
|
||||
const connection = window_.ethereum;
|
||||
const provider = getProvider();
|
||||
|
||||
if (!(provider instanceof ethers.providers.Web3Provider)) {
|
||||
window.alert("Please, connect to metamask extension");
|
||||
return;
|
||||
}
|
||||
|
||||
await updateWalletStatus();
|
||||
|
||||
listenToNetworkChange(connection);
|
||||
listenToWalletChange(connection);
|
||||
};
|
||||
|
||||
const listenToWalletChange = (connection: any): void => {
|
||||
connection.on("accountsChanged", async () => {
|
||||
console.log("Changed account!");
|
||||
updateWalletStatus();
|
||||
});
|
||||
};
|
||||
|
||||
const listenToNetworkChange = (connection: any) => {
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
connection.on("chainChanged", (networkChain: string) => {
|
||||
console.log("Changed network!");
|
||||
|
||||
if (isPossibleNetwork(networkChain)) {
|
||||
etherStore.setNetworkName(possibleChains[networkChain]);
|
||||
updateWalletStatus();
|
||||
} else {
|
||||
window.alert("Invalid chain!");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const requestNetworkChange = async (network: string): Promise<boolean> => {
|
||||
const etherStore = useEtherStore();
|
||||
if (!etherStore.walletAddress) return true;
|
||||
|
||||
try {
|
||||
const window_ = window as any;
|
||||
await window_.ethereum.request({
|
||||
method: "wallet_switchEthereumChain",
|
||||
params: [{ chainId: network2Chain[network] }], // chainId must be in hexadecimal numbers
|
||||
});
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export {
|
||||
getProvider,
|
||||
getContract,
|
||||
connectProvider,
|
||||
listenToNetworkChange,
|
||||
requestNetworkChange,
|
||||
};
|
44
src/blockchain/sellerMethods.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { getContract, getProvider } from "./provider";
|
||||
import { getTokenAddress, getP2PixAddress } from "./addresses";
|
||||
import { parseEther } from "ethers/lib/utils";
|
||||
|
||||
import { ethers } from "ethers";
|
||||
|
||||
import mockToken from "../utils/smart_contract_files/MockToken.json";
|
||||
|
||||
const approveTokens = async (tokenQty: string): Promise<any> => {
|
||||
const provider = getProvider();
|
||||
const signer = provider.getSigner();
|
||||
|
||||
const tokenContract = new ethers.Contract(
|
||||
getTokenAddress(),
|
||||
mockToken.abi,
|
||||
signer
|
||||
);
|
||||
|
||||
const apprv = await tokenContract.approve(
|
||||
getP2PixAddress(),
|
||||
parseEther(tokenQty)
|
||||
);
|
||||
|
||||
await apprv.wait();
|
||||
console.log(apprv);
|
||||
return apprv;
|
||||
};
|
||||
|
||||
const addDeposit = async (tokenQty: string, pixKey: string): Promise<any> => {
|
||||
const p2pContract = getContract();
|
||||
|
||||
const deposit = await p2pContract.deposit(
|
||||
getTokenAddress(),
|
||||
parseEther(tokenQty),
|
||||
pixKey,
|
||||
ethers.utils.formatBytes32String("")
|
||||
);
|
||||
|
||||
await deposit.wait();
|
||||
|
||||
return deposit;
|
||||
};
|
||||
|
||||
export { approveTokens, addDeposit };
|
95
src/blockchain/wallet.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
|
||||
import { getContract, getProvider } from "./provider";
|
||||
import { getTokenAddress, possibleChains } from "./addresses";
|
||||
|
||||
import mockToken from "../utils/smart_contract_files/MockToken.json";
|
||||
|
||||
import { ethers, type Event } from "ethers";
|
||||
import { formatEther } from "ethers/lib/utils";
|
||||
import { getValidDeposits } from "./events";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
|
||||
const updateWalletStatus = async (): Promise<void> => {
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const provider = getProvider();
|
||||
const signer = provider.getSigner();
|
||||
|
||||
const { chainId } = await provider.getNetwork();
|
||||
etherStore.setNetworkName(possibleChains[chainId]);
|
||||
|
||||
const mockTokenContract = new ethers.Contract(
|
||||
getTokenAddress(),
|
||||
mockToken.abi,
|
||||
signer
|
||||
);
|
||||
|
||||
const walletAddress = await provider.send("eth_requestAccounts", []);
|
||||
const balance = await mockTokenContract.balanceOf(walletAddress[0]);
|
||||
|
||||
etherStore.setBalance(formatEther(balance));
|
||||
etherStore.setWalletAddress(ethers.utils.getAddress(walletAddress[0]));
|
||||
};
|
||||
|
||||
const listValidDepositTransactionsByWalletAddress = async (
|
||||
walletAddress: string
|
||||
): Promise<ValidDeposit[]> => {
|
||||
const walletDeposits = await getValidDeposits();
|
||||
|
||||
if (walletDeposits) {
|
||||
return walletDeposits
|
||||
.filter((deposit) => deposit.seller == walletAddress)
|
||||
.sort((a, b) => {
|
||||
return b.blockNumber - a.blockNumber;
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const listAllTransactionByWalletAddress = async (
|
||||
walletAddress: string
|
||||
): Promise<Event[]> => {
|
||||
const p2pContract = getContract();
|
||||
|
||||
const filterDeposits = p2pContract.filters.DepositAdded([walletAddress]);
|
||||
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
|
||||
|
||||
const filterAddedLocks = p2pContract.filters.LockAdded([walletAddress]);
|
||||
const eventsAddedLocks = await p2pContract.queryFilter(filterAddedLocks);
|
||||
|
||||
const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]);
|
||||
const eventsReleasedLocks = await p2pContract.queryFilter(
|
||||
filterReleasedLocks
|
||||
);
|
||||
|
||||
return [...eventsDeposits, ...eventsAddedLocks, ...eventsReleasedLocks].sort(
|
||||
(a, b) => {
|
||||
return b.blockNumber - a.blockNumber;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// get wallet's release transactions
|
||||
const listReleaseTransactionByWalletAddress = async (
|
||||
walletAddress: string
|
||||
): Promise<Event[]> => {
|
||||
const p2pContract = getContract(true);
|
||||
|
||||
const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]);
|
||||
const eventsReleasedLocks = await p2pContract.queryFilter(
|
||||
filterReleasedLocks
|
||||
);
|
||||
|
||||
return eventsReleasedLocks.sort((a, b) => {
|
||||
return b.blockNumber - a.blockNumber;
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
updateWalletStatus,
|
||||
listValidDepositTransactionsByWalletAddress,
|
||||
listAllTransactionByWalletAddress,
|
||||
listReleaseTransactionByWalletAddress,
|
||||
};
|
102
src/components/BuyConfirmedComponent/BuyConfirmedComponent.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
||||
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
||||
import type { Event } from "ethers";
|
||||
|
||||
// props
|
||||
const props = defineProps<{
|
||||
lastWalletReleaseTransactions: Event[];
|
||||
tokenAmount: number | undefined;
|
||||
}>();
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["makeAnotherTransaction"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="text-container">
|
||||
<span class="text font-extrabold text-5xl max-w-[50rem]"
|
||||
>Os tokens já foram transferidos <br />
|
||||
para a sua carteira!
|
||||
</span>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<div
|
||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
<div>
|
||||
<p>Tokens recebidos</p>
|
||||
<p class="text-2xl text-gray-900">{{ props.tokenAmount }} BRZ</p>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<p>
|
||||
<b>Não encontrou os tokens? </b>Clique no botão abaixo para <br />
|
||||
cadastrar o BRZ em sua carteira.
|
||||
</p>
|
||||
</div>
|
||||
<CustomButton
|
||||
:text="'Cadastrar token na carteira'"
|
||||
@buttonClicked="() => {}"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="border-amber-500 border-2 rounded default-button text-white p-2 px-50 min-w-[198px]"
|
||||
@click="emit('makeAnotherTransaction')"
|
||||
>
|
||||
Fazer nova transação
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-container mt-16">
|
||||
<span class="text font-extrabold text-3xl max-w-[50rem]"
|
||||
>Histórico de compras
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full max-w-4xl">
|
||||
<ListingComponent
|
||||
:walletTransactions="lastWalletReleaseTransactions"
|
||||
:isManageMode="false"
|
||||
>
|
||||
</ListingComponent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
@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;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply text-gray-800 text-center;
|
||||
}
|
||||
.blur-container-row {
|
||||
@apply flex flex-row justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-4 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10 w-auto;
|
||||
}
|
||||
|
||||
.last-release-info {
|
||||
@apply font-medium text-base text-gray-900;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,38 @@
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import BuyConfirmedComponent from "../BuyConfirmedComponent.vue";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { MockEvents } from "@/model/mock/EventMock";
|
||||
|
||||
describe("BuyConfirmedComponent.vue", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
const wrapper = shallowMount(BuyConfirmedComponent, {
|
||||
props: {
|
||||
lastWalletReleaseTransactions: MockEvents,
|
||||
tokenAmount: 1,
|
||||
},
|
||||
});
|
||||
|
||||
test("Test component Header Text", () => {
|
||||
expect(wrapper.html()).toContain("Os tokens já foram transferidos");
|
||||
expect(wrapper.html()).toContain("para a sua carteira!");
|
||||
});
|
||||
|
||||
test("Test component Container Text", () => {
|
||||
expect(wrapper.html()).toContain("Tokens recebidos");
|
||||
expect(wrapper.html()).toContain("BRZ");
|
||||
expect(wrapper.html()).toContain("Não encontrou os tokens?");
|
||||
expect(wrapper.html()).toContain("Clique no botão abaixo para");
|
||||
expect(wrapper.html()).toContain("cadastrar o BRZ em sua carteira.");
|
||||
});
|
||||
|
||||
test("Test makeAnotherTransactionEmit", async () => {
|
||||
wrapper.vm.$emit("makeAnotherTransaction");
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted("makeAnotherTransaction")).toBeTruthy();
|
||||
});
|
||||
});
|
26
src/components/CustomButton/CustomButton.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<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 py-4 bg-amber-400;
|
||||
}
|
||||
</style>
|
27
src/components/CustomButton/__tests__/CustomButton.spec.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import CustomButton from "../CustomButton.vue";
|
||||
|
||||
describe("CustomButton.vue", () => {
|
||||
test("Test button content", () => {
|
||||
const wrapper = mount(CustomButton, {
|
||||
props: {
|
||||
text: "Testing",
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toContain("Testing");
|
||||
});
|
||||
|
||||
test("Test if disabled props works", () => {
|
||||
const wrapper = mount(CustomButton, {
|
||||
props: {
|
||||
isDisabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
const button = wrapper.find(".button") as HTMLButtonElement;
|
||||
//@ts-ignore
|
||||
expect(button.element.disabled).toBe(true);
|
||||
});
|
||||
});
|
@ -1,41 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve successfully created a project with
|
||||
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
|
||||
What's next?
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 2.6rem;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
215
src/components/ListingComponent/ListingComponent.vue
Normal file
@ -0,0 +1,215 @@
|
||||
<script setup lang="ts">
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { formatEther } from "@ethersproject/units";
|
||||
import type { BigNumber, Event } from "ethers";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
// props
|
||||
const props = defineProps<{
|
||||
walletTransactions: (Event | ValidDeposit)[];
|
||||
isManageMode: boolean;
|
||||
}>();
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const itemsToShow = ref<(Event | ValidDeposit)[]>([]);
|
||||
|
||||
// Methods
|
||||
const isValidDeposit = (
|
||||
deposit: Event | ValidDeposit
|
||||
): deposit is ValidDeposit => {
|
||||
return (deposit as ValidDeposit).depositID !== undefined;
|
||||
};
|
||||
|
||||
const showInitialItems = (): void => {
|
||||
itemsToShow.value = props.walletTransactions.slice(0, 3);
|
||||
};
|
||||
|
||||
const openEtherscanUrl = (transactionHash: string): void => {
|
||||
const networkUrl =
|
||||
etherStore.networkName == NetworkEnum.ethereum
|
||||
? "goerli.etherscan.io"
|
||||
: "mumbai.polygonscan.com";
|
||||
const url = `https://${networkUrl}/tx/${transactionHash}`;
|
||||
window.open(url, "_blank");
|
||||
};
|
||||
|
||||
const loadMore = (): void => {
|
||||
const itemsShowing = itemsToShow.value.length;
|
||||
itemsToShow.value?.push(
|
||||
...props.walletTransactions.slice(itemsShowing, itemsShowing + 3)
|
||||
);
|
||||
};
|
||||
|
||||
const getEventName = (event: string | undefined): string => {
|
||||
if (!event) return "Desconhecido";
|
||||
|
||||
const possibleEventName: { [key: string]: string } = {
|
||||
DepositAdded: "Oferta",
|
||||
LockAdded: "Compra",
|
||||
LockReleased: "Reserva",
|
||||
};
|
||||
|
||||
return possibleEventName[event];
|
||||
};
|
||||
|
||||
const getAmountFormatted = (amount?: BigNumber): string => {
|
||||
if (!amount) return "";
|
||||
return formatEther(amount);
|
||||
};
|
||||
|
||||
// watch props changes
|
||||
watch(props, async (): Promise<void> => {
|
||||
const itemsToShowQty = itemsToShow.value.length;
|
||||
if (itemsToShowQty == 0) showInitialItems();
|
||||
else
|
||||
itemsToShow.value =
|
||||
props.walletTransactions.length > itemsToShowQty
|
||||
? props.walletTransactions.slice(0, itemsToShowQty)
|
||||
: props.walletTransactions;
|
||||
});
|
||||
|
||||
//emits
|
||||
const emit = defineEmits(["cancelDeposit", "withdrawDeposit"]);
|
||||
|
||||
// initial itemsToShow value
|
||||
showInitialItems();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="blur-container">
|
||||
<div
|
||||
class="grid grid-cols-4 grid-flow-row w-full px-6"
|
||||
v-if="itemsToShow.length != 0"
|
||||
>
|
||||
<span class="text-xs text-gray-50 font-medium justify-self-center"
|
||||
>Valor</span
|
||||
>
|
||||
<span class="text-xs text-gray-50 font-medium justify-self-center"
|
||||
>Data</span
|
||||
>
|
||||
<span class="text-xs text-gray-50 font-medium justify-self-center">{{
|
||||
props.isManageMode ? "Cancelar oferta" : "Tipo de transação"
|
||||
}}</span>
|
||||
<span class="text-xs text-gray-50 font-medium justify-self-center">{{
|
||||
props.isManageMode ? "Retirar tokens" : "Checar transação"
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
class="grid grid-cols-4 grid-flow-row w-full bg-white px-6 py-4 rounded-lg"
|
||||
v-for="(item, index) in itemsToShow"
|
||||
:key="item.blockNumber"
|
||||
>
|
||||
<span class="last-release-info">
|
||||
{{
|
||||
isValidDeposit(item)
|
||||
? item.remaining
|
||||
: getAmountFormatted(item.args?.amount)
|
||||
}}
|
||||
BRZ
|
||||
</span>
|
||||
|
||||
<span class="last-release-info transaction-date"> 20 out 2022 </span>
|
||||
|
||||
<span class="last-release-info" v-if="!props.isManageMode">
|
||||
{{ getEventName((item as Event).event) }}
|
||||
</span>
|
||||
|
||||
<div
|
||||
v-if="!props.isManageMode"
|
||||
class="flex gap-2 cursor-pointer items-center justify-self-center"
|
||||
@click="openEtherscanUrl((item as Event)?.transactionHash)"
|
||||
>
|
||||
<span class="last-release-info">Etherscan</span>
|
||||
<img alt="Redirect image" src="@/assets/redirect.svg" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="props.isManageMode"
|
||||
class="flex gap-2 cursor-pointer items-center justify-self-center"
|
||||
@click="emit('cancelDeposit', (item as ValidDeposit).depositID, index)"
|
||||
>
|
||||
<span class="last-release-info">Cancelar</span>
|
||||
<img alt="Cancel image" src="@/assets/cancel.svg" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="props.isManageMode"
|
||||
class="flex gap-2 cursor-pointer items-center justify-self-center"
|
||||
@click="
|
||||
emit('withdrawDeposit', (item as ValidDeposit).depositID, index)
|
||||
"
|
||||
>
|
||||
<span class="last-release-info">Retirar</span>
|
||||
<img alt="Withdraw image" src="@/assets/withdraw.svg" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col justify-center items-center w-full mt-2 gap-2"
|
||||
v-if="
|
||||
itemsToShow.length != 0 &&
|
||||
itemsToShow.length != props.walletTransactions.length
|
||||
"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="text-white font-semibold"
|
||||
@click="loadMore()"
|
||||
>
|
||||
Carregar mais
|
||||
</button>
|
||||
<span class="text-gray-300">
|
||||
({{ itemsToShow.length }} de {{ props.walletTransactions.length }}
|
||||
{{ isManageMode ? "ofertas" : "transações" }})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span class="font-bold text-gray-900" v-if="itemsToShow.length == 0">
|
||||
Não há nenhuma transação anterior
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
@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;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply text-gray-800 text-center;
|
||||
}
|
||||
.blur-container-row {
|
||||
@apply flex flex-row justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-4 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md w-auto;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
@apply grid grid-cols-4 grid-flow-row items-center px-8 py-6 gap-4 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10 w-auto;
|
||||
}
|
||||
|
||||
.last-release-info {
|
||||
@apply font-medium text-base text-gray-900 justify-self-center;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,100 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { expect } from "vitest";
|
||||
import { MockValidDeposits } from "@/model/mock/ValidDepositMock";
|
||||
import { MockEvents } from "@/model/mock/EventMock";
|
||||
|
||||
describe("ListingComponent.vue", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
test("Test Headers on List in Manage Mode", () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
walletTransactions: MockValidDeposits,
|
||||
isManageMode: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toContain("Valor");
|
||||
expect(wrapper.html()).toContain("Data");
|
||||
expect(wrapper.html()).toContain("Cancelar oferta");
|
||||
expect(wrapper.html()).toContain("Retirar tokens");
|
||||
});
|
||||
|
||||
test("Test Headers on List in Unmanage Mode", () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
walletTransactions: MockEvents,
|
||||
isManageMode: false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toContain("Valor");
|
||||
expect(wrapper.html()).toContain("Data");
|
||||
expect(wrapper.html()).toContain("Tipo de transação");
|
||||
expect(wrapper.html()).toContain("Checar transação");
|
||||
});
|
||||
|
||||
test("Test number of elements in the list first render", () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
walletTransactions: MockEvents,
|
||||
isManageMode: false,
|
||||
},
|
||||
});
|
||||
|
||||
const elements = wrapper.findAll(".transaction-date");
|
||||
|
||||
expect(elements).toHaveLength(3);
|
||||
});
|
||||
|
||||
test("Test load more button behavior", async () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
walletTransactions: MockValidDeposits,
|
||||
isManageMode: false,
|
||||
},
|
||||
});
|
||||
const btn = wrapper.find("button");
|
||||
|
||||
let elements = wrapper.findAll(".transaction-date");
|
||||
expect(elements).toHaveLength(3);
|
||||
|
||||
await btn.trigger("click");
|
||||
|
||||
elements = wrapper.findAll(".transaction-date");
|
||||
|
||||
expect(elements).toHaveLength(5);
|
||||
});
|
||||
|
||||
test("Test cancel offer button emit", async () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
walletTransactions: MockValidDeposits,
|
||||
isManageMode: true,
|
||||
},
|
||||
});
|
||||
wrapper.vm.$emit("cancelDeposit");
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted("cancelDeposit")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Test withdraw offer button emit", async () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
walletTransactions: MockValidDeposits,
|
||||
isManageMode: true,
|
||||
},
|
||||
});
|
||||
wrapper.vm.$emit("withdrawDeposit");
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted("withdrawDeposit")).toBeTruthy();
|
||||
});
|
||||
});
|
73
src/components/LoadingComponent.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
// prop
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
message: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="text-container">
|
||||
<span class="text font-bold text-3xl max-w-[29rem]">{{
|
||||
props.title ? props.title : "Confirme em sua carteira"
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="blur-container w-[26rem]">
|
||||
<div
|
||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col text-center justify-center w-full items-center p-2 px-3 rounded-3xl min-w-fit gap-1"
|
||||
>
|
||||
<img
|
||||
alt="Polygon image"
|
||||
src="@/assets/validating.svg"
|
||||
width="96"
|
||||
height="48"
|
||||
/>
|
||||
<span class="text-black font-medium text-sm px-12 mt-4">
|
||||
{{ $props.message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-divide {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
}
|
||||
.bottom-position {
|
||||
top: -20px;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
|
||||
.page {
|
||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
@apply flex flex-col items-center justify-center gap-4;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply text-gray-800 text-center;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
</style>
|
213
src/components/QrCodeComponent.vue
Normal file
@ -0,0 +1,213 @@
|
||||
<script setup lang="ts">
|
||||
import { pix } from "../utils/QrCodePix";
|
||||
import { ref } from "vue";
|
||||
import { debounce } from "@/utils/debounce";
|
||||
import CustomButton from "./CustomButton/CustomButton.vue";
|
||||
import api from "../services/index";
|
||||
|
||||
// props and store references
|
||||
const props = defineProps({
|
||||
pixTarget: String,
|
||||
tokenValue: Number,
|
||||
});
|
||||
|
||||
const qrCode = ref<string>("");
|
||||
const qrCodePayload = ref<string>("");
|
||||
const isPixValid = ref<boolean>(false);
|
||||
const isCodeInputEmpty = ref<boolean>(true);
|
||||
const e2eId = ref<string>("");
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["pixValidated"]);
|
||||
|
||||
const pixQrCode = pix({
|
||||
pixKey: props.pixTarget ?? "",
|
||||
value: props.tokenValue,
|
||||
});
|
||||
|
||||
pixQrCode.base64QrCode().then((code: string) => {
|
||||
qrCode.value = code;
|
||||
});
|
||||
|
||||
qrCodePayload.value = pixQrCode.payload();
|
||||
|
||||
const handleInputEvent = async (event: any): Promise<void> => {
|
||||
const { value } = event.target;
|
||||
e2eId.value = value;
|
||||
await validatePix();
|
||||
};
|
||||
|
||||
const validatePix = async (): Promise<void> => {
|
||||
if (e2eId.value == "") {
|
||||
isPixValid.value = false;
|
||||
isCodeInputEmpty.value = true;
|
||||
return;
|
||||
}
|
||||
const sellerPixKey = props.pixTarget;
|
||||
const transactionValue = props.tokenValue;
|
||||
|
||||
if (sellerPixKey && transactionValue) {
|
||||
const body_req = {
|
||||
e2e_id: e2eId.value,
|
||||
pix_key: sellerPixKey,
|
||||
pix_value: transactionValue,
|
||||
};
|
||||
|
||||
isCodeInputEmpty.value = false;
|
||||
|
||||
try {
|
||||
await api.post("validate_pix", body_req);
|
||||
isPixValid.value = true;
|
||||
} catch (error) {
|
||||
isPixValid.value = false;
|
||||
}
|
||||
} else {
|
||||
isCodeInputEmpty.value = false;
|
||||
isPixValid.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="text-container">
|
||||
<span class="text font-extrabold text-2xl max-w-[30rem]">
|
||||
Utilize o QR Code ou copie o código para realizar o Pix
|
||||
</span>
|
||||
<span class="text font-medium text-md max-w-[28rem]">
|
||||
Após realizar o Pix no banco de sua preferência, insira o código de
|
||||
autenticação para enviar a transação para a rede.
|
||||
</span>
|
||||
</div>
|
||||
<div class="blur-container max-w-[28rem] text-black">
|
||||
<div
|
||||
class="flex-col items-center justify-center flex w-full bg-white p-8 rounded-lg break-normal"
|
||||
>
|
||||
<img alt="Qr code image" :src="qrCode" class="w-48 h-48" />
|
||||
<span class="text-center font-bold">Código pix</span>
|
||||
<div class="break-words w-4/5">
|
||||
<span class="text-center text-xs">
|
||||
{{ qrCodePayload }}
|
||||
</span>
|
||||
</div>
|
||||
<img
|
||||
alt="Copy PIX code"
|
||||
src="@/assets/copyPix.svg"
|
||||
width="16"
|
||||
height="16"
|
||||
class="pt-2 mb-5 cursor-pointer"
|
||||
/>
|
||||
<span class="text-xs text-start">
|
||||
<strong>ATENÇÃO!</strong> A transação só será processada após inserir
|
||||
o código de autenticação. Caso contrário não conseguiremos comprovar o
|
||||
seu depósito e não será possível transferir os tokens para sua
|
||||
carteira. Confira aqui como encontrar o código no comprovante.
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex-col items-center justify-center flex w-full bg-white p-5 rounded-lg px-5"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Digite o código do comprovante PIX"
|
||||
@input="debounce(handleInputEvent, 500)($event)"
|
||||
class="text-md w-full box-border p-2 h-6 mb-2 outline-none"
|
||||
/>
|
||||
<div class="custom-divide" v-if="!isCodeInputEmpty"></div>
|
||||
<div
|
||||
class="flex flex-col w-full"
|
||||
v-if="!isPixValid && !isCodeInputEmpty"
|
||||
>
|
||||
<div class="flex items-center h-8">
|
||||
<img
|
||||
alt="Invalid Icon"
|
||||
src="@/assets/invalidIcon.svg"
|
||||
width="14"
|
||||
class="cursor-pointer align-middle inline-block"
|
||||
/>
|
||||
<span class="px-1 text-red-500 font-normal text-xs"
|
||||
>Código inválido. Por favor, confira e tente novamente.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col w-full" v-else-if="isPixValid == true">
|
||||
<div class="flex items-center h-8">
|
||||
<img
|
||||
alt="Valid Icon"
|
||||
src="@/assets/validIcon.svg"
|
||||
width="14"
|
||||
class="cursor-pointer align-middle inline-block"
|
||||
/>
|
||||
<span class="px-1 text-green-500 font-normal text-sm">
|
||||
Código válido.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CustomButton
|
||||
:is-disabled="isPixValid == false"
|
||||
:text="'Enviar para a rede'"
|
||||
@button-clicked="emit('pixValidated', e2eId)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
/* Most modern browsers support this now. */
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #080808;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #080808;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
@apply rounded-lg border border-gray-200 p-2 text-black;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
@apply font-semibold tracking-wide text-emerald-50;
|
||||
}
|
||||
|
||||
.custom-divide {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
}
|
||||
.bottom-position {
|
||||
top: -20px;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
|
||||
.text-container {
|
||||
@apply flex flex-col items-center justify-center gap-4;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply text-gray-800 text-center;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-6;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
</style>
|
233
src/components/SearchComponent.vue
Normal file
@ -0,0 +1,233 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
||||
import { debounce } from "@/utils/debounce";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { connectProvider } from "@/blockchain/provider";
|
||||
import { verifyNetworkLiquidity } from "@/utils/networkLiquidity";
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import { decimalCount } from "@/utils/decimalCount";
|
||||
|
||||
// Store reference
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const {
|
||||
walletAddress,
|
||||
networkName,
|
||||
depositsValidListGoerli,
|
||||
depositsValidListMumbai,
|
||||
} = storeToRefs(etherStore);
|
||||
|
||||
// Reactive state
|
||||
const tokenValue = ref<number>(0);
|
||||
const enableConfirmButton = ref<boolean>(false);
|
||||
const enableWalletButton = ref<boolean>(false);
|
||||
const hasLiquidity = ref<boolean>(true);
|
||||
const validDecimals = ref<boolean>(true);
|
||||
const selectedGoerliDeposit = ref<ValidDeposit>();
|
||||
const selectedMumbaiDeposit = ref<ValidDeposit>();
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["tokenBuy"]);
|
||||
|
||||
// Blockchain methods
|
||||
const connectAccount = async (): Promise<void> => {
|
||||
await connectProvider();
|
||||
|
||||
enableOrDisableConfirmButton();
|
||||
};
|
||||
|
||||
const emitConfirmButton = (): void => {
|
||||
const selectedDeposit =
|
||||
networkName.value == NetworkEnum.ethereum
|
||||
? selectedGoerliDeposit.value
|
||||
: selectedMumbaiDeposit.value;
|
||||
emit("tokenBuy", selectedDeposit, tokenValue.value);
|
||||
};
|
||||
|
||||
// Debounce methods
|
||||
const handleInputEvent = (event: any): void => {
|
||||
const { value } = event.target;
|
||||
|
||||
tokenValue.value = Number(value);
|
||||
|
||||
if (decimalCount(String(tokenValue.value)) > 2) {
|
||||
validDecimals.value = false;
|
||||
enableConfirmButton.value = false;
|
||||
return;
|
||||
}
|
||||
validDecimals.value = true;
|
||||
|
||||
verifyLiquidity();
|
||||
};
|
||||
|
||||
// Verify if there is a valid deposit to buy
|
||||
const verifyLiquidity = (): void => {
|
||||
enableConfirmButton.value = false;
|
||||
selectedGoerliDeposit.value = undefined;
|
||||
selectedMumbaiDeposit.value = undefined;
|
||||
|
||||
if (tokenValue.value <= 0) {
|
||||
enableWalletButton.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedGoerliDeposit.value = verifyNetworkLiquidity(
|
||||
tokenValue.value,
|
||||
walletAddress.value,
|
||||
depositsValidListGoerli.value
|
||||
);
|
||||
selectedMumbaiDeposit.value = verifyNetworkLiquidity(
|
||||
tokenValue.value,
|
||||
walletAddress.value,
|
||||
depositsValidListMumbai.value
|
||||
);
|
||||
|
||||
enableOrDisableConfirmButton();
|
||||
if (selectedGoerliDeposit.value || selectedMumbaiDeposit.value) {
|
||||
hasLiquidity.value = true;
|
||||
enableWalletButton.value = true;
|
||||
} else {
|
||||
hasLiquidity.value = false;
|
||||
enableWalletButton.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const enableOrDisableConfirmButton = (): void => {
|
||||
if (selectedGoerliDeposit.value && networkName.value == NetworkEnum.ethereum)
|
||||
enableConfirmButton.value = true;
|
||||
else if (
|
||||
selectedMumbaiDeposit.value &&
|
||||
networkName.value == NetworkEnum.polygon
|
||||
)
|
||||
enableConfirmButton.value = true;
|
||||
else enableConfirmButton.value = false;
|
||||
};
|
||||
|
||||
watch(networkName, (): void => {
|
||||
enableOrDisableConfirmButton();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="text-container">
|
||||
<span class="text font-extrabold text-5xl max-w-[29rem]"
|
||||
>Adquira cripto com apenas um Pix</span
|
||||
>
|
||||
<span class="text font-medium text-base max-w-[28rem]"
|
||||
>Digite um valor, confira a oferta, conecte sua carteira e receba os
|
||||
tokens após realizar o Pix</span
|
||||
>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<div
|
||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
<div class="flex justify-between w-full items-center">
|
||||
<input
|
||||
type="number"
|
||||
class="border-none outline-none text-lg text-gray-900 w-fit"
|
||||
v-bind:class="{
|
||||
'font-semibold': tokenValue != undefined,
|
||||
'text-xl': tokenValue != undefined,
|
||||
}"
|
||||
@input="debounce(handleInputEvent, 500)($event)"
|
||||
placeholder="0 "
|
||||
step=".01"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
||||
>
|
||||
<img alt="Token image" class="w-fit" src="@/assets/brz.svg" />
|
||||
<span class="text-gray-900 text-lg w-fit" id="brz">BRZ</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="custom-divide py-2"></div>
|
||||
<div class="flex justify-between pt-2" v-if="hasLiquidity">
|
||||
<p class="text-gray-500 font-normal text-sm w-auto">
|
||||
~ R$ {{ tokenValue.toFixed(2) }}
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<img
|
||||
alt="Polygon image"
|
||||
src="@/assets/polygon.svg"
|
||||
width="24"
|
||||
height="24"
|
||||
v-if="selectedMumbaiDeposit"
|
||||
/>
|
||||
<img
|
||||
alt="Ethereum image"
|
||||
src="@/assets/ethereum.svg"
|
||||
width="24"
|
||||
height="24"
|
||||
v-if="selectedGoerliDeposit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex pt-2 justify-center" v-if="!validDecimals">
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Por favor utilize no máximo 2 casas decimais</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex pt-2 justify-center" v-else-if="!hasLiquidity">
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Atualmente não há liquidez nas redes para sua demanda</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<CustomButton
|
||||
v-if="!walletAddress"
|
||||
:text="'Conectar carteira'"
|
||||
:is-disabled="!enableWalletButton"
|
||||
@buttonClicked="connectAccount()"
|
||||
/>
|
||||
<CustomButton
|
||||
v-if="walletAddress"
|
||||
:text="'Confirmar compra'"
|
||||
:is-disabled="!enableConfirmButton"
|
||||
@buttonClicked="emitConfirmButton()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-divide {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
}
|
||||
.bottom-position {
|
||||
top: -20px;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
|
||||
.page {
|
||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
@apply flex flex-col items-center justify-center gap-4;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply text-gray-800 text-center;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
</style>
|
140
src/components/SellerSteps/SellerSearchComponent.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import CustomButton from "../CustomButton/CustomButton.vue";
|
||||
import { debounce } from "@/utils/debounce";
|
||||
import { decimalCount } from "@/utils/decimalCount";
|
||||
|
||||
// Reactive state
|
||||
const tokenValue = ref<number>(0);
|
||||
const enableSelectButton = ref<boolean>(false);
|
||||
const hasLiquidity = ref<boolean>(true);
|
||||
const validDecimals = ref<boolean>(true);
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["tokenBuy"]);
|
||||
|
||||
// Debounce methods
|
||||
const handleInputEvent = (event: any): void => {
|
||||
const { value } = event.target;
|
||||
|
||||
tokenValue.value = Number(value);
|
||||
|
||||
if (decimalCount(String(tokenValue.value)) > 2) {
|
||||
validDecimals.value = false;
|
||||
enableSelectButton.value = false;
|
||||
return;
|
||||
}
|
||||
validDecimals.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="text-container">
|
||||
<span class="text font-extrabold text-5xl max-w-[29rem]"
|
||||
>Adquira cripto com apenas um Pix</span
|
||||
>
|
||||
<span class="text font-medium text-base max-w-[28rem]"
|
||||
>Digite um valor, confira a oferta, conecte sua carteira e receba os
|
||||
tokens após realizar o Pix</span
|
||||
>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<div
|
||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
<div class="flex justify-between w-full items-center">
|
||||
<input
|
||||
type="number"
|
||||
class="border-none outline-none text-lg text-gray-900 w-fit"
|
||||
v-bind:class="{
|
||||
'font-semibold': tokenValue != undefined,
|
||||
'text-xl': tokenValue != undefined,
|
||||
}"
|
||||
@input="debounce(handleInputEvent, 500)($event)"
|
||||
placeholder="0 "
|
||||
step=".01"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
||||
>
|
||||
<img alt="Token image" class="w-fit" src="@/assets/brz.svg" />
|
||||
<span class="text-gray-900 text-lg w-fit" id="brz">BRZ</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="custom-divide py-2"></div>
|
||||
<div class="flex justify-between pt-2" v-if="hasLiquidity">
|
||||
<p class="text-gray-500 font-normal text-sm w-auto">
|
||||
~ R$ {{ tokenValue.toFixed(2) }}
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<img
|
||||
alt="Polygon image"
|
||||
src="@/assets/polygon.svg"
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
<img
|
||||
alt="Ethereum image"
|
||||
src="@/assets/ethereum.svg"
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex pt-2 justify-center" v-if="!validDecimals">
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Por favor utilize no máximo 2 casas decimais</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex pt-2 justify-center" v-else-if="!hasLiquidity">
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Atualmente não há liquidez nas redes para sua demanda</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<CustomButton
|
||||
:text="'Conectar carteira'"
|
||||
@buttonClicked="emit('tokenBuy')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-divide {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
}
|
||||
.bottom-position {
|
||||
top: -20px;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
|
||||
.page {
|
||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
@apply flex flex-col items-center justify-center gap-4;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply text-gray-800 text-center;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
</style>
|
92
src/components/SellerSteps/SendNetwork.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<script setup lang="ts">
|
||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["sendNetwork"]);
|
||||
|
||||
// props and store references
|
||||
const props = defineProps({
|
||||
pixKey: String,
|
||||
offer: Number,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="text-container">
|
||||
<span class="text font-extrabold text-5xl max-w-[50rem]"
|
||||
>Envie sua oferta para a rede
|
||||
</span>
|
||||
<span class="text text-xl font-medium text-base max-w-[30rem]"
|
||||
>Após a confirmação sua oferta estará disponível para outros usuários.
|
||||
Caso deseje retirar a oferta, será necessário aguardar 24h para receber
|
||||
os tokens de volta.</span
|
||||
>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<div
|
||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
<div>
|
||||
<p>Tokens ofertados</p>
|
||||
<p class="text-2xl text-gray-900">{{ props.offer }} BRZ</p>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<p>Chave Pix</p>
|
||||
<p class="text-xl text-gray-900 break-words">
|
||||
{{ props.pixKey }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<p>
|
||||
<b>Atenção! </b> Os tokens ofertados ficam registrados no smart
|
||||
contract e serão transferidos automaticamente para o comprador assim
|
||||
que o Pix for detectado e confirmado.
|
||||
</p>
|
||||
</div>
|
||||
<CustomButton
|
||||
:text="'Enviar para a rede'"
|
||||
@buttonClicked="emit('sendNetwork')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
@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;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply text-gray-800 text-center;
|
||||
}
|
||||
.blur-container-row {
|
||||
@apply flex flex-row justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
|
||||
}
|
||||
|
||||
.last-deposit-info {
|
||||
@apply font-medium text-base;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
</style>
|
149
src/components/SellerSteps/WantSellComponent.vue
Normal file
@ -0,0 +1,149 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import CustomButton from "../CustomButton/CustomButton.vue";
|
||||
import { debounce } from "@/utils/debounce";
|
||||
import { decimalCount } from "@/utils/decimalCount";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { connectProvider } from "@/blockchain/provider";
|
||||
|
||||
// Reactive state
|
||||
const etherStore = useEtherStore();
|
||||
const { walletAddress } = storeToRefs(etherStore);
|
||||
|
||||
const offer = ref<string>("");
|
||||
const pixKey = ref<string>("");
|
||||
|
||||
const enableSelectButton = ref<boolean>(false);
|
||||
const hasLiquidity = ref<boolean>(true);
|
||||
const validDecimals = ref<boolean>(true);
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["approveTokens"]);
|
||||
|
||||
// Debounce methods
|
||||
const handleInputEvent = (event: any): void => {
|
||||
const { value } = event.target;
|
||||
|
||||
offer.value = value;
|
||||
|
||||
if (decimalCount(offer.value) > 2) {
|
||||
validDecimals.value = false;
|
||||
enableSelectButton.value = false;
|
||||
return;
|
||||
}
|
||||
validDecimals.value = true;
|
||||
};
|
||||
|
||||
const handleButtonClick = async (
|
||||
offer: string,
|
||||
pixKey: string
|
||||
): Promise<void> => {
|
||||
if (walletAddress.value) emit("approveTokens", { offer, pixKey });
|
||||
else await connectProvider();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="text-container">
|
||||
<span class="text font-extrabold text-5xl max-w-[29rem]"
|
||||
>Venda cripto e receba em Pix</span
|
||||
>
|
||||
<span class="text font-medium text-base max-w-[28rem]"
|
||||
>Digite sua oferta, informe a chave Pix, selecione a rede, aprove o
|
||||
envio da transação e confirme sua oferta.</span
|
||||
>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<div
|
||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
<div class="flex justify-between w-full items-center">
|
||||
<input
|
||||
type="number"
|
||||
v-model="offer"
|
||||
class="border-none outline-none text-lg text-gray-900 w-fit"
|
||||
v-bind:class="{
|
||||
'font-semibold': offer != undefined,
|
||||
'text-xl': offer != undefined,
|
||||
}"
|
||||
@input="debounce(handleInputEvent, 500)($event)"
|
||||
placeholder="Digite sua oferta"
|
||||
step=".01"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
||||
>
|
||||
<img alt="Token image" class="w-fit" src="@/assets/brz.svg" />
|
||||
<span class="text-gray-900 text-lg w-fit" id="brz">BRZ</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex pt-2 justify-center" v-if="!validDecimals">
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Por favor utilize no máximo 2 casas decimais</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex pt-2 justify-center" v-else-if="!hasLiquidity">
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Atualmente não há liquidez nas redes para sua demanda</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col w-full bg-white px-10 py-8 rounded-lg border-y-10"
|
||||
>
|
||||
<div class="flex justify-between w-full items-center">
|
||||
<input
|
||||
type="text"
|
||||
v-model="pixKey"
|
||||
class="border-none outline-none text-lg text-gray-900 w-fit"
|
||||
placeholder="Digite a chave Pix"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<CustomButton
|
||||
:text="walletAddress ? 'Aprovar tokens' : 'Conectar Carteira'"
|
||||
@buttonClicked="handleButtonClick(offer, pixKey)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-divide {
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #d1d5db;
|
||||
}
|
||||
.bottom-position {
|
||||
top: -20px;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
}
|
||||
|
||||
.page {
|
||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
@apply flex flex-col items-center justify-center gap-4;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply text-gray-800 text-center;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
</style>
|
@ -1,121 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import WelcomeItem from "./WelcomeItem.vue";
|
||||
import DocumentationIcon from "./icons/IconDocumentation.vue";
|
||||
import ToolingIcon from "./icons/IconTooling.vue";
|
||||
import EcosystemIcon from "./icons/IconEcosystem.vue";
|
||||
import CommunityIcon from "./icons/IconCommunity.vue";
|
||||
import SupportIcon from "./icons/IconSupport.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener"
|
||||
>official documentation</a
|
||||
>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
|
||||
This project is served and bundled with
|
||||
<a
|
||||
href="https://vitejs.dev/guide/features.html"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Vite</a
|
||||
>. The recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener"
|
||||
>VSCode</a
|
||||
>
|
||||
+
|
||||
<a
|
||||
href="https://github.com/johnsoncodehk/volar"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Volar</a
|
||||
>. If you need to test your components and web pages, check out
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
||||
and
|
||||
<a href="https://on.cypress.io/component" target="_blank"
|
||||
>Cypress Component Testing</a
|
||||
>.
|
||||
|
||||
<br />
|
||||
|
||||
More instructions are available in <code>README.md</code>.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener"
|
||||
>Vue Router</a
|
||||
>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener"
|
||||
>Vue Test Utils</a
|
||||
>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener"
|
||||
>Vue Dev Tools</a
|
||||
>. If you need more resources, we suggest paying
|
||||
<a
|
||||
href="https://github.com/vuejs/awesome-vue"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>Awesome Vue</a
|
||||
>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a
|
||||
>, our official Discord server, or
|
||||
<a
|
||||
href="https://stackoverflow.com/questions/tagged/vue.js"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also subscribe to
|
||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener"
|
||||
>our mailing list</a
|
||||
>
|
||||
and follow the official
|
||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
|
||||
>@vuejs</a
|
||||
>
|
||||
twitter account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
As an independent project, Vue relies on community backing for its
|
||||
sustainability. You can help us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener"
|
||||
>becoming a sponsor</a
|
||||
>.
|
||||
</WelcomeItem>
|
||||
</template>
|
302
src/components/TopBar/TopBar.vue
Normal file
@ -0,0 +1,302 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { ref } from "vue";
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import { connectProvider, requestNetworkChange } from "@/blockchain/provider";
|
||||
import ethereumImage from "@/assets/ethereum.svg";
|
||||
import polygonImage from "@/assets/polygon.svg";
|
||||
|
||||
// Store reference
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const { walletAddress, sellerView } = storeToRefs(etherStore);
|
||||
|
||||
const menuOpenToggle = ref<boolean>(false);
|
||||
const menuHoverToggle = ref<boolean>(false);
|
||||
|
||||
const currencyMenuOpenToggle = ref<boolean>(false);
|
||||
const currencyMenuHoverToggle = ref<boolean>(false);
|
||||
|
||||
//Methods
|
||||
const connectMetaMask = async (): Promise<void> => {
|
||||
await connectProvider();
|
||||
};
|
||||
|
||||
const formatWalletAddress = (): string => {
|
||||
const walletAddressLength = walletAddress.value.length;
|
||||
const initialText = walletAddress.value.substring(0, 5);
|
||||
const finalText = walletAddress.value.substring(
|
||||
walletAddressLength - 4,
|
||||
walletAddressLength
|
||||
);
|
||||
return `${initialText}...${finalText}`;
|
||||
};
|
||||
|
||||
const disconnectUser = (): void => {
|
||||
etherStore.setWalletAddress("");
|
||||
closeMenu();
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const closeMenu = (): void => {
|
||||
menuOpenToggle.value = false;
|
||||
};
|
||||
|
||||
const networkChange = async (network: NetworkEnum): Promise<void> => {
|
||||
currencyMenuOpenToggle.value = false;
|
||||
const change = await requestNetworkChange(network);
|
||||
if (change) etherStore.setNetworkName(network);
|
||||
};
|
||||
|
||||
const getNetworkImage = (networkName: NetworkEnum): string => {
|
||||
let validImages = {
|
||||
Ethereum: ethereumImage,
|
||||
Polygon: polygonImage,
|
||||
Localhost: ethereumImage,
|
||||
};
|
||||
|
||||
return validImages[networkName];
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<RouterLink :to="'/'" class="default-button">
|
||||
<img
|
||||
alt="P2Pix logo"
|
||||
class="logo"
|
||||
src="@/assets/logo.svg"
|
||||
width="75"
|
||||
height="75"
|
||||
/>
|
||||
</RouterLink>
|
||||
|
||||
<div class="flex gap-4 items-center">
|
||||
<RouterLink :to="'/faq'" class="default-button"> FAQ </RouterLink>
|
||||
<RouterLink :to="sellerView ? '/' : '/seller'" class="default-button">
|
||||
{{ sellerView ? "Quero comprar" : "Quero vender" }}
|
||||
</RouterLink>
|
||||
<div class="flex flex-col" v-if="walletAddress">
|
||||
<div
|
||||
class="group top-bar-info cursor-pointer hover:bg-white"
|
||||
@click="
|
||||
[
|
||||
(currencyMenuOpenToggle = !currencyMenuOpenToggle),
|
||||
(menuOpenToggle = false),
|
||||
]
|
||||
"
|
||||
@mouseover="currencyMenuHoverToggle = true"
|
||||
@mouseout="currencyMenuHoverToggle = false"
|
||||
:style="{
|
||||
backgroundColor: currencyMenuOpenToggle
|
||||
? '#F9F9F9'
|
||||
: currencyMenuHoverToggle
|
||||
? '#F9F9F9'
|
||||
: 'transparent',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
alt="Choosed network image"
|
||||
:src="getNetworkImage(etherStore.networkName)"
|
||||
/>
|
||||
<span
|
||||
class="default-text group-hover:text-gray-900"
|
||||
:style="{
|
||||
color: currencyMenuOpenToggle
|
||||
? '#000000'
|
||||
: currencyMenuHoverToggle
|
||||
? '#000000'
|
||||
: 'rgb(249 250 251)',
|
||||
}"
|
||||
>
|
||||
{{ etherStore.networkName }}
|
||||
</span>
|
||||
<img
|
||||
class="text-gray-900"
|
||||
v-if="!currencyMenuHoverToggle && !currencyMenuOpenToggle"
|
||||
alt="Chevron Down"
|
||||
src="@/assets/chevronDown.svg"
|
||||
/>
|
||||
<img
|
||||
v-if="currencyMenuOpenToggle"
|
||||
alt="Chevron Up"
|
||||
src="@/assets/chevronUp.svg"
|
||||
/>
|
||||
<img
|
||||
v-if="currencyMenuHoverToggle && !currencyMenuOpenToggle"
|
||||
alt="Chevron Down Black"
|
||||
src="@/assets/chevronDownBlack.svg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="currencyMenuOpenToggle"
|
||||
class="mt-10 pl-3 absolute w-full text-gray-900"
|
||||
>
|
||||
<div class="mt-2">
|
||||
<div class="bg-white rounded-md z-10">
|
||||
<div
|
||||
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
|
||||
@click="networkChange(NetworkEnum.ethereum)"
|
||||
>
|
||||
<img
|
||||
alt="Ethereum image"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/ethereum.svg"
|
||||
/>
|
||||
<span class="text-gray-900 py-4 text-end font-semibold text-sm">
|
||||
Ethereum
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<div
|
||||
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
|
||||
@click="networkChange(NetworkEnum.polygon)"
|
||||
>
|
||||
<img
|
||||
alt="Polygon image"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/polygon.svg"
|
||||
/>
|
||||
<span class="text-gray-900 py-4 text-end font-semibold text-sm">
|
||||
Polygon
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
v-if="!walletAddress"
|
||||
class="border-amber-500 border-2 rounded default-button"
|
||||
@click="connectMetaMask()"
|
||||
>
|
||||
Conectar carteira
|
||||
</button>
|
||||
<div v-if="walletAddress" class="account-info">
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="top-bar-info cursor-pointer"
|
||||
@click="
|
||||
[
|
||||
(menuOpenToggle = !menuOpenToggle),
|
||||
(currencyMenuOpenToggle = false),
|
||||
]
|
||||
"
|
||||
@mouseover="menuHoverToggle = true"
|
||||
@mouseout="menuHoverToggle = false"
|
||||
:style="{
|
||||
backgroundColor: menuOpenToggle
|
||||
? '#F9F9F9'
|
||||
: menuHoverToggle
|
||||
? '#F9F9F9'
|
||||
: 'transparent',
|
||||
}"
|
||||
>
|
||||
<img alt="Account image" src="@/assets/account.svg" />
|
||||
<span
|
||||
class="default-text text-sm"
|
||||
:style="{
|
||||
color: menuOpenToggle
|
||||
? '#000000'
|
||||
: menuHoverToggle
|
||||
? '#000000'
|
||||
: 'rgb(249 250 251)',
|
||||
}"
|
||||
>
|
||||
{{ formatWalletAddress() }}
|
||||
</span>
|
||||
<img
|
||||
class="text-gray-900"
|
||||
v-if="!menuHoverToggle && !menuOpenToggle"
|
||||
alt="Chevron Down"
|
||||
src="@/assets/chevronDown.svg"
|
||||
/>
|
||||
<img
|
||||
v-if="menuOpenToggle"
|
||||
alt="Chevron Up"
|
||||
src="@/assets/chevronUp.svg"
|
||||
/>
|
||||
<img
|
||||
v-if="menuHoverToggle && !menuOpenToggle"
|
||||
alt="Chevron Down Black"
|
||||
src="@/assets/chevronDownBlack.svg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="menuOpenToggle"
|
||||
class="mt-10 absolute w-full text-gray-900"
|
||||
>
|
||||
<div class="pl-4 mt-2">
|
||||
<div class="bg-white rounded-md z-10">
|
||||
<div class="menu-button" @click="closeMenu()">
|
||||
<RouterLink to="/transaction_history" class="redirect_button">
|
||||
Histórico de transações
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<div class="menu-button" @click="closeMenu()">
|
||||
<RouterLink to="/manage_bids" class="redirect_button">
|
||||
Gerenciar Ofertas
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<div class="menu-button" @click="disconnectUser">
|
||||
<RouterLink to="/" class="redirect_button">
|
||||
Desconectar
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
@apply flex flex-row justify-between w-full items-center;
|
||||
}
|
||||
|
||||
.default-button {
|
||||
@apply px-4 py-2 rounded text-gray-50 font-semibold text-base hover:bg-transparent;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
@apply flex items-center gap-6;
|
||||
}
|
||||
|
||||
.default-text {
|
||||
@apply text-gray-50 font-semibold text-base;
|
||||
}
|
||||
|
||||
.top-bar-info {
|
||||
@apply flex justify-between gap-2 px-4 py-2 border-amber-500 border-2 rounded;
|
||||
}
|
||||
|
||||
.redirect_button {
|
||||
@apply py-4 text-gray-900 font-semibold text-xs w-full;
|
||||
}
|
||||
|
||||
.menu-button {
|
||||
@apply flex text-center justify-center hover:bg-gray-200;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
@apply bg-gray-200 rounded;
|
||||
}
|
||||
</style>
|
35
src/components/TopBar/__tests__/TopBar.spec.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/* eslint-disable no-undef */
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import TopBar from "../TopBar.vue";
|
||||
import { useEtherStore } from "../../../store/ether";
|
||||
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
|
||||
describe("TopBar.vue", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("should render connect wallet button", () => {
|
||||
const wrapper = shallowMount(TopBar);
|
||||
expect(wrapper.html()).toContain("Conectar carteira");
|
||||
});
|
||||
|
||||
it("should render button to change to seller view when in buyer screen", () => {
|
||||
const wrapper = shallowMount(TopBar);
|
||||
expect(wrapper.html()).toContain("Quero vender");
|
||||
});
|
||||
|
||||
it("should render button to change to seller view when in buyer screen", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setSellerView(true);
|
||||
const wrapper = shallowMount(TopBar);
|
||||
expect(wrapper.html()).toContain("Quero comprar");
|
||||
});
|
||||
|
||||
it("should render the P2Pix logo correctly", () => {
|
||||
const wrapper = shallowMount(TopBar);
|
||||
const img = wrapper.findAll(".logo");
|
||||
expect(img.length).toBe(1);
|
||||
});
|
||||
});
|
@ -1,86 +0,0 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.item {
|
||||
margin-top: 0;
|
||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
i {
|
||||
top: calc(50% - 25px);
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.item:before {
|
||||
content: " ";
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:after {
|
||||
content: " ";
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:first-of-type:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,12 +0,0 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,12 +0,0 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="17"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,12 +0,0 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="18"
|
||||
height="20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,12 +0,0 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@ -1,19 +0,0 @@
|
||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
class="iconify iconify--mdi"
|
||||
width="24"
|
||||
height="24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
@ -1,11 +1,13 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
import "./assets/main.css";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router);
|
||||
app.use(createPinia());
|
||||
|
||||
app.mount("#app");
|
||||
|
12
src/model/Faq.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export type Faq = Section[];
|
||||
|
||||
export type Section = {
|
||||
name: string;
|
||||
items: Question[];
|
||||
};
|
||||
|
||||
export type Question = {
|
||||
title: string;
|
||||
content: string;
|
||||
isOpen?: boolean;
|
||||
};
|
4
src/model/NetworkEnum.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum NetworkEnum {
|
||||
ethereum = "Ethereum",
|
||||
polygon = "Polygon",
|
||||
}
|
11
src/model/Pix.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export type Pix = {
|
||||
pixKey: string;
|
||||
merchantCity?: string;
|
||||
merchantName?: string;
|
||||
value?: number;
|
||||
transactionId?: string;
|
||||
message?: string;
|
||||
cep?: string;
|
||||
currency?: number;
|
||||
countryCode?: string;
|
||||
};
|
10
src/model/ValidDeposit.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { BigNumber } from "ethers";
|
||||
|
||||
export type ValidDeposit = {
|
||||
depositID: BigNumber;
|
||||
blockNumber: number;
|
||||
remaining: number;
|
||||
seller: string;
|
||||
pixKey: string;
|
||||
pixTarget?: string;
|
||||
};
|
92
src/model/mock/EventMock.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import type { Event } from "ethers";
|
||||
import { vi } from "vitest";
|
||||
|
||||
export const MockEvents: Event[] = [
|
||||
{
|
||||
blockNumber: 1,
|
||||
blockHash: "0x8",
|
||||
transactionIndex: 1,
|
||||
removed: false,
|
||||
address: "0x0",
|
||||
data: "0x0",
|
||||
topics: ["0x0", "0x0"],
|
||||
transactionHash: "0x0",
|
||||
logIndex: 1,
|
||||
event: "DepositAdded",
|
||||
eventSignature: "DepositAdded(address,uint256,address,uint256)",
|
||||
args: [
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x00",
|
||||
},
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x6c6b935b8bbd400000",
|
||||
},
|
||||
],
|
||||
getBlock: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
getTransaction: vi.fn(),
|
||||
getTransactionReceipt: vi.fn(),
|
||||
},
|
||||
{
|
||||
blockNumber: 2,
|
||||
blockHash: "0x8",
|
||||
transactionIndex: 2,
|
||||
removed: false,
|
||||
address: "0x0",
|
||||
data: "0x0",
|
||||
topics: ["0x0", "0x0"],
|
||||
transactionHash: "0x0",
|
||||
logIndex: 2,
|
||||
event: "LockAdded",
|
||||
eventSignature: "LockAdded(address,uint256,address,uint256)",
|
||||
args: [
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x00",
|
||||
},
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x6c6b935b8bbd400000",
|
||||
},
|
||||
],
|
||||
getBlock: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
getTransaction: vi.fn(),
|
||||
getTransactionReceipt: vi.fn(),
|
||||
},
|
||||
{
|
||||
blockNumber: 3,
|
||||
blockHash: "0x8",
|
||||
transactionIndex: 3,
|
||||
removed: false,
|
||||
address: "0x0",
|
||||
data: "0x0",
|
||||
topics: ["0x0", "0x0"],
|
||||
transactionHash: "0x0",
|
||||
logIndex: 3,
|
||||
event: "LockReleased",
|
||||
eventSignature: "LockReleased(address,uint256,address,uint256)",
|
||||
args: [
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x00",
|
||||
},
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x6c6b935b8bbd400000",
|
||||
},
|
||||
],
|
||||
getBlock: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
getTransaction: vi.fn(),
|
||||
getTransactionReceipt: vi.fn(),
|
||||
},
|
||||
];
|
40
src/model/mock/ValidDepositMock.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { parseEther } from "ethers/lib/utils";
|
||||
import type { ValidDeposit } from "../ValidDeposit";
|
||||
|
||||
export const MockValidDeposits: ValidDeposit[] = [
|
||||
{
|
||||
blockNumber: 1,
|
||||
depositID: parseEther("1"),
|
||||
remaining: 70,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: "123456789",
|
||||
},
|
||||
{
|
||||
blockNumber: 2,
|
||||
depositID: parseEther("2"),
|
||||
remaining: 200,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: "123456789",
|
||||
},
|
||||
{
|
||||
blockNumber: 3,
|
||||
depositID: parseEther("3"),
|
||||
remaining: 1250,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: "123456789",
|
||||
},
|
||||
{
|
||||
blockNumber: 4,
|
||||
depositID: parseEther("4"),
|
||||
remaining: 4000,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: "123456789",
|
||||
},
|
||||
{
|
||||
blockNumber: 5,
|
||||
depositID: parseEther("5"),
|
||||
remaining: 2000,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: "123456789",
|
||||
},
|
||||
];
|
@ -1,5 +1,9 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import HomeView from "../views/HomeView.vue";
|
||||
import TransactionHistoryView from "../views/TransactionHistoryView.vue";
|
||||
import FaqView from "../views/FaqView.vue";
|
||||
import ManageBidsView from "../views/ManageBidsView.vue";
|
||||
import SellerView from "@/views/SellerView.vue";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@ -10,12 +14,24 @@ const router = createRouter({
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
name: "about",
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import("../views/AboutView.vue"),
|
||||
path: "/seller",
|
||||
name: "seller",
|
||||
component: SellerView,
|
||||
},
|
||||
{
|
||||
path: "/transaction_history",
|
||||
name: "transaction history",
|
||||
component: TransactionHistoryView,
|
||||
},
|
||||
{
|
||||
path: "/manage_bids",
|
||||
name: "manage bids",
|
||||
component: ManageBidsView,
|
||||
},
|
||||
{
|
||||
path: "/faq",
|
||||
name: "faq",
|
||||
component: FaqView,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
14
src/services/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import axios from "axios";
|
||||
|
||||
const defaultConfig = {
|
||||
"Content-Type": "application/json",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
};
|
||||
|
||||
const api = axios.create({
|
||||
...defaultConfig,
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
});
|
||||
|
||||
export default api;
|
5
src/shims-vue.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module "*.vue" {
|
||||
import { DefineComponent } from "vue";
|
||||
const component: DefineComponent;
|
||||
export default component;
|
||||
}
|
51
src/store/ether.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useEtherStore = defineStore("ether", {
|
||||
state: () => ({
|
||||
walletAddress: "",
|
||||
balance: "",
|
||||
networkName: NetworkEnum.ethereum,
|
||||
loadingLock: false,
|
||||
sellerView: false,
|
||||
// Depósitos válidos para compra GOERLI
|
||||
depositsValidListGoerli: [] as ValidDeposit[],
|
||||
// Depósitos válidos para compra MUMBAI
|
||||
depositsValidListMumbai: [] as ValidDeposit[],
|
||||
}),
|
||||
actions: {
|
||||
setWalletAddress(walletAddress: string) {
|
||||
this.walletAddress = walletAddress;
|
||||
},
|
||||
setBalance(balance: string) {
|
||||
this.balance = balance;
|
||||
},
|
||||
setNetworkName(networkName: NetworkEnum) {
|
||||
this.networkName = networkName;
|
||||
},
|
||||
setLoadingLock(isLoadingLock: boolean) {
|
||||
this.loadingLock = isLoadingLock;
|
||||
},
|
||||
setSellerView(sellerView: boolean) {
|
||||
this.sellerView = sellerView;
|
||||
},
|
||||
setDepositsValidListGoerli(depositsValidList: ValidDeposit[]) {
|
||||
this.depositsValidListGoerli = depositsValidList;
|
||||
},
|
||||
setDepositsValidListMumbai(depositsValidList: ValidDeposit[]) {
|
||||
this.depositsValidListMumbai = depositsValidList;
|
||||
},
|
||||
},
|
||||
// Alterar para integrar com mumbai
|
||||
getters: {
|
||||
getValidDepositByWalletAddress: (state) => {
|
||||
return (walletAddress: string) =>
|
||||
state.depositsValidListGoerli
|
||||
.filter((deposit) => deposit.seller == walletAddress)
|
||||
.sort((a, b) => {
|
||||
return b.blockNumber - a.blockNumber;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
11
src/svgTransform.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
process() {
|
||||
return {
|
||||
code: `module.exports = {};`,
|
||||
};
|
||||
},
|
||||
getCacheKey() {
|
||||
// The output is always the same.
|
||||
return "svgTransform";
|
||||
},
|
||||
};
|
76
src/utils/QrCodePix.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import qrcode from "qrcode";
|
||||
import type { QRCodeToDataURLOptions } from "qrcode";
|
||||
import { crc16ccitt } from "crc";
|
||||
import type { Pix } from "@/model/Pix";
|
||||
|
||||
const pix = ({
|
||||
pixKey,
|
||||
merchantCity = "city",
|
||||
merchantName = "name",
|
||||
value,
|
||||
message,
|
||||
cep,
|
||||
transactionId = "***",
|
||||
currency = 986,
|
||||
countryCode = "BR",
|
||||
}: Pix) => {
|
||||
const payloadKeyString = generatePixKey(pixKey, message);
|
||||
|
||||
const payload: string[] = [
|
||||
formatEMV("00", "01"), //Payload Format Indicator
|
||||
formatEMV("26", payloadKeyString), // Merchant Account Information
|
||||
formatEMV("52", "0000"), //Merchant Category Code
|
||||
formatEMV("53", String(currency)), // Transaction Currency
|
||||
];
|
||||
|
||||
if (String(value) === "0") {
|
||||
value = undefined;
|
||||
}
|
||||
if (value) {
|
||||
payload.push(formatEMV("54", value.toFixed(2)));
|
||||
}
|
||||
|
||||
payload.push(formatEMV("58", countryCode.toUpperCase())); // Country Code
|
||||
payload.push(formatEMV("59", merchantName)); // Merchant Name
|
||||
payload.push(formatEMV("60", merchantCity)); // Merchant City
|
||||
|
||||
if (cep) {
|
||||
payload.push(formatEMV("61", cep)); // Postal Code
|
||||
}
|
||||
|
||||
payload.push(formatEMV("62", formatEMV("05", transactionId))); // Additional Data Field Template
|
||||
|
||||
payload.push("6304");
|
||||
|
||||
const stringPayload = payload.join("");
|
||||
const crcResult = crc16ccitt(stringPayload)
|
||||
.toString(16)
|
||||
.toUpperCase()
|
||||
.padStart(4, "0");
|
||||
|
||||
const payloadPIX = `${stringPayload}${crcResult}`;
|
||||
|
||||
return {
|
||||
payload: (): string => payloadPIX,
|
||||
base64QrCode: (options?: QRCodeToDataURLOptions): Promise<string> =>
|
||||
qrcode.toDataURL(payloadPIX, options),
|
||||
};
|
||||
};
|
||||
|
||||
const generatePixKey = (pixKey: string, message?: string): string => {
|
||||
const payload: string[] = [
|
||||
formatEMV("00", "BR.GOV.BCB.PIX"),
|
||||
formatEMV("01", pixKey),
|
||||
];
|
||||
if (message) {
|
||||
payload.push(formatEMV("02", message));
|
||||
}
|
||||
return payload.join("");
|
||||
};
|
||||
|
||||
const formatEMV = (id: string, param: string): string => {
|
||||
const len = param?.length?.toString().padStart(2, "0");
|
||||
return `${id}${len}${param}`;
|
||||
};
|
||||
|
||||
export { pix };
|
9
src/utils/debounce.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export const debounce = (func: any, delay: number) => {
|
||||
let timer: any = null;
|
||||
return function (this: unknown, ...args: any) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
};
|
6
src/utils/decimalCount.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const decimalCount = (numStr: string): number => {
|
||||
if (numStr.includes(".")) {
|
||||
return numStr.split(".")[1].length;
|
||||
}
|
||||
return 0;
|
||||
};
|
23
src/utils/networkLiquidity.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
|
||||
const verifyNetworkLiquidity = (
|
||||
tokenValue: number,
|
||||
walletAddress: string,
|
||||
validDepositList: ValidDeposit[]
|
||||
): ValidDeposit | undefined => {
|
||||
const element = validDepositList.find((element) => {
|
||||
const remaining = element.remaining;
|
||||
if (
|
||||
tokenValue!! <= remaining &&
|
||||
tokenValue!! != 0 &&
|
||||
element.seller !== walletAddress
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
export { verifyNetworkLiquidity };
|
337
src/utils/smart_contract_files/MockToken.json
Normal file
923
src/utils/smart_contract_files/P2PIX.json
Normal file
8
src/utils/smart_contract_files/goerli.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"signers": [
|
||||
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
||||
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
|
||||
],
|
||||
"p2pix": "0x5f3EFA9A90532914545CEf527C530658af87e196",
|
||||
"token": "0x294003F602c321627152c6b7DED3EAb5bEa853Ee"
|
||||
}
|
8
src/utils/smart_contract_files/localhost.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"signers": [
|
||||
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
||||
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
|
||||
],
|
||||
"p2pix": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
|
||||
"token": "0x5FbDB2315678afecb367f032d93F642f64180aa3"
|
||||
}
|
8
src/utils/smart_contract_files/polygon-mumbai.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"signers": [
|
||||
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
||||
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
|
||||
],
|
||||
"p2pix": "0x5f3EFA9A90532914545CEf527C530658af87e196",
|
||||
"token": "0x294003F602c321627152c6b7DED3EAb5bEa853Ee"
|
||||
}
|
6
src/utils/smart_contract_files/wallets.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"wallets":[
|
||||
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
||||
"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"
|
||||
]
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@media (min-width: 1024px) {
|
||||
.about {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
189
src/views/FaqView.vue
Normal file
@ -0,0 +1,189 @@
|
||||
<script setup lang="ts">
|
||||
import type { Faq } from "@/model/Faq";
|
||||
import { ref } from "vue";
|
||||
import { marked } from "marked";
|
||||
|
||||
const faq = ref<Faq>([
|
||||
{
|
||||
name: "1. Como Começar",
|
||||
items: [
|
||||
{
|
||||
title: "O que é uma carteira (wallet)",
|
||||
content:
|
||||
"# Lorem ipsum dolor sit **amet** consectetur. Neque libero magna mi purus. Aliquam vitae feugiat quis sapien. Pharetra gravida nisi donec bibendum mauris aliquam molestie. Et nunc placerat in ac integer maecenas arcu. Lotuam tincidunt morbi ac sed fames habitasse velit et nunc.",
|
||||
},
|
||||
{
|
||||
title: "O que é uma carteira (wallet)",
|
||||
content:
|
||||
"Lorem ipsum dolor sit amet consectetur. Neque libero magna mi purus. Aliquam vitae feugiat quis sapien. Pharetra gravida nisi donec bibendum mauris aliquam molestie. Et nunc placerat in ac integer maecenas arcu. Lotuam tincidunt morbi ac sed fames habitasse velit et nunc.",
|
||||
},
|
||||
{
|
||||
title: "O que é uma carteira (wallet)",
|
||||
content:
|
||||
"Lorem ipsum dolor sit amet consectetur. Neque libero magna mi purus. Aliquam vitae feugiat quis sapien. Pharetra gravida nisi donec bibendum mauris aliquam molestie. Et nunc placerat in ac integer maecenas arcu. Lotuam tincidunt morbi ac sed fames habitasse velit et nunc.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "2. Comprar tokens",
|
||||
items: [
|
||||
{
|
||||
title: "O que é um endereço de carteira (wallet address)",
|
||||
content:
|
||||
"Lorem ipsum dolor sit amet consectetur. Neque libero magna mi purus. Aliquam vitae feugiat quis sapien. Pharetra gravida nisi donec bibendum mauris aliquam molestie. Et nunc placerat in ac integer maecenas arcu. Lotuam tincidunt morbi ac sed fames habitasse velit et nunc.",
|
||||
},
|
||||
{
|
||||
title: "O que é um endereço de carteira (wallet address)",
|
||||
content:
|
||||
"Lorem ipsum dolor sit amet consectetur. Neque libero magna mi purus. Aliquam vitae feugiat quis sapien. Pharetra gravida nisi donec bibendum mauris aliquam molestie. Et nunc placerat in ac integer maecenas arcu. Lotuam tincidunt morbi ac sed fames habitasse velit et nunc.",
|
||||
},
|
||||
{
|
||||
title: "O que é um endereço de carteira (wallet address)",
|
||||
content:
|
||||
"Lorem ipsum dolor sit amet consectetur. Neque libero magna mi purus. Aliquam vitae feugiat quis sapien. Pharetra gravida nisi donec bibendum mauris aliquam molestie. Et nunc placerat in ac integer maecenas arcu. Lotuam tincidunt morbi ac sed fames habitasse velit et nunc.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "3. Vender tokens",
|
||||
items: [
|
||||
{
|
||||
title: "Como conectar a carteira ao p2pix",
|
||||
content:
|
||||
"Lorem ipsum dolor sit amet consectetur. Neque libero magna mi purus. Aliquam vitae feugiat quis sapien. Pharetra gravida nisi donec bibendum mauris aliquam molestie. Et nunc placerat in ac integer maecenas arcu. Lotuam tincidunt morbi ac sed fames habitasse velit et nunc.",
|
||||
},
|
||||
{
|
||||
title: "Como conectar a carteira ao p2pix",
|
||||
content:
|
||||
"Lorem ipsum dolor sit amet consectetur. Neque libero magna mi purus. Aliquam vitae feugiat quis sapien. Pharetra gravida nisi donec bibendum mauris aliquam molestie. Et nunc placerat in ac integer maecenas arcu. Lotuam tincidunt morbi ac sed fames habitasse velit et nunc.",
|
||||
},
|
||||
{
|
||||
title: "Como conectar a carteira ao p2pix",
|
||||
content:
|
||||
"Lorem ipsum dolor sit amet consectetur. Neque libero magna mi purus. Aliquam vitae feugiat quis sapien. Pharetra gravida nisi donec bibendum mauris aliquam molestie. Et nunc placerat in ac integer maecenas arcu. Lotuam tincidunt morbi ac sed fames habitasse velit et nunc.",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const selectedSection = ref<number>(0);
|
||||
|
||||
const setSelectedSection = (index: number) => {
|
||||
selectedSection.value = index;
|
||||
};
|
||||
|
||||
const openItem = (index: number) => {
|
||||
faq.value[selectedSection.value].items[index].isOpen =
|
||||
!faq.value[selectedSection.value].items[index].isOpen;
|
||||
|
||||
faq.value[selectedSection.value].items[index].content = marked(
|
||||
faq.value[selectedSection.value].items[index].content
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="text-container">
|
||||
<span class="text font-extrabold text-5xl max-w-[50rem]"
|
||||
>Perguntas Frequentes
|
||||
</span>
|
||||
<span class="text text-xl font-medium text-base max-w-[40rem]"
|
||||
>Não conseguiu uma resposta para sua dúvida? Acesse a comunidade do
|
||||
Discord para falar diretamente conosco.</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between w-10/12 mt-20">
|
||||
<div>
|
||||
<h1 class="text-3xl text-gray-800 font-bold">Sumário</h1>
|
||||
<h3
|
||||
:class="index == selectedSection ? 'selected-sumario' : 'sumario'"
|
||||
v-for="(f, index) in faq"
|
||||
v-bind:key="f.name"
|
||||
@click="setSelectedSection(index)"
|
||||
>
|
||||
{{ f.name }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="w-4/6">
|
||||
<div
|
||||
v-for="(item, index) in faq[selectedSection].items"
|
||||
v-bind:key="item.title"
|
||||
>
|
||||
<div class="flex cursor-pointer" @click="openItem(index)">
|
||||
<img
|
||||
alt="plus"
|
||||
src="@/assets/plus.svg"
|
||||
class="mr-3"
|
||||
v-if="!item.isOpen"
|
||||
/>
|
||||
<img
|
||||
alt="plus"
|
||||
src="@/assets/minus.svg"
|
||||
class="mr-3"
|
||||
v-if="item.isOpen"
|
||||
/>
|
||||
<h4>{{ item.title }}</h4>
|
||||
</div>
|
||||
<div
|
||||
style="padding-top: 24px"
|
||||
v-if="item.isOpen"
|
||||
v-html="item.content"
|
||||
></div>
|
||||
<div class="hr"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sumario {
|
||||
margin-top: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-sumario {
|
||||
font-weight: bolder;
|
||||
margin-top: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.page {
|
||||
@apply flex flex-col items-center justify-center w-full mt-16;
|
||||
}
|
||||
|
||||
.hr {
|
||||
border: 1px solid #1f2937;
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
p,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
@apply text-gray-800 text-xl;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
h2,
|
||||
h4 {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
@apply flex flex-col items-center justify-center gap-4;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply text-gray-800 text-center;
|
||||
}
|
||||
.blur-container-row {
|
||||
@apply flex flex-row justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
|
||||
}
|
||||
</style>
|
@ -1,9 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
import TheWelcome from "../components/TheWelcome.vue";
|
||||
import SearchComponent from "@/components/SearchComponent.vue";
|
||||
import ValidationComponent from "@/components/LoadingComponent.vue";
|
||||
import BuyConfirmedComponent from "@/components/BuyConfirmedComponent/BuyConfirmedComponent.vue";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import QrCodeComponent from "@/components/QrCodeComponent.vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { addLock, releaseLock } from "@/blockchain/buyerMethods";
|
||||
import {
|
||||
updateWalletStatus,
|
||||
listReleaseTransactionByWalletAddress,
|
||||
} from "@/blockchain/wallet";
|
||||
import { getNetworksLiquidity } from "@/blockchain/events";
|
||||
import type { Event } from "ethers";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
|
||||
enum Step {
|
||||
Search,
|
||||
Buy,
|
||||
List,
|
||||
}
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setSellerView(false);
|
||||
|
||||
// States
|
||||
const { loadingLock, walletAddress } = storeToRefs(etherStore);
|
||||
const flowStep = ref<Step>(Step.Search);
|
||||
const pixTarget = ref<string>("");
|
||||
const tokenAmount = ref<number>();
|
||||
const _lockID = ref<string>("");
|
||||
const loadingRelease = ref<boolean>(false);
|
||||
const lastWalletReleaseTransactions = ref<Event[]>([]);
|
||||
|
||||
const confirmBuyClick = async (
|
||||
selectedDeposit: ValidDeposit,
|
||||
tokenValue: number
|
||||
) => {
|
||||
// finish buy screen
|
||||
pixTarget.value = selectedDeposit.pixKey;
|
||||
tokenAmount.value = tokenValue;
|
||||
|
||||
// Makes lock with deposit ID and the Amount
|
||||
if (selectedDeposit) {
|
||||
flowStep.value = Step.Buy;
|
||||
etherStore.setLoadingLock(true);
|
||||
|
||||
await addLock(selectedDeposit.depositID, tokenValue)
|
||||
.then((lockID) => {
|
||||
_lockID.value = lockID;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
flowStep.value = Step.Search;
|
||||
});
|
||||
|
||||
etherStore.setLoadingLock(false);
|
||||
}
|
||||
};
|
||||
|
||||
const releaseTransaction = async (e2eId: string) => {
|
||||
flowStep.value = Step.List;
|
||||
loadingRelease.value = true;
|
||||
|
||||
if (_lockID.value && tokenAmount.value) {
|
||||
const release = await releaseLock(
|
||||
pixTarget.value,
|
||||
tokenAmount.value,
|
||||
e2eId,
|
||||
_lockID.value
|
||||
);
|
||||
release.wait();
|
||||
|
||||
await listReleaseTransactionByWalletAddress(
|
||||
walletAddress.value.toLowerCase()
|
||||
).then((releaseTransactions) => {
|
||||
if (releaseTransactions)
|
||||
lastWalletReleaseTransactions.value = releaseTransactions;
|
||||
});
|
||||
|
||||
await updateWalletStatus();
|
||||
loadingRelease.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await getNetworksLiquidity();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<TheWelcome />
|
||||
</main>
|
||||
<SearchComponent
|
||||
v-if="flowStep == Step.Search"
|
||||
@token-buy="confirmBuyClick"
|
||||
/>
|
||||
<div v-if="flowStep == Step.Buy">
|
||||
<QrCodeComponent
|
||||
:pixTarget="pixTarget"
|
||||
:tokenValue="tokenAmount"
|
||||
@pix-validated="releaseTransaction"
|
||||
v-if="!loadingLock"
|
||||
/>
|
||||
<ValidationComponent
|
||||
v-if="loadingLock"
|
||||
:message="'A transação está sendo enviada para a rede'"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="flowStep == Step.List">
|
||||
<BuyConfirmedComponent
|
||||
v-if="!loadingRelease"
|
||||
:last-wallet-release-transactions="lastWalletReleaseTransactions"
|
||||
:tokenAmount="tokenAmount"
|
||||
@make-another-transaction="flowStep = Step.Search"
|
||||
/>
|
||||
<ValidationComponent
|
||||
v-if="loadingRelease"
|
||||
:message="'A transação está sendo enviada para a rede. Em breve os tokens serão depositados em sua carteira.'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
86
src/views/ManageBidsView.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { storeToRefs } from "pinia";
|
||||
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
||||
import type { BigNumber } from "ethers";
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import { cancelDeposit, withdrawDeposit } from "@/blockchain/buyerMethods";
|
||||
import { listValidDepositTransactionsByWalletAddress } from "@/blockchain/wallet";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const { walletAddress, networkName } = storeToRefs(etherStore);
|
||||
const depositList = ref<ValidDeposit[]>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
if (walletAddress.value) {
|
||||
const walletDeposits = await listValidDepositTransactionsByWalletAddress(
|
||||
walletAddress.value
|
||||
);
|
||||
if (walletDeposits) {
|
||||
depositList.value = walletDeposits;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const handleCancelDeposit = async (depositID: BigNumber, index: number) => {
|
||||
const response = await cancelDeposit(depositID);
|
||||
if (response) {
|
||||
console.log("Depósito cancelado com sucesso.");
|
||||
depositList.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleWithDrawDeposit = async (depositID: BigNumber, index: number) => {
|
||||
const response = await withdrawDeposit(depositID);
|
||||
if (response) {
|
||||
console.log("Token retirado com sucesso.");
|
||||
depositList.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
watch(walletAddress, async () => {
|
||||
await listValidDepositTransactionsByWalletAddress(walletAddress.value)
|
||||
.then((res) => {
|
||||
if (res) depositList.value = res;
|
||||
})
|
||||
.catch(() => {
|
||||
depositList.value = [];
|
||||
});
|
||||
});
|
||||
|
||||
watch(networkName, async () => {
|
||||
await listValidDepositTransactionsByWalletAddress(walletAddress.value)
|
||||
.then((res) => {
|
||||
if (res) depositList.value = res;
|
||||
})
|
||||
.catch(() => {
|
||||
depositList.value = [];
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="header">Gerenciar ofertas</div>
|
||||
<div class="w-full max-w-4xl">
|
||||
<ListingComponent
|
||||
:wallet-transactions="depositList"
|
||||
:is-manage-mode="true"
|
||||
@cancel-deposit="handleCancelDeposit"
|
||||
@withdraw-deposit="handleWithDrawDeposit"
|
||||
></ListingComponent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
@apply flex flex-col items-center gap-10 mt-20 w-full;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply text-3xl text-gray-900 leading-9 font-bold justify-center flex;
|
||||
}
|
||||
</style>
|
77
src/views/SellerView.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import WantSellComponent from "../components/SellerSteps/WantSellComponent.vue";
|
||||
import SendNetwork from "../components/SellerSteps/SendNetwork.vue";
|
||||
import ValidationComponent from "../components/LoadingComponent.vue";
|
||||
import { approveTokens, addDeposit } from "../blockchain/sellerMethods";
|
||||
|
||||
import { ref } from "vue";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
|
||||
enum Step {
|
||||
Search,
|
||||
Sell,
|
||||
Network,
|
||||
}
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setSellerView(true);
|
||||
|
||||
const flowStep = ref<Step>(Step.Sell);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const offerValue = ref<string>("");
|
||||
const pixKeyBuyer = ref<string>("");
|
||||
|
||||
// Verificar tipagem
|
||||
const approveOffer = async (args: { offer: string; pixKey: string }) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
offerValue.value = args.offer;
|
||||
pixKeyBuyer.value = args.pixKey;
|
||||
await approveTokens(args.offer);
|
||||
flowStep.value = Step.Network;
|
||||
loading.value = false;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
flowStep.value = Step.Sell;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const sendNetwork = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
if (offerValue.value && pixKeyBuyer.value) {
|
||||
await addDeposit(String(offerValue.value), pixKeyBuyer.value);
|
||||
flowStep.value = Step.Sell;
|
||||
loading.value = false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
flowStep.value = Step.Network;
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="flowStep == Step.Sell">
|
||||
<WantSellComponent v-if="!loading" @approve-tokens="approveOffer" />
|
||||
<ValidationComponent
|
||||
v-if="loading"
|
||||
:message="'A transação está sendo enviada para a rede.'"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="flowStep == Step.Network">
|
||||
<SendNetwork
|
||||
:pixKey="pixKeyBuyer"
|
||||
:offer="Number(offerValue)"
|
||||
v-if="!loading"
|
||||
@send-network="sendNetwork"
|
||||
/>
|
||||
<ValidationComponent
|
||||
v-if="loading"
|
||||
:message="'A transação está sendo enviada para a rede.'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
63
src/views/TransactionHistoryView.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
||||
import { listAllTransactionByWalletAddress } from "@/blockchain/wallet";
|
||||
import type { Event } from "ethers";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
const { walletAddress, networkName } = storeToRefs(etherStore);
|
||||
const allUserTransactions = ref<(Event | ValidDeposit)[]>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
if (walletAddress.value) {
|
||||
await listAllTransactionByWalletAddress(walletAddress.value).then((res) => {
|
||||
if (res) allUserTransactions.value = res;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
watch(walletAddress, async (newValue) => {
|
||||
await listAllTransactionByWalletAddress(newValue)
|
||||
.then((res) => {
|
||||
if (res) allUserTransactions.value = res;
|
||||
})
|
||||
.catch(() => {
|
||||
allUserTransactions.value = [];
|
||||
});
|
||||
});
|
||||
|
||||
watch(networkName, async () => {
|
||||
await listAllTransactionByWalletAddress(walletAddress.value)
|
||||
.then((res) => {
|
||||
if (res) allUserTransactions.value = res;
|
||||
})
|
||||
.catch(() => {
|
||||
allUserTransactions.value = [];
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="header">Histórico de transações</div>
|
||||
<div class="w-full max-w-4xl">
|
||||
<ListingComponent
|
||||
:wallet-transactions="allUserTransactions"
|
||||
:is-manage-mode="false"
|
||||
></ListingComponent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
@apply flex flex-col gap-10 mt-20 w-full items-center;
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply text-3xl text-gray-900 leading-9 font-bold justify-center flex;
|
||||
}
|
||||
</style>
|
@ -5,7 +5,9 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"types": ["jest", "node"],
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
|
||||
"references": [
|
||||
|
@ -1,11 +1,21 @@
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
import { defineConfig } from "vite";
|
||||
import { defineConfig } from "vitest/config";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
coverage: {
|
||||
provider: "c8",
|
||||
all: true,
|
||||
src: ["./src"],
|
||||
reporter: ["text", "lcov", "html"],
|
||||
},
|
||||
},
|
||||
plugins: [vue(), vueJsx()],
|
||||
resolve: {
|
||||
alias: {
|
||||
|