├── .env.local ├── .github └── workflows │ └── fmt.yml ├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── env.d.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── public ├── arweave.png ├── icons │ ├── 1024.png │ ├── 192.png │ └── 512.png ├── logo_dark.svg ├── logo_light.svg └── manifest.json ├── src ├── components │ ├── Balance.tsx │ ├── ChangelogModal.tsx │ ├── CollectionModal.tsx │ ├── Footer.tsx │ ├── ListingModal.tsx │ ├── Metas.tsx │ ├── MintCollectible.tsx │ ├── Nav.tsx │ ├── PSTSwitcher.tsx │ ├── Search.tsx │ ├── SetupModal.tsx │ ├── Watchlist.tsx │ ├── icons │ │ ├── Facebook.tsx │ │ ├── Github.tsx │ │ ├── Instagram.tsx │ │ └── Twitter.tsx │ └── space │ │ ├── Art.tsx │ │ ├── Collection.tsx │ │ └── Community.tsx ├── pages │ ├── 404.tsx │ ├── 500.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── app.tsx │ ├── index.tsx │ ├── orbit │ │ ├── index.tsx │ │ ├── order │ │ │ └── [id].tsx │ │ └── post │ │ │ └── [addr].tsx │ ├── space │ │ ├── [id].tsx │ │ └── index.tsx │ ├── swap.tsx │ └── user │ │ ├── [input].tsx │ │ └── [input] │ │ ├── creations.tsx │ │ ├── owns.tsx │ │ ├── trades.tsx │ │ └── transactions.tsx ├── store │ ├── actions.ts │ ├── index.ts │ ├── reducers.ts │ └── reducers │ │ ├── address.ts │ │ └── theme.ts ├── styles │ ├── components │ │ ├── Balance.module.sass │ │ ├── ChangelogModal.module.sass │ │ ├── Footer.module.sass │ │ ├── ListingModal.module.sass │ │ ├── Nav.module.sass │ │ ├── PSTSwitcher.module.sass │ │ ├── PeriodMenu.sass │ │ ├── Search.module.sass │ │ ├── SearchInput.module.sass │ │ ├── SearchPopover.sass │ │ ├── SetupModal.module.sass │ │ └── Watchlist.module.sass │ ├── global.sass │ ├── progress.sass │ ├── variables.sass │ └── views │ │ ├── 404.module.sass │ │ ├── app.module.sass │ │ ├── art.module.sass │ │ ├── collection.module.sass │ │ ├── community.module.sass │ │ ├── home.module.sass │ │ ├── orbit.module.sass │ │ ├── space.module.sass │ │ ├── swap.module.sass │ │ └── user.module.sass └── utils │ ├── animations.ts │ ├── arconnect.ts │ ├── arweave.ts │ ├── format.ts │ ├── geofence.ts │ ├── graph.ts │ ├── infinite_scroll.ts │ ├── order.ts │ ├── storage_names.ts │ └── user.ts ├── tsconfig.json ├── vercel.json └── yarn.lock /.env.local: -------------------------------------------------------------------------------- 1 | FAUCET_WALLET={"d":"JVWkmvTqbl-ze4YwHP0PEc-NUG2FAzmux-ojhrwvxE55ikQFixZHvXwfrHDFeHfDzxzacJxsD2Sk2VIzdZnVclX6lZWbnQOrd8_HqYEynp4shWRG7q6IsSUG7NGUU2GCvF1WBRksI3ql_pRBSqUikkLmG5PNwGNEOZOzIm2UjYt1l1cbJARhKwfDxB52oj1LRRyiy-VjTFp7xULGkcktFRi4I7Yw-HGhQoxULrgqdFFrMYRh1F2Rsyx4BxOoinC38MTeFa0D7PK6rkPvZACfgREDuw5shI9r6xO0oNYRqF4KX8oaeycPYCtjcRAGBDo08lZFdemJdr1TFvnBU2Nl91rdb1-M64Sc461TE1KQOD-ay1TlhlOfhpm-ahVYtOyL1MEIQJ9yas0SbMJg-wBzXbRqWFajuB5D5Y4DnFImClvPRRkUlNpEgD25HPlvMQNC4ohiZ3n6KArsf0_nmTkvCdF_kkyTglcIeILhLBrSS2vvPEpHgxSlqOA9L9q6WKCYcc5jYRj3L55n-Itv6OFu1EXHvgpPloVTIFodmtMurAmWNeHqi5RKSa7cJFsXDdRxuNoJzQCwd_8fU-aU5cDyforkoyckvpP4qsbPuKSC0lueDmZECC_BED2aPtB4urI3kt7kjoRMljSfBQOjCeQUr64f-bqg5TbXGy_dYceb8AE","dp":"kEvLtfwEDLpyYEhdJlkd4r2q5SgTvOttf_ej00N67grCIW_rp4afK4wbv0GxgyZ4as40SIxYRa5m1C44sMB2UyuJDpEEs2D3c5XxnTZ9ptF6Y2_BasbUfsb5v5AQ0ItS3hyDG6hdyJh7qtNImDt4Zv4vOeLPH0EpVLv4Po6uAPi16l_67vsZ_YH-OEq4jbujAqVJYPCYDoEfHEpoNnVdtrWlv0ncTLgR_pTDntxh2jYye_dCWADCa03kxw22CNixNJByzwKqGlXTfraf8i1VAZNYIf8Nsp-FS-MDETSb0GA7xYBDogKp4763PnTsLUSLD3imov_SpjIdNtNJ5GIOAQ","dq":"lfBSUuBD9Ct06KAMr74KcDoh4SY6SP1yihTUDlNA50xpj-lhHrxiRebWLxykl6QTAhmjSIS-Hs5QLhiIlbcDfsiH1W6mrEJCfC4d9L4jceoaj4BUZ1gN8JYAL-rHs9slVqCQBfnrCKiR3N2laClRQ0-0dfQ0_PbrwOMRavfzqL6BkfXyT8MvEsxIB4cV_cfxSdtuN7uDFHVPjWAZyAl6n2e4kVuDAq8olbzYBkuvqjSOTa3tbfTx27B2DdQuMtaOS9l-05WAxEcwzvvpa8_85mFn8CQ63P4P4kiWElA_jpqMO6y8BA06q3JMIl04jiBYnODse0clx_Wzy5tYamPDMQ","e":"AQAB","ext":true,"kty":"RSA","n":"uZL1ZB54oyLFchn4uPDWVKp0nLQNmwzfeGqfMHnnRgnIXjyc8RDPl2JJcTktq8wgNXIZYr9Cuq3bCAAXwudNw67IA-MN-OE8KP3ze3G40T4EdQ4fKXFbni09mp6Sw8mtIUzCSPMcxItHuiVkiLxb3WPw-zPY3G4js_co_VqYPklqH71B0piYDf9k5xf-Thb6oTCYsqF8lmRiFa3KEsA8O9YV3uvq7TKSrASOulyPOMoSgJCFIXEtWc1i6mw3pe8ngGnpVSkYj3850hzOgeSxB3JHVfUoWJ0LwaDlCqxEs8lM02kklbA_0ynzT4l3gqeVOPiFWkjMQwAh8eueeehgrJmbC9VUsEFWZcO_6RXZJcBzGfKUfzBzyfmtquOOszGkSGNO_2463QEUnEMjRG4aTm6PMEY_4s3G9rSfe7Ndk2lsEffukAPFHRhObr5rD636hMXu3Kh-2ogtU9iafjS9yr9FSrxLsx6pTjWLqcwRUsHrLkFBzaDViNWEBG1_p7iauaenuVPrfgn2OIxG5q1HomJmuPOB3B5g-8aqXxMbKNh9-qtXNNF-mGBGftLU3Av4VcmTYBe98sbY8NLKSiK6OWccO1Vdv8Qkip6_6XnJh0my7NA8NdjwpP58LZifKvvKvt1BYT1qG2jjC46jjXMqOje3fDebC6stNCuZ_SaH4tE","p":"_kEVhynD53ydRxdJn6ftzQJIWB7lc_FChniH3AoQZEDPVvjH0Yr0k17Fj3Y9Ef3ZfhH4wCxGqMkQk-7anoF5NEDklFphWn5AJts2b_h54-JwjelD10FePKzme292kV3ICegxnNQceVicgsQ9QPz2T-noWeBRgcVVtEw9sAOcR3ZkCuNOvwa885_i4zj0UOp2PDaKKuE9mFpJTFBB3GKlWohIfxT2Vyqtr5KM9TGoW639Ld2L801IiB30Y2e7U7NBvVupRcnMacZcrJS3CKPORVqTd4yWKtjdGZetNv3YhuBTg7JCZMfLS0nfk4ANc0rYndXIDc9kGqDHY2fBuM27AQ","q":"utkm2HtI5Y9z-_5e7g5e2Df4TjI0CFAU_MoAr-XTw7liAQlbIThR5_znjwr-rCf8pGCfNJsechKybLNNWE3P4toeBiy3N4fvhRoWQnH-c8q6yJNCQN6yOySVvc2i6YUdUT9HclafKKsLr0pOvWXnUtRcBzAkTLcg3Hvnv8CYgBdCeLBPtAMNINRxSAsWHlDoQk1jTY586oe-AZ_Hpsy4r9Wrfcc76wpPq4WxEe-u352sgmr_pOMg4n-DsFDm9dq6b9mntsVol6PZV1X7KP0MelP_3wlRIIaldORCqdPlxFdhV282SL1yXIVuzFNenv60M1BH3lUzWHgJoANyTGU30Q","qi":"M2pS_vCKq_ro6TZ-22rtjoXXWixrGpPoCNCye5ZodKCEZK1BzJloCpaF6t64cyDLZUaVK4KxF6V_tmhXPqXccc2BMh_N6tLT5_4zJkdIEcTJuvrc5ZToxg1J4n_OANAMce7_poN4qoB1N8Jn8tu-66V-tK7FpUwsD3GhbNp-vqaKm3Tp-zIB6NKl-TVfR1MWvy3s7I-ieXuLFhWmY8wFKLDHbzrPfJLVSU1YJHL77UKaNCQWAzRhWl4zL60EHDrkxjGKT_hZN0ovZbyd2kqM3xVLj9T3JIFdqP7n-rexM5y_K5dz5K93aQ34f60qSBza9cTOpBC14v_0OoOS0Xh8nA"} -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: fmt 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Check code formatting 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: "14" 15 | 16 | - name: Install yarn 17 | run: npm i -g yarn 18 | 19 | - name: Install dependencies 20 | run: yarn 21 | 22 | - name: Check fmt 23 | run: yarn fmt:check 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | node_modules 3 | *.log 4 | .vscode -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## `1.0.0-0` 4 | 5 | - Initial Beta Release 6 | - UI refresh 7 | - Faster pages 8 | - Better overviewing experience for collectibles 9 | - Include the Orbit protocol explorer in the UI 10 | - Verto ID 11 | - Collections 12 | - ArConnect support 13 | - Invite system 14 | - Library refactor 15 | - Caching 16 | 17 | ## `1.0.0-1` 18 | 19 | - Allow minting new collectibles on Verto 20 | - Layout fixes 21 | - Price details in the swap page 22 | - Make setup modal username fields more obvious 23 | - Verify usernames in setup modal 24 | - Warn users about tx mining delay for Verto ID to save 25 | - Fix space erroring on not found tokens 26 | - Add changelogs 27 | - Full beta release to the public 28 | - Refactor workspace 29 | 30 | ## `1.0.0-2` 31 | 32 | - Support markdown descriptions for collectibles and bios 33 | - Fixup ID setup not updating addresses 34 | - Fixup collection contract owner bug 35 | - Fix error on swap when the initial token does not have a price 36 | - Fix 404 errors incorrectly displayed on Orbit 37 | - Custom 500 error page 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 th8ta LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Verto logo (dark version) 4 | 5 | 6 |

Verto

7 | 8 |

9 | A decentralized token exchange protocol on Arweave 10 |

11 | 12 | 17 |

18 | 19 | - Website: [verto.exchange](https://verto.exchange) 20 | - Supported tokens: [verto.exchange/space](https://verto.exchange/space) 21 | - Docs: [docs.verto.exchange](http://docs.verto.exchange/) 22 | - Twitter: [@vertoexchange](https://twitter.com/vertoexchange) 23 | - Discord: [chat](https://verto.exchange/chat) 24 | 25 | ## Building the UI 26 | 27 | The UI is written in React/Next.js. Before building it, you will need to install [Node](https://nodejs.org/), then [Yarn](https://yarnpkg.com/). After that you can install the dependencies from the repository root: 28 | 29 | ```sh 30 | yarn install 31 | ``` 32 | 33 | ...then build from source: 34 | 35 | ```sh 36 | yarn build 37 | ``` 38 | 39 | ## Special Thanks 40 | 41 | - [Sam Williams](https://twitter.com/samecwilliams) 42 | - [Cedrik Boudreau](https://github.com/cedriking) 43 | - [Aidan O'Kelly](https://github.com/aidanok) 44 | - [John Letey](https://github.com/johnletey) 45 | 46 | ## License 47 | 48 | See the license [here](./LICENSE). 49 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.module.sass" { 2 | const content: { [className: string]: string }; 3 | export default content; 4 | } 5 | 6 | declare module "*.md" { 7 | const content: string; 8 | export default content; 9 | } 10 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withImages = require("next-images"); 2 | 3 | module.exports = { 4 | ...withImages(), 5 | future: { 6 | webpack5: true, 7 | }, 8 | webpack: (config, { dev, isServer }) => { 9 | if (!dev && !isServer) { 10 | Object.assign(config.resolve.alias, { 11 | react: "preact/compat", 12 | "react-dom": "preact/compat", 13 | }); 14 | } 15 | 16 | Object.assign(config.externals, { 17 | fs: "fs", 18 | }); 19 | Object.assign(config.module, { 20 | rules: config.module.rules.concat([ 21 | { 22 | test: /\.md$/, 23 | loader: "emit-file-loader", 24 | options: { 25 | name: "dist/[path][name].[ext]", 26 | }, 27 | }, 28 | { 29 | test: /\.md$/, 30 | loader: "raw-loader", 31 | }, 32 | ]), 33 | }); 34 | 35 | return config; 36 | }, 37 | async headers() { 38 | return [ 39 | { 40 | source: "/(.*)", 41 | headers: [ 42 | { 43 | key: "X-Community-Contract", 44 | value: "usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A", 45 | }, 46 | ], 47 | }, 48 | ]; 49 | }, 50 | async redirects() { 51 | return [ 52 | { 53 | source: "/u/:user", 54 | destination: "/@:user", 55 | permanent: true, 56 | }, 57 | { 58 | source: "/user/:user", 59 | destination: "/@:user", 60 | permanent: true, 61 | }, 62 | { 63 | source: "/roadmap", 64 | destination: 65 | "https://www.notion.so/verto/48f99dfb0ba744f5b5257185f53ad7c4", 66 | permanent: true, 67 | }, 68 | { 69 | source: "/feedback", 70 | destination: "https://forms.gle/m3TJDfg1HRpV994Y8", 71 | permanent: true, 72 | }, 73 | ]; 74 | }, 75 | async rewrites() { 76 | return [ 77 | { 78 | source: "/(@):user", 79 | destination: "/user/:user", 80 | }, 81 | { 82 | source: "/(@):user/trades", 83 | destination: "/user/:user/trades", 84 | }, 85 | { 86 | source: "/(@):user/transactions", 87 | destination: "/user/:user/transactions", 88 | }, 89 | { 90 | source: "/(@):user/creations", 91 | destination: "/user/:user/creations", 92 | }, 93 | { 94 | source: "/(@):user/owns", 95 | destination: "/user/:user/owns", 96 | }, 97 | { 98 | source: "/i/win", 99 | destination: 100 | "https://raw.githubusercontent.com/useverto/trading-post/master/install/windows.ps1", 101 | }, 102 | { 103 | source: "/i/mac", 104 | destination: 105 | "https://raw.githubusercontent.com/useverto/trading-post/master/install/mac.sh", 106 | }, 107 | { 108 | source: "/i/linux", 109 | destination: 110 | "https://raw.githubusercontent.com/useverto/trading-post/master/install/linux.sh", 111 | }, 112 | { 113 | source: "/chat", 114 | destination: "https://discord.gg/sNgJkMg", 115 | }, 116 | { 117 | source: "/link", 118 | destination: 119 | "https://arweave.net/CodqSDWXY5CALyMf9oFLCtTDRYdW4lV9X9O7j-73g1U", 120 | }, 121 | ]; 122 | }, 123 | }; 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "verto", 4 | "version": "1.0.0-2", 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "fmt": "prettier --write .", 9 | "fmt:check": "prettier --check ." 10 | }, 11 | "gitHooks": { 12 | "pre-commit": "prettier --write . && git add -A" 13 | }, 14 | "dependencies": { 15 | "@iconicicons/react": "^1.5.0", 16 | "@primer/octicons-react": "^13.0.0", 17 | "@ramp-network/ramp-instant-sdk": "^2.5.0", 18 | "@verto/js": "^0.0.0-alpha.19", 19 | "@verto/ui": "^2.1.24-alpha", 20 | "ar-gql": "^0.0.6", 21 | "arconnect": "^0.4.2", 22 | "ardb": "^1.0.8", 23 | "arverify": "^0.0.11", 24 | "arweave": "^1.10.13", 25 | "axios": "^0.21.1", 26 | "capture-website": "^1.4.0", 27 | "chart.js": "^2.9.4", 28 | "chrome-aws-lambda": "^8.0.2", 29 | "copy-to-clipboard": "^3.3.1", 30 | "dayjs": "^1.10.4", 31 | "fathom-client": "^3.1.0", 32 | "file-loader": "^6.2.0", 33 | "framer-motion": "^4.1.3", 34 | "marked": "^3.0.0", 35 | "moment": "^2.29.1", 36 | "next": "10.2.3", 37 | "nprogress": "^0.2.0", 38 | "preact": "^10.5.13", 39 | "raw-loader": "^4.0.2", 40 | "react": "^17.0.2", 41 | "react-chartjs-2": "^2.11.1", 42 | "react-dom": "^17.0.2", 43 | "react-media-hook": "^0.4.9", 44 | "react-redux": "^7.2.4", 45 | "redux": "^4.1.0", 46 | "semver": "^7.3.5", 47 | "smartweave": "^0.4.31", 48 | "social-username-url": "^0.0.3", 49 | "swr": "^0.5.6", 50 | "typed.js": "^2.0.12", 51 | "use-arconnect": "^1.0.1" 52 | }, 53 | "devDependencies": { 54 | "@types/marked": "^2.0.4", 55 | "@types/nprogress": "^0.2.0", 56 | "@types/react": "^17.0.3", 57 | "@types/react-redux": "^7.1.16", 58 | "@types/redux": "^3.6.0", 59 | "@types/semver": "^7.3.8", 60 | "next-images": "^1.7.0", 61 | "prettier": "^2.2.1", 62 | "sass": "^1.32.8", 63 | "typescript": "^4.2.3", 64 | "yorkie": "^2.0.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/arweave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/useverto/interface/75fcd1eea0321280ee497180291e420154539768/public/arweave.png -------------------------------------------------------------------------------- /public/icons/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/useverto/interface/75fcd1eea0321280ee497180291e420154539768/public/icons/1024.png -------------------------------------------------------------------------------- /public/icons/192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/useverto/interface/75fcd1eea0321280ee497180291e420154539768/public/icons/192.png -------------------------------------------------------------------------------- /public/icons/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/useverto/interface/75fcd1eea0321280ee497180291e420154539768/public/icons/512.png -------------------------------------------------------------------------------- /public/logo_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/logo_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Verto", 3 | "name": "The Verto Protocol", 4 | "description": "A decentralized token exchange protocol on Arweave", 5 | "icons": [ 6 | { 7 | "src": "/icons/192.png", 8 | "type": "image/png", 9 | "sizes": "192x192" 10 | }, 11 | { 12 | "src": "/icons/512.png", 13 | "type": "image/png", 14 | "sizes": "512x512" 15 | }, 16 | { 17 | "src": "/icons/1024.png", 18 | "type": "image/png", 19 | "sizes": "1024x1024" 20 | } 21 | ], 22 | "scope": "/", 23 | "start_url": "/?source=pwa", 24 | "display": "minimal-ui", 25 | "theme_color": "#000000" 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Balance.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { balanceHistory } from "../utils/arweave"; 3 | import { Line } from "react-chartjs-2"; 4 | import { formatAddress } from "../utils/format"; 5 | import { GraphDataConfig, GraphOptions } from "../utils/graph"; 6 | import { motion, AnimatePresence } from "framer-motion"; 7 | import { client } from "../utils/arweave"; 8 | import { ClipboardIcon } from "@iconicicons/react"; 9 | import { Tooltip, useTheme, useToasts } from "@verto/ui"; 10 | import { useCountUp } from "../utils/animations"; 11 | import { useSelector } from "react-redux"; 12 | import { RootState } from "../store/reducers"; 13 | import copy from "copy-to-clipboard"; 14 | import styles from "../styles/components/Balance.module.sass"; 15 | 16 | const Balance = () => { 17 | const [history, setHistory] = useState<{ [date: string]: number }>(); 18 | const [balance, setBalance] = useState(0); 19 | const [historicalBalance, setHistorycalBalance] = useState<{ 20 | date: string; 21 | val: number; 22 | }>(); 23 | const { setToast } = useToasts(); 24 | const decimals = 5; 25 | const animatedBalance = useCountUp({ end: balance, decimals }); 26 | const address = useSelector((state: RootState) => state.addressReducer); 27 | const theme = useTheme(); 28 | 29 | useEffect(() => { 30 | if (!address) return; 31 | loadData(); 32 | }, [address]); 33 | 34 | async function loadData() { 35 | setBalance( 36 | parseFloat( 37 | client.ar.winstonToAr(await client.wallets.getBalance(address)) ?? "0" 38 | ) 39 | ); 40 | setHistory(await balanceHistory(address)); 41 | } 42 | 43 | return ( 44 |
45 |
46 |

{historicalBalance?.date || "Total balance"}

47 |

48 | {(historicalBalance?.val || animatedBalance).toLocaleString( 49 | undefined, 50 | { maximumFractionDigits: decimals } 51 | )} 52 | AR 53 |

54 |

55 | Wallet: {formatAddress(address ?? "...")} 56 | 57 | 69 | 70 |

71 |
72 | 73 | {history && ( 74 | setHistorycalBalance(undefined)} 81 | > 82 | 116 | 117 | )} 118 | 119 |
120 | ); 121 | }; 122 | 123 | export default Balance; 124 | -------------------------------------------------------------------------------- /src/components/ChangelogModal.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Modal, useTheme } from "@verto/ui"; 2 | import { lastViewedChangelog } from "../utils/storage_names"; 3 | import { useEffect, useRef, useState } from "react"; 4 | import marked from "marked"; 5 | import changelog from "../../CHANGELOG.md"; 6 | import pkg from "../../package.json"; 7 | import styles from "../styles/components/ChangelogModal.module.sass"; 8 | 9 | const ChangelogModal = (props) => { 10 | const changelogRef = useRef(); 11 | const theme = useTheme(); 12 | const [formattedChangelog, setFormattedChangelog] = useState(""); 13 | 14 | useEffect(() => { 15 | if (!changelogRef.current || formattedChangelog === "") return; 16 | changelogRef.current.scrollTop = changelogRef.current.scrollHeight; 17 | }, [changelogRef.current, props.open, formattedChangelog]); 18 | 19 | useEffect(() => setFormattedChangelog(marked(changelog)), [changelog]); 20 | 21 | return ( 22 | 23 | v{pkg.version} Changelog 24 | 25 |
34 | 44 |
45 |
46 | ); 47 | }; 48 | 49 | export default ChangelogModal; 50 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Spacer, useTheme } from "@verto/ui"; 2 | import styles from "../styles/components/Footer.module.sass"; 3 | 4 | const Footer = () => { 5 | const theme = useTheme(); 6 | 7 | return ( 8 |
11 |

12 | Verto 13 |

14 |
15 | 20 | Chat 21 | 22 | 27 | Code 28 | 29 | 30 | Arweave 31 | 32 |
33 |
34 | ); 35 | }; 36 | 37 | export default Footer; 38 | -------------------------------------------------------------------------------- /src/components/ListingModal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Modal, 3 | Input, 4 | useInput, 5 | Spacer, 6 | Button, 7 | useToasts, 8 | useModal, 9 | } from "@verto/ui"; 10 | import { useEffect, useState } from "react"; 11 | import { interactWrite, readContract } from "smartweave"; 12 | import { client, COMMUNITY_CONTRACT, isAddress } from "../utils/arweave"; 13 | import CollectionModal from "./CollectionModal"; 14 | import MintCollectible from "./MintCollectible"; 15 | import styles from "../styles/components/ListingModal.module.sass"; 16 | 17 | export default function ListingModal(props: Props) { 18 | const contractIDInput = useInput(); 19 | const [tokenName, setTokenName] = useState(""); 20 | // TODO: custom layout 21 | const [selectedLayout, setSelectedLayout] = useState<"community" | "art">( 22 | "community" 23 | ); 24 | const [loading, setLoading] = useState(false); 25 | 26 | useEffect(() => { 27 | contractIDInput.setStatus(undefined); 28 | contractIDInput.setState(""); 29 | setTokenName(""); 30 | setSelectedLayout("community"); 31 | }, [props.open]); 32 | 33 | const [disabled, setDisabled] = useState(false); 34 | 35 | useEffect(() => { 36 | (async () => { 37 | if (!isAddress(contractIDInput.state)) return setTokenName(""); 38 | 39 | try { 40 | const currentState = await readContract(client, contractIDInput.state); 41 | setTokenName(currentState.ticker ?? ""); 42 | 43 | if ( 44 | !currentState.ticker || 45 | !currentState.name || 46 | !currentState.balances 47 | ) { 48 | setToast({ 49 | description: "Not a token contract", 50 | type: "error", 51 | duration: 4500, 52 | }); 53 | setDisabled(true); 54 | return; 55 | } 56 | 57 | setDisabled(false); 58 | 59 | if (currentState.roles || currentState.votes) 60 | setSelectedLayout("community"); 61 | else setSelectedLayout("art"); 62 | } catch { 63 | setToast({ 64 | description: "Could not read contract", 65 | type: "error", 66 | duration: 2750, 67 | }); 68 | setDisabled(true); 69 | } 70 | })(); 71 | }, [contractIDInput.state]); 72 | 73 | const { setToast } = useToasts(); 74 | 75 | async function listToken() { 76 | if (!isAddress(contractIDInput.state)) 77 | return contractIDInput.setStatus("error"); 78 | 79 | setLoading(true); 80 | try { 81 | await interactWrite(client, "use_wallet", COMMUNITY_CONTRACT, { 82 | function: "list", 83 | id: contractIDInput.state, 84 | type: selectedLayout, 85 | }); 86 | 87 | setToast({ 88 | description: "Token is now listed", 89 | type: "success", 90 | duration: 4500, 91 | }); 92 | setToast({ 93 | description: "Expect a delay before seeing your token listed", 94 | type: "warning", 95 | duration: 3850, 96 | }); 97 | props.onClose(); 98 | } catch { 99 | setToast({ 100 | description: "Error listing token", 101 | type: "error", 102 | duration: 4500, 103 | }); 104 | } 105 | setLoading(false); 106 | } 107 | 108 | const collectionModal = useModal(); 109 | const mintModal = useModal(); 110 | 111 | return ( 112 | <> 113 | 114 | List new token 115 | 116 | Token contract ID {tokenName !== "" && `(${tokenName})`} 119 | } 120 | className={styles.Input} 121 | placeholder="Contract ID" 122 | type="text" 123 | {...contractIDInput.bindings} 124 | /> 125 | 126 | Choose token layout 127 |
128 |
setSelectedLayout("community")} 135 | > 136 |
137 | 138 |
139 | Community 140 | 141 | Recommended for community PSTs 142 | 143 |
144 |
setSelectedLayout("art")} 151 | > 152 |
153 | 154 |
155 | Art {"&"} collectible 156 | 157 | Recommended for arts and other collectibles 158 | 159 |
160 |
161 | 162 | 171 | 172 |

173 | { 175 | collectionModal.setState(true); 176 | props.onClose(); 177 | }} 178 | > 179 | Create collection 180 | {" "} 181 | or{" "} 182 | { 184 | mintModal.setState(true); 185 | props.onClose(); 186 | }} 187 | > 188 | Mint collectible 189 | 190 |

191 |
192 |
193 | 194 | 195 | 196 | ); 197 | } 198 | 199 | interface Props { 200 | open: boolean; 201 | onClose: () => void; 202 | } 203 | 204 | const CommunitySkeleton = () => ( 205 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 231 | 232 | 233 | 234 | ); 235 | 236 | const ArtSkeleton = () => ( 237 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | ); 252 | 253 | // TODO 254 | const CustomSkeleton = () => ( 255 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 281 | 282 | 283 | 284 | ); 285 | -------------------------------------------------------------------------------- /src/components/Metas.tsx: -------------------------------------------------------------------------------- 1 | import { ROOT_URL } from "../utils/arweave"; 2 | 3 | const Metas = ({ title, image, subtitle, localImage }: MetaProps) => ( 4 | <> 5 | 6 | 10 | 11 | 12 | 13 | 14 | 25 | 29 | 30 | 34 | 35 | 39 | 50 | 54 | 55 | ); 56 | 57 | export default Metas; 58 | 59 | interface MetaProps { 60 | title: string; 61 | image?: string; 62 | subtitle?: string; 63 | localImage?: string; 64 | } 65 | -------------------------------------------------------------------------------- /src/components/MintCollectible.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronLeftIcon, ChevronRightIcon } from "@iconicicons/react"; 2 | import { 3 | Modal, 4 | useInput, 5 | Input, 6 | Spacer, 7 | Button, 8 | useCheckbox, 9 | Checkbox, 10 | useToasts, 11 | } from "@verto/ui"; 12 | import { useEffect, useState } from "react"; 13 | import { useSelector } from "react-redux"; 14 | import { RootState } from "../store/reducers"; 15 | import { interactWrite } from "smartweave"; 16 | import { 17 | client, 18 | COLLECTIBLE_CONTRACT_SRC, 19 | COMMUNITY_CONTRACT, 20 | } from "../utils/arweave"; 21 | import styles from "../styles/components/SetupModal.module.sass"; 22 | 23 | const MintCollectible = (props) => { 24 | const nameInput = useInput(); 25 | const [description, setDescription] = useState(""); 26 | const [file, setFile] = useState(); 27 | const allowMintingCheckbox = useCheckbox(true); 28 | const [fileContent, setFileContent] = useState(""); 29 | 30 | const [allowAdvanced, setAllowAdvanced] = useState(false); 31 | const [advancedView, setAdvancedView] = useState(false); 32 | 33 | const tickerInput = useInput("VRTNFT"); 34 | const titleInput = useInput(""); 35 | const [balancesObj, setBalancesObj] = useState("{}"); 36 | 37 | // reset inputs 38 | useEffect(() => { 39 | nameInput.reset(); 40 | setDescription(""); 41 | setFile(undefined); 42 | allowMintingCheckbox.reset(); 43 | setFileContent(""); 44 | tickerInput.reset(); 45 | titleInput.reset(); 46 | setBalancesObj("{}"); 47 | setAllowAdvanced(false); 48 | setAdvancedView(false); 49 | }, [props.open]); 50 | 51 | useEffect(() => { 52 | if (!file) return; 53 | const reader = new FileReader(); 54 | 55 | reader.readAsDataURL(file); 56 | reader.onload = (e) => setFileContent(e.target.result as string); 57 | }, [file]); 58 | 59 | useEffect(() => { 60 | if (allowAdvanced) return; 61 | titleInput.setState(nameInput.state); 62 | }, [nameInput.state]); 63 | 64 | useEffect(() => { 65 | if (!advancedView) return; 66 | setAllowAdvanced(true); 67 | }, [advancedView]); 68 | 69 | const address = useSelector((state: RootState) => state.addressReducer); 70 | 71 | useEffect(() => { 72 | if (!address || allowAdvanced) return; 73 | setBalancesObj( 74 | JSON.stringify( 75 | { 76 | [address]: 1, 77 | }, 78 | null, 79 | 2 80 | ) 81 | ); 82 | }, [address, advancedView, props.open]); 83 | 84 | const [loading, setLoading] = useState(false); 85 | const { setToast } = useToasts(); 86 | 87 | async function mint() { 88 | // checks 89 | if (nameInput.state === "") return; 90 | 91 | if (description === "") 92 | return setToast({ 93 | description: "Please provide a description", 94 | type: "error", 95 | duration: 3000, 96 | }); 97 | 98 | if (!file) 99 | return setToast({ 100 | description: "Please upload a file", 101 | type: "error", 102 | duration: 3000, 103 | }); 104 | 105 | if ( 106 | tickerInput.state === "" || 107 | titleInput.state === "" || 108 | !tickerInput.state.match(/^[A-Z0-9]+$/) 109 | ) 110 | return setAdvancedView(true); 111 | 112 | if (balancesObj === "") { 113 | setAdvancedView(true); 114 | setToast({ 115 | description: "Please add a balances object", 116 | type: "error", 117 | duration: 3000, 118 | }); 119 | return; 120 | } 121 | 122 | try { 123 | const balances = JSON.parse(balancesObj); 124 | 125 | if (Object.keys(balances).length < 1) { 126 | setAdvancedView(true); 127 | setToast({ 128 | description: "Please add at least on holder to the balances", 129 | type: "error", 130 | duration: 3450, 131 | }); 132 | return; 133 | } 134 | } catch { 135 | setAdvancedView(true); 136 | setToast({ 137 | description: "Invalid JSON in balances", 138 | type: "error", 139 | duration: 3400, 140 | }); 141 | return; 142 | } 143 | 144 | // inputs are verified, create transaction 145 | 146 | setLoading(true); 147 | 148 | let id = ""; 149 | let data: ArrayBuffer; 150 | 151 | const loadFile = (f: File) => 152 | new Promise>((resolve, reject) => { 153 | const reader = new FileReader(); 154 | 155 | reader.readAsArrayBuffer(f); 156 | reader.onload = (e) => resolve(e); 157 | reader.onerror = (e) => reject(e); 158 | reader.onabort = (e) => reject(e); 159 | }); 160 | 161 | try { 162 | data = (await loadFile(file)).target.result as ArrayBuffer; 163 | } catch { 164 | setToast({ 165 | description: "Could not load file", 166 | type: "error", 167 | duration: 3000, 168 | }); 169 | setLoading(false); 170 | return; 171 | } 172 | 173 | try { 174 | const contractTx = await client.createTransaction({ data }); 175 | 176 | contractTx.addTag("Content-Type", file.type); 177 | contractTx.addTag("Exchange", "Verto"); 178 | contractTx.addTag("Action", "marketplace/create"); 179 | contractTx.addTag("App-Name", "SmartWeaveContract"); 180 | contractTx.addTag("App-Version", "0.3.0"); 181 | contractTx.addTag("Contract-Src", COLLECTIBLE_CONTRACT_SRC); 182 | contractTx.addTag( 183 | "Init-State", 184 | JSON.stringify({ 185 | name: nameInput.state, 186 | ticker: tickerInput.state, 187 | title: titleInput.state, 188 | description, 189 | owner: address, 190 | allowMinting: allowMintingCheckbox.state, 191 | balances: JSON.parse(balancesObj), 192 | contentType: file.type, 193 | createdAt: Math.floor(Date.now() / 1000).toString(), 194 | invocations: [], 195 | foreignCalls: [], 196 | }) 197 | ); 198 | 199 | await client.transactions.sign(contractTx); 200 | 201 | let uploader = await client.transactions.getUploader(contractTx); 202 | 203 | while (!uploader.isComplete) { 204 | await uploader.uploadChunk(); 205 | } 206 | 207 | id = contractTx.id; 208 | } catch { 209 | setToast({ 210 | description: "Could not mint token", 211 | type: "error", 212 | duration: 3000, 213 | }); 214 | setLoading(false); 215 | return; 216 | } 217 | 218 | try { 219 | await interactWrite(client, "use_wallet", COMMUNITY_CONTRACT, { 220 | function: "list", 221 | id, 222 | type: "art", 223 | }); 224 | 225 | setToast({ 226 | description: "Token minted and listed", 227 | type: "success", 228 | duration: 4500, 229 | }); 230 | setToast({ 231 | description: 232 | "Expect a delay before seeing your token minted and listed", 233 | type: "warning", 234 | duration: 3850, 235 | }); 236 | props.onClose(); 237 | } catch { 238 | setToast({ 239 | description: "Could not list token", 240 | type: "error", 241 | duration: 3000, 242 | }); 243 | } 244 | 245 | setLoading(false); 246 | } 247 | 248 | return ( 249 | 250 | Mint a new collectible 251 | 252 | {(!advancedView && ( 253 | <> 254 | 261 | 262 |

Description

263 |
264 | 270 |
271 | 272 |
273 |

274 | {(file?.type.match(/^image\//) && ( 275 | art 276 | )) || 277 | ((file?.type.match(/^video\//) || 278 | file?.type.match(/^audio\//)) && ( 279 | 286 | ))} 287 | {file?.name || "Drag & drop or click to add a file"} 288 |

289 | setFile(e.target.files?.[0])} 292 | /> 293 |
294 | 295 |
302 | 303 | Allow minting new tokens 304 | 305 | setAdvancedView(true)} 308 | > 309 | Advanced 310 | 311 | 312 |
313 | 314 | 322 | 323 | )) || ( 324 | <> 325 | 332 | 333 | 340 | 341 |

Balances object

342 |
343 | 350 |
351 | 352 | setAdvancedView(false)} 355 | > 356 | 357 | Back 358 | 359 | 360 | )} 361 |
362 |
363 | ); 364 | }; 365 | 366 | export default MintCollectible; 367 | -------------------------------------------------------------------------------- /src/components/PSTSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { AnimatePresence, motion } from "framer-motion"; 3 | import { useRouter } from "next/router"; 4 | import { CACHE_URL } from "../utils/arweave"; 5 | import axios from "axios"; 6 | import styles from "../styles/components/PSTSwitcher.module.sass"; 7 | 8 | const PSTSwitcher = () => { 9 | const [firstLoad, setFirstLoad] = useState(true); 10 | const [images, setImages] = useState({ 11 | img1: { 12 | id: "", 13 | logo: "", 14 | }, 15 | img2: { 16 | id: "", 17 | logo: "", 18 | }, 19 | img3: { 20 | id: "", 21 | logo: "", 22 | }, 23 | img4: { 24 | id: "", 25 | logo: "", 26 | }, 27 | }); 28 | const router = useRouter(); 29 | 30 | const fetch = async (index: number): Promise => { 31 | const { data: res } = await axios.get( 32 | `${CACHE_URL}/site/communities/random` 33 | ); 34 | 35 | let imgs: typeof images; 36 | setImages((val) => { 37 | imgs = val; 38 | return val; 39 | }); 40 | 41 | if (imgs) 42 | for (const value of Object.values(imgs)) { 43 | // @ts-ignore 44 | if (value.id === res[index].id) return false; 45 | } 46 | 47 | try { 48 | setImages((val) => ({ 49 | ...val, 50 | [`img${index + 1}`]: { 51 | id: res[index].id, 52 | logo: res[index].logo, 53 | }, 54 | })); 55 | 56 | return true; 57 | } catch { 58 | return false; 59 | } 60 | }; 61 | 62 | const fetchImages = async () => { 63 | let firstLoad: boolean; 64 | setFirstLoad((val) => { 65 | firstLoad = val; 66 | return val; 67 | }); 68 | 69 | if (firstLoad) { 70 | for (let i = 0; i < 4; i++) { 71 | let res = await fetch(i); 72 | while (!res) res = await fetch(i); 73 | } 74 | setFirstLoad(false); 75 | } else { 76 | let index = Math.floor(Math.random() * 4); 77 | let res = await fetch(index); 78 | while (!res) res = await fetch(index); 79 | } 80 | }; 81 | 82 | useEffect(() => { 83 | const main = async () => { 84 | await fetchImages(); 85 | setTimeout(main, 2500); 86 | }; 87 | main(); 88 | }, []); 89 | 90 | return ( 91 |
92 | 93 | {images.img1.logo !== "" && ( 94 | router.push(`space/${images.img1.id}`)} 103 | > 104 | pst 110 | 111 | )} 112 | {images.img2.logo !== "" && ( 113 | router.push(`space/${images.img2.id}`)} 137 | > 138 | pst 144 | 145 | )} 146 | {images.img3.logo !== "" && ( 147 | router.push(`space/${images.img3.id}`)} 156 | > 157 | pst 163 | 164 | )} 165 | {images.img4.logo !== "" && ( 166 | router.push(`space/${images.img4.id}`)} 180 | > 181 | pst 187 | 188 | )} 189 | 190 |
191 | ); 192 | }; 193 | 194 | export default PSTSwitcher; 195 | -------------------------------------------------------------------------------- /src/components/Search.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence, motion } from "framer-motion"; 2 | import { opacityAnimation } from "../utils/animations"; 3 | import { MenuIcon, SearchIcon, ShareIcon } from "@iconicicons/react"; 4 | import { useState, useEffect } from "react"; 5 | import { useTheme, Spacer, generateAvatarGradient } from "@verto/ui"; 6 | import { CACHE_URL } from "../utils/arweave"; 7 | import { useRouter } from "next/router"; 8 | import axios from "axios"; 9 | import Link from "next/link"; 10 | import searchInputStyles from "../styles/components/SearchInput.module.sass"; 11 | import styles from "../styles/components/Search.module.sass"; 12 | 13 | export default function Search({ open, setOpen }) { 14 | const [query, setQuery] = useState(""); 15 | const [results, setResults] = useState([]); 16 | const theme = useTheme(); 17 | const router = useRouter(); 18 | 19 | useEffect(() => { 20 | if (router.query.q && router.query.q !== "") { 21 | setQuery(router.query.q.toString()); 22 | setOpen(true); 23 | } else setQuery(""); 24 | }, [open, router.query]); 25 | 26 | const [page, setPage] = useState(0); 27 | const [noMore, setNoMore] = useState(false); 28 | 29 | useEffect(() => { 30 | setPage(0); 31 | setNoMore(false); 32 | loadMore(true); 33 | }, [query]); 34 | 35 | async function loadMore(initial = false) { 36 | if (query === "") return setResults([]); 37 | 38 | let { data } = await axios.get( 39 | `${CACHE_URL}/site/search/${query}/${initial ? 0 : page}` 40 | ); 41 | 42 | if (data.length === 0) return setNoMore(true); 43 | 44 | data = data.map((val) => ({ 45 | ...val, 46 | image: (val.image && `https://arweave.net/${val.image}`) || undefined, 47 | gradient: 48 | (!val.image && 49 | val.type === "user" && 50 | generateAvatarGradient(val.username || val.usertag || "")) || 51 | undefined, 52 | })); 53 | 54 | setPage((val) => val + 1); 55 | setResults((val) => { 56 | if (initial) return data; 57 | return [...val, ...data]; 58 | }); 59 | } 60 | 61 | return ( 62 | 63 | {open && ( 64 | 65 |
setOpen(false)} /> 66 |
67 |
68 |

74 | Search for something... 75 |

76 | 77 | setQuery(e.target.value)} 81 | autoFocus 82 | /> 83 |
84 | 85 | {query !== "" && ( 86 | 94 | 95 | {results.map((item, i) => ( 96 | 104 | setOpen(false)} 113 | > 114 |
115 | {(item.type !== "collection" && 116 | (item.image || item.type !== "user") && ( 117 | {item.name} 128 | )) || 129 | (item.type === "user" && 130 | !item.image && 131 | item.gradient && ( 132 |
138 | 139 | {(item.username || "") 140 | .charAt(0) 141 | .toUpperCase()} 142 | 143 |
144 | )) || ( 145 | 146 | )} 147 |
148 |

{item.name}

149 |

154 | {item.type === "user" 155 | ? `@${item.username}` 156 | : item.ticker ?? "Collection"} 157 |

158 |
159 |
160 | 161 |
162 | 163 | ))} 164 |
165 | 166 | {results.length === 0 && query !== "" && ( 167 | 168 | No results found. 169 | 170 | )} 171 | 172 | {!noMore && results.length > 0 && ( 173 | <> 174 | 175 |
loadMore()} 178 | > 179 | See more 180 |
181 | 182 | 183 | )} 184 |
185 | )} 186 |
187 |
188 | 189 | )} 190 | 191 | ); 192 | } 193 | 194 | export function useSearch() { 195 | const [open, setOpen] = useState(false); 196 | 197 | return { open, setOpen }; 198 | } 199 | -------------------------------------------------------------------------------- /src/components/icons/Facebook.tsx: -------------------------------------------------------------------------------- 1 | const Facebook = () => ( 2 | 9 | 16 | 17 | ); 18 | 19 | export default Facebook; 20 | -------------------------------------------------------------------------------- /src/components/icons/Github.tsx: -------------------------------------------------------------------------------- 1 | const Twitter = () => ( 2 | 9 | 16 | 17 | ); 18 | 19 | export default Twitter; 20 | -------------------------------------------------------------------------------- /src/components/icons/Instagram.tsx: -------------------------------------------------------------------------------- 1 | const Instagram = () => ( 2 | 9 | 10 | 17 | 24 | 25 | 26 | ); 27 | 28 | export default Instagram; 29 | -------------------------------------------------------------------------------- /src/components/icons/Twitter.tsx: -------------------------------------------------------------------------------- 1 | const Twitter = () => ( 2 | 9 | 16 | 17 | ); 18 | 19 | export default Twitter; 20 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { SearchIcon } from "@iconicicons/react"; 2 | import { Page, Spacer } from "@verto/ui"; 3 | import Search, { useSearch } from "../components/Search"; 4 | import Head from "next/head"; 5 | import Metas from "../components/Metas"; 6 | import searchInputStyles from "../styles/components/SearchInput.module.sass"; 7 | import styles from "../styles/views/404.module.sass"; 8 | 9 | const NotFound = () => { 10 | const search = useSearch(); 11 | 12 | return ( 13 | 14 | 15 | 404 - Page Not Found 16 | 17 | 18 |
19 |

404

20 |

Page Not Found

21 | 22 |
search.setOpen(true)} 25 | > 26 |

Maybe try searching...

27 | 28 |
29 |
30 | 31 |
32 | ); 33 | }; 34 | 35 | export default NotFound; 36 | -------------------------------------------------------------------------------- /src/pages/500.tsx: -------------------------------------------------------------------------------- 1 | import { Page, Spacer } from "@verto/ui"; 2 | import Head from "next/head"; 3 | import Metas from "../components/Metas"; 4 | import styles from "../styles/views/404.module.sass"; 5 | 6 | const ServerError = () => ( 7 | 8 | 9 | 500 - Internal Server Error 10 | 11 | 12 |
13 |

500

14 |

Internal Server Error

15 | 16 |

17 | Try getting help at our{" "} 18 | 19 | Discord 20 | 21 | . 22 |

23 |
24 |
25 | ); 26 | 27 | export default ServerError; 28 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | Modal, 4 | Spacer, 5 | useModal, 6 | useToasts, 7 | VertoProvider, 8 | useTheme, 9 | } from "@verto/ui"; 10 | import { useRouter } from "next/router"; 11 | import { useEffect, useState } from "react"; 12 | import { 13 | Provider as ReduxProvider, 14 | useDispatch, 15 | useSelector, 16 | } from "react-redux"; 17 | import { RootState } from "../store/reducers"; 18 | import { updateTheme } from "../store/actions"; 19 | import { DisplayTheme } from "@verto/ui/dist/types"; 20 | import { permissions } from "../utils/arconnect"; 21 | import { 22 | ignorePermissionWarning, 23 | lastViewedChangelog, 24 | theme as themeStorageName, 25 | } from "../utils/storage_names"; 26 | import { CACHE_URL } from "../utils/arweave"; 27 | import { gt, valid } from "semver"; 28 | import pkg from "../../package.json"; 29 | import store from "../store"; 30 | import Progress from "nprogress"; 31 | import Footer from "../components/Footer"; 32 | import Nav from "../components/Nav"; 33 | import Head from "next/head"; 34 | import ChangelogModal from "../components/ChangelogModal"; 35 | import axios from "axios"; 36 | import * as Fathom from "fathom-client"; 37 | import "../styles/global.sass"; 38 | import "../styles/progress.sass"; 39 | 40 | export default function App({ Component, pageProps }) { 41 | const router = useRouter(); 42 | const [scheme, setScheme] = useState<"dark" | "light">("light"); 43 | 44 | Progress.configure({ showSpinner: false }); 45 | 46 | useEffect(() => { 47 | router.events.on("routeChangeStart", () => Progress.start()); 48 | router.events.on("routeChangeComplete", () => Progress.done()); 49 | router.events.on("routeChangeError", () => Progress.done()); 50 | 51 | return () => { 52 | router.events.off("routeChangeStart", () => Progress.start()); 53 | router.events.off("routeChangeComplete", () => Progress.done()); 54 | router.events.off("routeChangeError", () => Progress.done()); 55 | }; 56 | }); 57 | 58 | useEffect(() => { 59 | // Initialize Fathom when the app loads 60 | // Example: yourdomain.com 61 | // - Do not include https:// 62 | // - This must be an exact match of your domain. 63 | // - If you're using www. for your domain, make sure you include that here. 64 | Fathom.load("NFVUFKXC", { 65 | includedDomains: ["verto.exchange", "www.verto.exchange"], 66 | }); 67 | 68 | function onRouteChangeComplete() { 69 | Fathom.trackPageview(); 70 | } 71 | // Record a pageview when route changes 72 | router.events.on("routeChangeComplete", onRouteChangeComplete); 73 | 74 | // Unassign event listener 75 | return () => { 76 | router.events.off("routeChangeComplete", onRouteChangeComplete); 77 | }; 78 | }, []); 79 | 80 | useEffect(() => { 81 | if (!window) return; 82 | const query = window.matchMedia("(prefers-color-scheme: dark)"); 83 | const updateScheme = (val) => setScheme(val.matches ? "dark" : "light"); 84 | 85 | updateScheme(query); 86 | query.addEventListener("change", updateScheme); 87 | 88 | return () => { 89 | query.removeEventListener("change", updateScheme); 90 | }; 91 | }, []); 92 | 93 | useEffect(() => { 94 | if (!window.arweaveWallet) 95 | window.addEventListener("arweaveWalletLoaded", checkLogin); 96 | else checkLogin(); 97 | 98 | return () => { 99 | window.removeEventListener("arweaveWalletLoaded", checkLogin); 100 | }; 101 | }, [router.asPath]); 102 | 103 | async function checkLogin() { 104 | const protectedRoutes = /\/(app|swap)/; 105 | const connected = (await window.arweaveWallet.getPermissions()).length > 0; 106 | 107 | if (router.asPath.match(protectedRoutes) && !connected) router.push("/"); 108 | } 109 | 110 | const permissionsModal = useModal(); 111 | 112 | useEffect(() => { 113 | window.addEventListener("arweaveWalletLoaded", checkPerms); 114 | 115 | return () => { 116 | window.removeEventListener("arweaveWalletLoaded", checkPerms); 117 | }; 118 | }); 119 | 120 | async function checkPerms() { 121 | const existingPerms = await window.arweaveWallet.getPermissions(); 122 | const ignore = localStorage.getItem(ignorePermissionWarning); 123 | 124 | if (ignore && JSON.parse(ignore).val) return; 125 | 126 | if (existingPerms.length === 0) return; 127 | for (const perm of permissions) { 128 | if (!existingPerms.includes(perm)) { 129 | permissionsModal.setState(true); 130 | break; 131 | } 132 | } 133 | } 134 | 135 | return ( 136 | 137 | 138 | 139 | 140 | 145 | 146 | 147 |