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 |
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 |
148 |
149 |
150 |
151 | Missing permissions
152 |
153 | A few permissions are missing. Some of them are essential for
154 | Verto to work. Please allow these to use Verto to it's full
155 | potential.
156 |
157 |
164 | {
166 | try {
167 | await window.arweaveWallet.connect(permissions, {
168 | name: "Verto",
169 | });
170 | } catch {}
171 | permissionsModal.setState(false);
172 | }}
173 | small
174 | >
175 | Allow
176 |
177 |
178 | {
182 | localStorage.setItem(
183 | ignorePermissionWarning,
184 | JSON.stringify({ val: true })
185 | );
186 | permissionsModal.setState(false);
187 | }}
188 | >
189 | Don't show again
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | );
199 | }
200 |
201 | const Theme = ({ children }) => {
202 | const theme = useSelector((state: RootState) => state.themeReducer);
203 | const dispatch = useDispatch();
204 | const [displayTheme, setDisplayTheme] = useState
("Light");
205 |
206 | useEffect(() => {
207 | let loadedTheme = localStorage.getItem(themeStorageName);
208 | if (!loadedTheme) return;
209 | if (!["Dark", "Light", "System"].includes(loadedTheme))
210 | loadedTheme = "Light";
211 | dispatch(updateTheme(loadedTheme as DisplayTheme));
212 | }, []);
213 |
214 | useEffect(() => {
215 | const query = window.matchMedia("(prefers-color-scheme: dark)");
216 | const updateScheme = (val) =>
217 | setDisplayTheme(val.matches ? "Dark" : "Light");
218 |
219 | localStorage.setItem(themeStorageName, theme);
220 |
221 | if (theme === "System") setDisplayTheme(query.matches ? "Dark" : "Light");
222 | else setDisplayTheme(theme);
223 |
224 | query.addEventListener("change", updateScheme);
225 |
226 | return () => {
227 | query.removeEventListener("change", updateScheme);
228 | };
229 | }, [theme]);
230 |
231 | return {children} ;
232 | };
233 |
234 | const StatusChecker = ({ children }) => {
235 | const { setToast } = useToasts();
236 |
237 | // check arweave.net
238 | useEffect(() => {
239 | (async () => {
240 | try {
241 | await axios({
242 | method: "GET",
243 | url: "https://arweave.net",
244 | timeout: 8000,
245 | });
246 | } catch {
247 | setToast({
248 | description: "The arweave.net gateway is down",
249 | type: "error",
250 | duration: 7000,
251 | });
252 | }
253 | })();
254 | }, []);
255 |
256 | // check cache
257 | useEffect(() => {
258 | (async () => {
259 | try {
260 | const { data } = await axios({
261 | method: "GET",
262 | url: `${CACHE_URL}/ping`,
263 | timeout: 8000,
264 | });
265 |
266 | if (data.connection !== 1)
267 | setToast({
268 | description: "The cache server is not connected to the DB",
269 | type: "error",
270 | duration: 7000,
271 | });
272 | } catch {
273 | setToast({
274 | description: "The cache server is down",
275 | type: "error",
276 | duration: 7000,
277 | });
278 | }
279 | })();
280 | }, []);
281 |
282 | return <>{children}>;
283 | };
284 |
285 | const Changelog = () => {
286 | const changelogModal = useModal();
287 | const address = useSelector((state: RootState) => state.addressReducer);
288 |
289 | useEffect(() => {
290 | if (!window || !address) return;
291 | const storedVersion = localStorage.getItem(lastViewedChangelog);
292 |
293 | if (!valid(storedVersion) || gt(pkg.version, storedVersion))
294 | changelogModal.setState(true);
295 | }, [address]);
296 |
297 | return ;
298 | };
299 |
--------------------------------------------------------------------------------
/src/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import Document, {
2 | Html,
3 | Head,
4 | Main,
5 | NextScript,
6 | DocumentContext,
7 | } from "next/document";
8 | import { ROOT_URL } from "../utils/arweave";
9 |
10 | class MyDocument extends Document {
11 | static async getInitialProps(ctx: DocumentContext) {
12 | const initialProps = await Document.getInitialProps(ctx);
13 |
14 | return initialProps;
15 | }
16 |
17 | render() {
18 | return (
19 |
20 |
21 |
22 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | );
83 | }
84 | }
85 |
86 | export default MyDocument;
87 |
--------------------------------------------------------------------------------
/src/pages/app.tsx:
--------------------------------------------------------------------------------
1 | import { BalanceInterface, UserInterface } from "@verto/js/dist/faces";
2 | import {
3 | Card,
4 | Modal,
5 | Page,
6 | Spacer,
7 | Tooltip,
8 | useModal,
9 | useTheme,
10 | useToasts,
11 | Avatar,
12 | Button,
13 | } from "@verto/ui";
14 | import { useEffect, useState } from "react";
15 | import { RootState } from "../store/reducers";
16 | import { useSelector } from "react-redux";
17 | import { AnimatePresence, motion } from "framer-motion";
18 | import {
19 | cardAnimation,
20 | cardListAnimation,
21 | opacityAnimation,
22 | } from "../utils/animations";
23 | import {
24 | PlusIcon,
25 | ChevronUpIcon,
26 | ChevronDownIcon,
27 | ArrowRightIcon,
28 | ChevronRightIcon,
29 | } from "@iconicicons/react";
30 | import {
31 | arPrice,
32 | CACHE_URL,
33 | isAddress,
34 | client as arweave,
35 | } from "../utils/arweave";
36 | import { useRouter } from "next/router";
37 | import { formatAddress } from "../utils/format";
38 | import { interactWrite } from "smartweave";
39 | import Balance from "../components/Balance";
40 | import Verto from "@verto/js";
41 | import Head from "next/head";
42 | import Metas from "../components/Metas";
43 | import Watchlist from "../components/Watchlist";
44 | import axios from "axios";
45 | import Link from "next/link";
46 | import ListingModal from "../components/ListingModal";
47 | import styles from "../styles/views/app.module.sass";
48 |
49 | const client = new Verto();
50 |
51 | const App = () => {
52 | const [balances, setBalances] = useState([]);
53 | const address = useSelector((state: RootState) => state.addressReducer);
54 | const [showMorePsts, setShowMorePsts] = useState(false);
55 | const theme = useTheme();
56 | const [owned, setOwned] = useState([]);
57 | const [userData, setUserData] = useState();
58 | const [loadingOwned, setLoadingOwned] = useState(true);
59 | const router = useRouter();
60 | const listModal = useModal();
61 | const { setToast } = useToasts();
62 |
63 | useEffect(() => {
64 | if (!address) return;
65 | setBalances([]);
66 | setOwned([]);
67 | setLoadingOwned(true);
68 |
69 | (async () => {
70 | const user = (await client.getUser(address)) ?? null;
71 | setUserData(user);
72 |
73 | const { data: ownedCollectibles } = await axios.get(
74 | `${CACHE_URL}/user/${user?.username ?? address}/owns`
75 | );
76 |
77 | setOwned(
78 | await Promise.all(
79 | ownedCollectibles.map(async (artoworkID: string) => {
80 | const { data } = await axios.get(
81 | `${CACHE_URL}/site/artwork/${artoworkID}`
82 | );
83 | return {
84 | ...data,
85 | owner: {
86 | ...data.owner,
87 | image: data.owner.image
88 | ? `https://arweave.net/${data.owner.image}`
89 | : undefined,
90 | },
91 | price:
92 | (await arPrice()) * (await client.getPrice(artoworkID)).price,
93 | };
94 | })
95 | )
96 | );
97 | setLoadingOwned(false);
98 |
99 | if (user) {
100 | for (const addr of user.addresses) {
101 | const addressBalances = await client.getBalances(addr);
102 |
103 | setBalances((val) =>
104 | [
105 | ...val.filter(
106 | (existingBalance) =>
107 | !addressBalances.find(({ id }) => id === existingBalance.id)
108 | ),
109 | ...addressBalances.map((addBalance) => ({
110 | ...addBalance,
111 | balance:
112 | addBalance.balance +
113 | (val.find(({ id }) => id === addBalance.id)?.balance ?? 0),
114 | })),
115 | ].filter(({ id }) => !ownedCollectibles.includes(id))
116 | );
117 | }
118 | } else
119 | setBalances(
120 | (await client.getBalances(address)).filter(
121 | ({ id }) => !ownedCollectibles.includes(id)
122 | )
123 | );
124 | })();
125 | }, [address]);
126 |
127 | return (
128 |
129 |
130 | Verto - Home
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | Balances
140 |
141 |
142 | {
145 | if (!userData)
146 | return setToast({
147 | description: "Please setup your Verto ID first",
148 | type: "error",
149 | duration: 5300,
150 | });
151 | listModal.setState(true);
152 | }}
153 | >
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | {balances.map(
162 | (item, i) =>
163 | (showMorePsts || i < 4) && (
164 |
165 |
177 |
178 |
179 | )
180 | )}
181 |
182 |
183 | {balances.length > 4 && (
184 |
185 |
186 | setShowMorePsts((val) => !val)}
189 | >
190 | Show{" "}
191 | {(showMorePsts && (
192 | <>
193 | less
194 |
195 | >
196 | )) || (
197 | <>
198 | all
199 |
200 | >
201 | )}
202 |
203 |
204 | )}
205 |
206 | {balances.length === 0 && (
207 | Nothing in wallet
208 | )}
209 |
210 |
217 |
Owned collectibles
218 |
219 |
220 |
221 | {owned.slice(0, 4).map((collectible, i) => (
222 |
223 | router.push(`/space/${collectible.id}`)}
234 | />
235 |
236 | ))}
237 |
238 |
239 | {owned.length > 0 && (
240 |
241 |
242 |
243 | View all
244 |
245 |
246 |
247 |
248 | )}
249 |
250 | {!loadingOwned && owned.length === 0 && (
251 |
252 | You do not own any collectibles.
253 | Consider checking out Space
254 |
255 | )}
256 | {/** Placeholder */}
257 | {owned.length === 0 && (
258 |
265 | )}
266 |
267 |
268 |
269 |
270 | );
271 | };
272 |
273 | export default App;
274 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Card, Page, Spacer, useModal } from "@verto/ui";
2 | import { useEffect, useState } from "react";
3 | import { permissions } from "../utils/arconnect";
4 | import { useRouter } from "next/router";
5 | import { RootState } from "../store/reducers";
6 | import { useSelector, useDispatch } from "react-redux";
7 | import { updateAddress } from "../store/actions";
8 | import { AnimatePresence, motion } from "framer-motion";
9 | import { useMediaPredicate } from "react-media-hook";
10 | import { cardListAnimation, opacityAnimation } from "../utils/animations";
11 | import { CACHE_URL } from "../utils/arweave";
12 | import { OrderInterface } from "@verto/js/dist/faces";
13 | import { getType } from "../utils/order";
14 | import Typed from "typed.js";
15 | import PSTSwitcher from "../components/PSTSwitcher";
16 | import axios from "axios";
17 | import Verto from "@verto/js";
18 | import Head from "next/head";
19 | import Metas from "../components/Metas";
20 | import SetupModal from "../components/SetupModal";
21 | import styles from "../styles/views/home.module.sass";
22 |
23 | const client = new Verto();
24 |
25 | const Home = ({ artwork }: { artwork: any }) => {
26 | const address = useSelector((state: RootState) => state.addressReducer);
27 | const router = useRouter();
28 | const dispatch = useDispatch();
29 |
30 | useEffect(() => {
31 | const options = {
32 | strings: ["PSTs", "PSCs", "NFTs", "collectibles", "anything"],
33 | loop: true,
34 | loopCount: 2,
35 |
36 | typeSpeed: 100,
37 | backSpeed: 50,
38 | backDelay: 2000,
39 | smartBackspace: false,
40 | };
41 |
42 | const typed = new Typed("#typed", options);
43 |
44 | return () => {
45 | typed.destroy();
46 | };
47 | }, []);
48 |
49 | const [arworkData, setArtworkData] = useState(artwork);
50 |
51 | useEffect(() => {
52 | (async () => {
53 | const res = await client.getPrice(artwork.id);
54 | const { data: gecko } = await axios.get(
55 | "https://api.coingecko.com/api/v3/simple/price?ids=arweave&vs_currencies=usd"
56 | );
57 |
58 | setArtworkData((val) => ({
59 | ...val,
60 | owner: {
61 | ...val.owner,
62 | image: val.owner.image
63 | ? `https://arweave.net/${artwork.owner.image}`
64 | : undefined,
65 | },
66 | price: res?.price ? (res.price * gecko.arweave.usd).toFixed(2) : null,
67 | }));
68 | })();
69 | }, []);
70 |
71 | const setupModal = useModal();
72 |
73 | async function login() {
74 | await window.arweaveWallet.connect(permissions, { name: "Verto" });
75 |
76 | const activeAddress = await window.arweaveWallet.getActiveAddress();
77 | dispatch(updateAddress(activeAddress));
78 |
79 | const user = await client.getUser(activeAddress);
80 |
81 | if (!user) setupModal.setState(true);
82 | }
83 |
84 | type Activity = OrderInterface & {
85 | actions: {
86 | id: string;
87 | descriptions: string;
88 | timestamp: number;
89 | match?: string;
90 | }[];
91 | };
92 | const [latestActivity, setLatestActivity] = useState([]);
93 |
94 | const mobile = useMediaPredicate("(max-width: 720px)");
95 |
96 | useEffect(() => {
97 | (async () => {
98 | const { data: activities } = await axios.get(
99 | `${CACHE_URL}/latest-activity`
100 | );
101 |
102 | setLatestActivity(
103 | activities.map(
104 | ({
105 | status,
106 | sender,
107 | target,
108 | token,
109 | input,
110 | output,
111 | timestamp,
112 | actions,
113 | }: Activity) => ({
114 | id: actions[0].id,
115 | status,
116 | sender,
117 | target,
118 | token,
119 | input,
120 | output,
121 | timestamp,
122 | })
123 | )
124 | );
125 | })();
126 | }, []);
127 |
128 | return (
129 | <>
130 |
131 | Verto - Welcome
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | Exchange
140 |
141 | on Arweave
142 |
143 |
144 |
145 | Verto is a decentralized trading protocol
146 |
147 | built on top of{" "}
148 |
153 | Arweave
154 |
155 | .
156 |
157 |
158 |
159 | router.push("/space")}
163 | >
164 | Explore tokens
165 |
166 |
167 | {
170 | if (!address) login();
171 | else router.push("/swap");
172 | }}
173 | >
174 | Trade now
175 |
176 |
177 |
178 |
179 |
180 | {Object.keys(arworkData).length > 0 && (
181 |
182 | router.push(`/space/${arworkData.id}`)}
192 | />
193 |
194 | )}
195 |
196 |
197 |
198 |
199 |
200 |
Latest Activity
201 |
202 | {latestActivity.map((activity, i) => (
203 |
204 |
213 | {i !== latestActivity.length - 1 && }
214 |
215 | ))}
216 |
217 |
218 |
219 |
220 |
221 |
What are PSTs?
222 |
223 | Profit-Sharing Tokens, or PSTs, are a new incentivization
224 | mechanism for the open web that allow developers to earn a stream
225 | of micro-dividends for the duration their application is used. (
226 |
231 | Source
232 |
233 | )
234 |
235 |
236 | In order for PSTs to have value, however, they need to be able to
237 | be exchanged for other PSTs or AR. This is where Verto comes in...
238 |
239 |
240 |
241 |
242 |
243 |
Why Verto?
244 |
245 | Verto is a completely decentralized network of trading posts built
246 | on top of the blockweave. Anyone can host their own trading post and
247 | power the exchange, while also being incentivized to do so. With
248 | Verto, you can pick the trading post you'd like to use and exchange
249 | your PSTs freely!
250 |
251 |
252 | Decisions for Verto are made by our very own Profit-Sharing DAO,
253 | which means that anyone can have a say in the direction of our
254 | platform.
255 |
256 |
257 | You can also easily host your own Trading Post, while accruing PSTs
258 | for doing so. See our Trading Post Repository for documentation.
259 |
260 |
261 |
262 |
VRT - The Verto Protocol Token
263 |
264 | VRT is a way for any person to passively earn a volume-weighted
265 | index of all profit sharing tokens being traded on the exchange.
266 | 0.5% of any PST transaction made on the exchange is sent straight to
267 | a VRT holder.
268 |
269 |
270 | By holding VRT, a user is also inherently a member of the Verto
271 | Profit-Sharing DAO, which means they can choose to stake their
272 | tokens to have a say in the various decisions made for the platform.
273 |
274 |
275 |
276 |
Get in Touch
277 |
278 | Have a question or are interested in purchasing more VRT than you
279 | can find on the exchange? Join our Discord. Let's chat!
280 |
281 |
282 |
283 |
284 | >
285 | );
286 | };
287 |
288 | export async function getServerSideProps() {
289 | return {
290 | redirect: {
291 | destination: "https://github.com/useverto/flex",
292 | permanent: false,
293 | },
294 | };
295 |
296 | const { data } = await axios.get(`${CACHE_URL}/site/artwork`);
297 |
298 | return { props: { artwork: data } };
299 | }
300 |
301 | export default Home;
302 |
--------------------------------------------------------------------------------
/src/pages/orbit/index.tsx:
--------------------------------------------------------------------------------
1 | import Verto from "@verto/js";
2 | import { TradingPostInterface } from "@verto/js/dist/faces";
3 | import { Card, Page, Spacer } from "@verto/ui";
4 | import { useRouter } from "next/router";
5 | import { useEffect, useState } from "react";
6 | import { motion } from "framer-motion";
7 | import { cardListAnimation } from "../../utils/animations";
8 | import Metas from "../../components/Metas";
9 | import Head from "next/head";
10 | import useSWR from "swr";
11 |
12 | const client = new Verto();
13 |
14 | const Orbit = (props: { posts: TradingPostInterface[] }) => {
15 | const { data: posts } = useSWR(
16 | "getTradingPosts",
17 | () => client.getTradingPosts(),
18 | {
19 | initialData: props.posts,
20 | }
21 | );
22 |
23 | const router = useRouter();
24 | const [status, setStatus] = useState({});
25 |
26 | useEffect(() => {
27 | (async () => {
28 | for (const post of posts) {
29 | let status;
30 |
31 | try {
32 | await fetch(post.endpoint);
33 | status = "online";
34 | } catch {
35 | status = "offline";
36 | }
37 |
38 | setStatus((val) => ({ ...val, [post.address]: status }));
39 | }
40 | })();
41 | }, []);
42 |
43 | return (
44 |
45 |
46 | Verto - Orbit
47 |
48 |
49 |
50 | {posts
51 | .sort((a, b) => b.stake - a.stake)
52 | .map((post, i) => (
53 |
54 | router.push(`/orbit/post/${post.address}`)}
60 | />
61 |
62 |
63 | ))}
64 |
65 | );
66 | };
67 |
68 | export async function getStaticProps() {
69 | const res = await client.getTradingPosts();
70 |
71 | return { props: { posts: res }, revalidate: 1 };
72 | }
73 |
74 | export default Orbit;
75 |
--------------------------------------------------------------------------------
/src/pages/orbit/order/[id].tsx:
--------------------------------------------------------------------------------
1 | import { Card, Page, Spacer, Tooltip } from "@verto/ui";
2 | import { useRouter } from "next/router";
3 | import { cardListAnimation } from "../../../utils/animations";
4 | import { motion } from "framer-motion";
5 | import { getType } from "../../../utils/order";
6 | import { CACHE_URL } from "../../../utils/arweave";
7 | import { useMediaPredicate } from "react-media-hook";
8 | import { formatAddress } from "../../../utils/format";
9 | import axios from "axios";
10 | import Head from "next/head";
11 | import Metas from "../../../components/Metas";
12 | import Link from "next/link";
13 | import useSWR from "swr";
14 | import styles from "../../../styles/views/orbit.module.sass";
15 |
16 | const Order = (props: { order: any; id: string }) => {
17 | const router = useRouter();
18 | if (router.isFallback) return <>>;
19 |
20 | const { data: order } = useSWR(
21 | "getOrder",
22 | async () => {
23 | const { data } = await axios.get(`${CACHE_URL}/order/${props.id}`);
24 | return data;
25 | },
26 | {
27 | initialData: props.order,
28 | }
29 | );
30 |
31 | const notMobile = useMediaPredicate("(min-width: 720px)");
32 |
33 | function shortOnMobile(addr: string) {
34 | if (notMobile) return addr;
35 | else return formatAddress(addr, 18);
36 | }
37 |
38 | return (
39 |
40 |
41 | Verto - Order {props.id}
42 |
43 |
44 |
45 |
46 |
47 | Order
48 | {getType(order.input)}
49 |
50 |
51 | {shortOnMobile(order.id)}
52 |
53 |
54 |
57 |
58 |
59 |
60 | Owner:
61 | {shortOnMobile(order.sender)}
62 |
63 |
64 |
65 | {order.actions.map((action, i) => (
66 |
67 |
73 |
74 |
75 | ))}
76 |
77 | );
78 | };
79 |
80 | export async function getStaticPaths() {
81 | return {
82 | paths: [],
83 | fallback: "blocking",
84 | };
85 | }
86 |
87 | export async function getStaticProps({ params: { id } }) {
88 | try {
89 | const { data: order } = await axios.get(`${CACHE_URL}/order/${id}`);
90 |
91 | return { props: { order, id }, revalidate: 1 };
92 | } catch (e) {
93 | if (e?.response?.status === 404)
94 | return {
95 | redirect: {
96 | destination: "/404",
97 | permanent: false,
98 | },
99 | };
100 |
101 | return {
102 | redirect: {
103 | destination: "/500",
104 | permanent: false,
105 | },
106 | };
107 | }
108 | }
109 |
110 | export default Order;
111 |
--------------------------------------------------------------------------------
/src/pages/orbit/post/[addr].tsx:
--------------------------------------------------------------------------------
1 | import { Card, Loading, Page, Spacer, Tooltip, useTheme } from "@verto/ui";
2 | import { AnimatePresence, motion } from "framer-motion";
3 | import { Bar } from "react-chartjs-2";
4 | import { cardListAnimation } from "../../../utils/animations";
5 | import { getType } from "../../../utils/order";
6 | import { useEffect, useState } from "react";
7 | import { CACHE_URL } from "../../../utils/arweave";
8 | import { useRouter } from "next/router";
9 | import { useMediaPredicate } from "react-media-hook";
10 | import { formatAddress } from "../../../utils/format";
11 | import Metas from "../../../components/Metas";
12 | import Head from "next/head";
13 | import axios from "axios";
14 | import useInfiniteScroll from "../../../utils/infinite_scroll";
15 | import dayjs from "dayjs";
16 | import duration from "dayjs/plugin/duration";
17 | import Verto from "@verto/js";
18 | import useSWR from "swr";
19 | import styles from "../../../styles/views/orbit.module.sass";
20 |
21 | const client = new Verto();
22 | dayjs.extend(duration);
23 |
24 | const Post = (props: { addr: string; stats: any[]; orders: any[] }) => {
25 | const router = useRouter();
26 | if (router.isFallback) return <>>;
27 |
28 | const { data: stats } = useSWR(
29 | "getStats",
30 | async () => {
31 | const { data } = await axios.get(
32 | `${CACHE_URL}/posts/${props.addr}/stats`
33 | );
34 | return data;
35 | },
36 | {
37 | initialData: props.stats,
38 | }
39 | );
40 | const { data: orders } = useSWR(
41 | "getOrders",
42 | async () => {
43 | const { data } = await axios.get(
44 | `${CACHE_URL}/posts/${props.addr}/orders?limit=10`
45 | );
46 | return data;
47 | },
48 | {
49 | initialData: props.orders,
50 | }
51 | );
52 |
53 | const { loading, data } = useInfiniteScroll(loadMore, orders);
54 | const [status, setStatus] = useState<"online" | "offline">();
55 | const theme = useTheme();
56 | const [postUptime, setPostUptime] = useState(0);
57 | const [postData, setPostData] = useState<{
58 | version: string;
59 | fee: number;
60 | balance: number;
61 | stake: number;
62 | }>({
63 | version: "0.0.0",
64 | fee: 0,
65 | balance: 0,
66 | stake: 0,
67 | });
68 |
69 | useEffect(() => {
70 | (async () => {
71 | try {
72 | const { data: post } = await axios.get(
73 | `${CACHE_URL}/posts/${props.addr}`
74 | );
75 | const { data: pingData } = await axios.get(post.endpoint);
76 | setStatus("online");
77 | setPostData((val) => ({
78 | ...val,
79 | balance: post.balance,
80 | stake: post.stake,
81 | }));
82 | setPostUptime(pingData.uptime);
83 | } catch {
84 | setStatus("offline");
85 | }
86 | })();
87 | }, []);
88 |
89 | useEffect(() => {
90 | (async () => {
91 | const { tradeFee, version } = await client.getConfig(props.addr);
92 | setPostData((val) => ({
93 | ...val,
94 | version,
95 | fee: tradeFee,
96 | }));
97 | })();
98 | }, []);
99 |
100 | async function loadMore() {
101 | const { data: moreOrders } = await axios.get(
102 | `${CACHE_URL}/posts/${props.addr}/orders?limit=10&after=${
103 | data[data.length - 1].id
104 | }`
105 | );
106 |
107 | return moreOrders;
108 | }
109 |
110 | function calculateUptime() {
111 | const data = dayjs.duration({ seconds: postUptime });
112 | const days = Math.floor(data.asDays());
113 | const hours = Math.floor(
114 | dayjs.duration({ days: data.asDays() - days }).asHours()
115 | );
116 |
117 | return `${days} day${days > 1 ? "s" : ""} ${hours} hour${
118 | hours > 1 ? "s" : ""
119 | }`;
120 | }
121 |
122 | const notMobile = useMediaPredicate("(min-width: 720px)");
123 |
124 | function shortOnMobile(addr: string) {
125 | if (notMobile) return addr;
126 | else return formatAddress(addr, 18);
127 | }
128 |
129 | return (
130 |
131 |
132 | Verto - Post {props.addr}
133 |
134 |
135 |
136 |
165 |
166 |
167 |
168 | Version
169 | {postData.version}
170 |
171 |
172 | Fee
173 | {(postData.fee * 100).toLocaleString()}%
174 |
175 |
176 | Uptime
177 | {calculateUptime()}
178 |
179 |
180 | Stake
181 | {postData.stake.toLocaleString()} VRT
182 |
183 |
184 | Balance
185 | {postData.balance.toLocaleString(undefined, {
186 | maximumFractionDigits: 5,
187 | })}{" "}
188 | AR
189 |
190 |
191 | item.succeeded)
203 | .reverse(),
204 | },
205 | {
206 | label: "Pending",
207 | backgroundColor: "rgba(255, 211, 54, 0.5)",
208 | borderColor: "#FFD336",
209 | borderWidth: 0.5,
210 | data: Object.values(stats)
211 | // @ts-ignore
212 | .map((item) => item.pending)
213 | .reverse(),
214 | },
215 | {
216 | label: "Ended",
217 | backgroundColor: "rgba(130, 130, 130, 0.5)",
218 | borderColor: "#828282",
219 | borderWidth: 0.5,
220 | data: Object.values(stats)
221 | // @ts-ignore
222 | .map((item) => item.neutral + item.errored)
223 | .reverse(),
224 | },
225 | ],
226 | }}
227 | height={50}
228 | options={{
229 | tooltips: {
230 | mode: "index",
231 | intersect: false,
232 | backgroundColor: theme === "Light" ? "#000000" : "#ffffff",
233 | titleFontFamily: '"Poppins", sans-serif',
234 | bodyFontColor: theme === "Light" ? "#d4d4d4" : "#666666",
235 | bodyFontFamily: '"Poppins", sans-serif',
236 | titleFontColor: theme === "Light" ? "#ffffff" : "#000000",
237 | padding: 9,
238 | },
239 | hover: {
240 | mode: "nearest",
241 | intersect: true,
242 | },
243 | scales: {
244 | xAxes: [
245 | {
246 | display: false,
247 | stacked: true,
248 | },
249 | ],
250 | yAxes: [
251 | {
252 | display: false,
253 | stacked: true,
254 | },
255 | ],
256 | },
257 | legend: {
258 | display: false,
259 | },
260 | }}
261 | />
262 |
263 | {data.map((order, i) => (
264 |
265 |
271 |
272 |
273 | ))}
274 |
275 | {loading && (
276 |
282 |
283 |
284 |
285 | )}
286 |
287 |
288 | );
289 | };
290 |
291 | export async function getStaticPaths() {
292 | return {
293 | paths: [],
294 | fallback: "blocking",
295 | };
296 | }
297 |
298 | export async function getStaticProps({ params: { addr } }) {
299 | try {
300 | const { data: stats } = await axios.get(`${CACHE_URL}/posts/${addr}/stats`);
301 |
302 | const { data: orders } = await axios.get(
303 | `${CACHE_URL}/posts/${addr}/orders?limit=10`
304 | );
305 |
306 | return { props: { addr, stats, orders }, revalidate: 1 };
307 | } catch (e) {
308 | if (e?.response?.status === 404)
309 | return {
310 | redirect: {
311 | destination: "/404",
312 | permanent: false,
313 | },
314 | };
315 |
316 | return {
317 | redirect: {
318 | destination: "/500",
319 | permanent: false,
320 | },
321 | };
322 | }
323 | }
324 |
325 | export default Post;
326 |
--------------------------------------------------------------------------------
/src/pages/space/[id].tsx:
--------------------------------------------------------------------------------
1 | import { Page } from "@verto/ui";
2 | import { CACHE_URL, isAddress } from "../../utils/arweave";
3 | import Verto from "@verto/js";
4 | import axios from "axios";
5 | import Community from "../../components/space/Community";
6 | import Collection from "../../components/space/Collection";
7 | import Art from "../../components/space/Art";
8 |
9 | const client = new Verto();
10 |
11 | const Token = (props) => {
12 | // TODO: custom layout
13 |
14 | return (
15 |
16 | {(props.type === "community" && ) ||
17 | (props.type === "collection" && ) || (
18 |
19 | )}
20 |
21 | );
22 | };
23 |
24 | export async function getStaticPaths() {
25 | return {
26 | paths: [],
27 | fallback: "blocking",
28 | };
29 | }
30 |
31 | export async function getStaticProps({ params: { id } }) {
32 | if (!isAddress(id))
33 | return {
34 | redirect: {
35 | destination: "/404",
36 | permanent: false,
37 | },
38 | };
39 |
40 | const {
41 | data: { type, id: returnedID },
42 | } = await axios.get(`${CACHE_URL}/site/type/${id}`);
43 |
44 | if (!type && !returnedID)
45 | return {
46 | redirect: {
47 | destination: "/404",
48 | permanent: false,
49 | },
50 | };
51 |
52 | if (type === "collection") {
53 | const { data } = await axios.get(`${CACHE_URL}/site/collection/${id}`);
54 |
55 | return {
56 | props: {
57 | ...data,
58 | type: "collection",
59 | },
60 | revalidate: 1,
61 | };
62 | } else {
63 | const {
64 | data: { state },
65 | } = await axios.get(`${CACHE_URL}/${id}`);
66 | const res = await client.getPrice(id);
67 |
68 | const { data: gecko } = await axios.get(
69 | "https://api.coingecko.com/api/v3/simple/price?ids=arweave&vs_currencies=usd"
70 | );
71 |
72 | return {
73 | props: {
74 | id,
75 | name: state.name,
76 | ticker: state.ticker,
77 | price: res ? res.price * gecko.arweave.usd : "--",
78 | type: type || "community",
79 | },
80 | revalidate: 1,
81 | };
82 | }
83 | }
84 |
85 | export default Token;
86 |
--------------------------------------------------------------------------------
/src/pages/user/[input]/creations.tsx:
--------------------------------------------------------------------------------
1 | import { Card, Page, Spacer, Loading } from "@verto/ui";
2 | import { UserInterface } from "@verto/js/dist/faces";
3 | import { useRouter } from "next/router";
4 | import { arPrice, CACHE_URL, isAddress } from "../../../utils/arweave";
5 | import { cardAnimation } from "../../../utils/animations";
6 | import { AnimatePresence, motion } from "framer-motion";
7 | import { Art } from "../../../utils/user";
8 | import axios from "axios";
9 | import Verto from "@verto/js";
10 | import Head from "next/head";
11 | import Metas from "../../../components/Metas";
12 | import useInfiniteScroll from "../../../utils/infinite_scroll";
13 | import styles from "../../../styles/views/user.module.sass";
14 |
15 | const client = new Verto();
16 |
17 | const Creations = (props: {
18 | user: UserInterface | null;
19 | input: string;
20 | creations: Art[];
21 | }) => {
22 | const router = useRouter();
23 | if (router.isFallback) return <>>;
24 |
25 | const { loading, data } = useInfiniteScroll(loadMore, props.creations);
26 |
27 | async function loadMore(): Promise {
28 | let arts = [];
29 |
30 | const { data: ids } = await axios.get(
31 | `${CACHE_URL}/user/${props.user?.username ?? props.input}/creations/${
32 | data.length
33 | }`
34 | );
35 |
36 | for (const id of ids) {
37 | let { data: artworkData } = await axios.get(
38 | `${CACHE_URL}/site/artwork/${id}`
39 | );
40 | const price = (await arPrice()) * (await client.getPrice(id)).price;
41 |
42 | if (artworkData.owner.image)
43 | artworkData.owner.image = `https://arweave.net/${artworkData.owner.image}`;
44 |
45 | arts.push({
46 | ...artworkData,
47 | price,
48 | });
49 | }
50 |
51 | return arts;
52 | }
53 |
54 | return (
55 |
56 |
57 | @{props.user?.username || props.input} - Creations
58 |
66 |
70 |
71 |
72 | All Creations
73 |
74 |
75 |
76 | {data.map((art, i) => (
77 |
82 | router.push(`/space/${art.id}`)}
93 | />
94 |
95 | ))}
96 |
97 |
98 |
99 | {loading && (
100 |
106 |
107 |
108 |
109 | )}
110 |
111 |
112 | );
113 | };
114 |
115 | export async function getStaticPaths() {
116 | return {
117 | paths: [],
118 | fallback: "blocking",
119 | };
120 | }
121 |
122 | export async function getStaticProps({ params: { input } }) {
123 | const user = (await client.getUser(input)) ?? null;
124 | const creations: Art[] = [];
125 |
126 | // redirect if the user cannot be found and if it is not and address either
127 | if (!isAddress(input) && !user)
128 | return {
129 | redirect: {
130 | destination: "/404",
131 | permanent: false,
132 | },
133 | };
134 |
135 | for (let i = 0; i < 2; i++) {
136 | const { data: ids } = await axios.get(
137 | `${CACHE_URL}/user/${input}/creations/${i * 4}`
138 | );
139 |
140 | for (const id of ids) {
141 | let { data } = await axios.get(`${CACHE_URL}/site/artwork/${id}`);
142 | const price = (await arPrice()) * (await client.getPrice(id)).price;
143 |
144 | if (data.owner.image)
145 | data.owner.image = `https://arweave.net/${data.owner.image}`;
146 |
147 | creations.push({
148 | ...data,
149 | price,
150 | });
151 | }
152 | }
153 |
154 | return { props: { creations, user, input }, revalidate: 1 };
155 | }
156 |
157 | export default Creations;
158 |
--------------------------------------------------------------------------------
/src/pages/user/[input]/owns.tsx:
--------------------------------------------------------------------------------
1 | import { Card, Page, Spacer, Loading } from "@verto/ui";
2 | import { UserInterface } from "@verto/js/dist/faces";
3 | import { useRouter } from "next/router";
4 | import { arPrice, CACHE_URL, isAddress } from "../../../utils/arweave";
5 | import { cardAnimation } from "../../../utils/animations";
6 | import { AnimatePresence, motion } from "framer-motion";
7 | import { Art } from "../../../utils/user";
8 | import axios from "axios";
9 | import Verto from "@verto/js";
10 | import Head from "next/head";
11 | import Metas from "../../../components/Metas";
12 | import useInfiniteScroll from "../../../utils/infinite_scroll";
13 | import styles from "../../../styles/views/user.module.sass";
14 |
15 | const client = new Verto();
16 |
17 | const Owns = (props: {
18 | user: UserInterface | null;
19 | input: string;
20 | owns: Art[];
21 | }) => {
22 | const router = useRouter();
23 | if (router.isFallback) return <>>;
24 |
25 | const { loading, data } = useInfiniteScroll(loadMore, props.owns);
26 |
27 | async function loadMore(): Promise {
28 | let arts = [];
29 |
30 | const { data: ids } = await axios.get(
31 | `${CACHE_URL}/user/${props.user?.username ?? props.input}/owns/${
32 | data.length
33 | }`
34 | );
35 |
36 | for (const id of ids) {
37 | let { data: artworkData } = await axios.get(
38 | `${CACHE_URL}/site/artwork/${id}`
39 | );
40 | const price = (await arPrice()) * (await client.getPrice(id)).price;
41 |
42 | if (artworkData.owner.image)
43 | artworkData.owner.image = `https://arweave.net/${artworkData.owner.image}`;
44 |
45 | arts.push({
46 | ...artworkData,
47 | price,
48 | });
49 | }
50 |
51 | return arts;
52 | }
53 |
54 | return (
55 |
56 |
57 |
58 | @{props.user?.username || props.input} - Owned art {"&"} collectibles
59 |
60 |
70 |
74 |
75 |
76 | Owned art {"&"} collectibles
77 |
78 |
79 |
80 | {data.map((art, i) => (
81 |
86 | router.push(`/space/${art.id}`)}
97 | />
98 |
99 | ))}
100 |
101 |
102 |
103 | {loading && (
104 |
110 |
111 |
112 |
113 | )}
114 |
115 |
116 | );
117 | };
118 |
119 | export async function getStaticPaths() {
120 | return {
121 | paths: [],
122 | fallback: "blocking",
123 | };
124 | }
125 |
126 | export async function getStaticProps({ params: { input } }) {
127 | const user = (await client.getUser(input)) ?? null;
128 | const owns: Art[] = [];
129 |
130 | // redirect if the user cannot be found and if it is not and address either
131 | if (!isAddress(input) && !user)
132 | return {
133 | redirect: {
134 | destination: "/404",
135 | permanent: false,
136 | },
137 | };
138 |
139 | for (let i = 0; i < 2; i++) {
140 | const { data: ids } = await axios.get(
141 | `${CACHE_URL}/user/${input}/owns/${i * 4}`
142 | );
143 |
144 | for (const id of ids) {
145 | let { data } = await axios.get(`${CACHE_URL}/site/artwork/${id}`);
146 | const price = (await arPrice()) * (await client.getPrice(id)).price;
147 |
148 | if (data.owner.image)
149 | data.owner.image = `https://arweave.net/${data.owner.image}`;
150 |
151 | owns.push({
152 | ...data,
153 | price,
154 | });
155 | }
156 | }
157 |
158 | return { props: { owns, user, input }, revalidate: 1 };
159 | }
160 |
161 | export default Owns;
162 |
--------------------------------------------------------------------------------
/src/pages/user/[input]/trades.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Button,
3 | Card,
4 | Modal,
5 | Page,
6 | Spacer,
7 | useModal,
8 | useToasts,
9 | } from "@verto/ui";
10 | import { useEffect, useState } from "react";
11 | import { OrderInterface, UserInterface } from "@verto/js/dist/faces";
12 | import { motion } from "framer-motion";
13 | import { cardListAnimation } from "../../../utils/animations";
14 | import { useSelector } from "react-redux";
15 | import { RootState } from "../../../store/reducers";
16 | import { addToCancel, getCancelledOrders } from "../../../utils/order";
17 | import { useRouter } from "next/router";
18 | import { isAddress } from "../../../utils/arweave";
19 | import Verto from "@verto/js";
20 | import Head from "next/head";
21 | import Metas from "../../../components/Metas";
22 |
23 | const client = new Verto();
24 |
25 | const Trades = (props: { user: UserInterface | null; input: string }) => {
26 | const router = useRouter();
27 | if (router.isFallback) return <>>;
28 |
29 | const [orders, setOrders] = useState([]);
30 | const [isCurrentUser, setIsCurrentUser] = useState(false);
31 | const currentAddress = useSelector(
32 | (state: RootState) => state.addressReducer
33 | );
34 | const cancelModal = useModal();
35 | const [cancelID, setCancelID] = useState("");
36 | const { setToast } = useToasts();
37 | const [cancelled, setCancelled] = useState([]);
38 |
39 | // load orders
40 | useEffect(() => {
41 | (async () => {
42 | let res: OrderInterface[] = [];
43 |
44 | if (props.user) {
45 | for (const address of props.user.addresses) {
46 | res.push(...(await client.getOrders(address)));
47 | }
48 | } else res.push(...(await client.getOrders(props.input)));
49 |
50 | setOrders(res.sort((a, b) => b.timestamp - a.timestamp));
51 | })();
52 | }, []);
53 |
54 | // set if the profile is owned by the logged in user
55 | useEffect(() => {
56 | if (
57 | !props.user?.addresses.includes(currentAddress) &&
58 | props.input !== currentAddress
59 | )
60 | return;
61 | setIsCurrentUser(true);
62 | }, [currentAddress]);
63 | useEffect(() => setCancelled(getCancelledOrders()), []);
64 |
65 | return (
66 |
67 |
68 | @{props.user?.username || props.input} - Trades
69 |
77 |
81 |
82 |
83 | All Trades
84 |
85 | {orders.map((order, i) => (
86 |
87 | {
89 | let type: any;
90 | if (order.input.split(" ")[1] === "AR") {
91 | type = "buy";
92 | } else {
93 | type = "sell";
94 | }
95 |
96 | return type;
97 | })()}
98 | from={{
99 | amount: parseFloat(order.input.split(" ")[0]),
100 | ticker: order.input.split(" ")[1],
101 | }}
102 | to={order.output.split(" ")[1]}
103 | timestamp={new Date(order.timestamp * 1000)}
104 | status={(() => {
105 | let status: any = order.status;
106 | const acceptedStatuses = [
107 | "success",
108 | "pending",
109 | "cancelled",
110 | "refunded",
111 | ];
112 | if (!acceptedStatuses.includes(order.status)) status = "error";
113 |
114 | return status;
115 | })()}
116 | orderID={order.id}
117 | cancel={
118 | (isCurrentUser &&
119 | order.status === "pending" &&
120 | !cancelled.includes(order.id) &&
121 | (() => {
122 | setCancelID(order.id);
123 | cancelModal.setState(true);
124 | })) ||
125 | undefined
126 | }
127 | />
128 |
129 |
130 | ))}
131 |
132 | Cancel order
133 |
134 | Are you sure you want to cancel your order ({cancelID}) ?
135 |
136 | {
140 | try {
141 | await client.cancel(cancelID);
142 | setCancelID("");
143 | cancelModal.setState(false);
144 | addToCancel(cancelID);
145 | setCancelled((val) => [...val, cancelID]);
146 | setToast({
147 | description: "Cancelled order",
148 | type: "success",
149 | duration: 3000,
150 | });
151 | } catch {
152 | setToast({
153 | description: "Could not cancel order",
154 | type: "error",
155 | duration: 3000,
156 | });
157 | }
158 | }}
159 | >
160 | Cancel
161 |
162 |
163 |
164 | );
165 | };
166 |
167 | export async function getStaticPaths() {
168 | return {
169 | paths: [],
170 | fallback: "blocking",
171 | };
172 | }
173 |
174 | export async function getStaticProps({ params: { input } }) {
175 | const user = (await client.getUser(input)) ?? null;
176 |
177 | // redirect if the user cannot be found and if it is not and address either
178 | if (!isAddress(input) && !user)
179 | return {
180 | redirect: {
181 | destination: "/404",
182 | permanent: false,
183 | },
184 | };
185 |
186 | if (user && input !== user.username)
187 | return {
188 | redirect: {
189 | destination: `/@${user.username}`,
190 | permanent: false,
191 | },
192 | };
193 |
194 | return { props: { user, input }, revalidate: 1 };
195 | }
196 |
197 | export default Trades;
198 |
--------------------------------------------------------------------------------
/src/pages/user/[input]/transactions.tsx:
--------------------------------------------------------------------------------
1 | import { Loading, Page, Spacer, Tooltip } from "@verto/ui";
2 | import { TransactionInterface, UserInterface } from "@verto/js/dist/faces";
3 | import { AnimatePresence, motion } from "framer-motion";
4 | import { cardListAnimation } from "../../../utils/animations";
5 | import { useRouter } from "next/router";
6 | import { isAddress } from "../../../utils/arweave";
7 | import { useMediaPredicate } from "react-media-hook";
8 | import { formatAddress } from "../../../utils/format";
9 | import Verto from "@verto/js";
10 | import Head from "next/head";
11 | import Metas from "../../../components/Metas";
12 | import useInfiniteScroll from "../../../utils/infinite_scroll";
13 | import styles from "../../../styles/views/user.module.sass";
14 |
15 | const client = new Verto();
16 |
17 | const Transactions = (props: {
18 | user: UserInterface | null;
19 | input: string;
20 | txs: TransactionInterface[];
21 | }) => {
22 | const router = useRouter();
23 | if (router.isFallback) return <>>;
24 |
25 | const { loading, data } = useInfiniteScroll(
26 | loadMore,
27 | props.txs
28 | );
29 |
30 | async function loadMore() {
31 | if (data.length === 0) return [];
32 | let res: TransactionInterface[] = [];
33 |
34 | if (props.user) {
35 | for (const address of props.user.addresses) {
36 | res.push(
37 | // @ts-ignore
38 | ...(await client.getTransactions(address, data[data.length - 1].id))
39 | );
40 | }
41 | } else
42 | res.push(
43 | // @ts-ignore
44 | ...(await client.getTransactions(props.input, data[data.length - 1].id))
45 | );
46 |
47 | return res;
48 | }
49 |
50 | const notMobile = useMediaPredicate("(min-width: 720px)");
51 |
52 | function shortOnMobile(addr: string) {
53 | if (notMobile) return addr;
54 | else return formatAddress(addr, 12);
55 | }
56 |
57 | return (
58 |
59 |
60 | @{props.user?.username || props.input} - Transactions
61 |
69 |
73 |
74 |
75 | All Transactions
76 |
77 |
105 |
106 | {loading && (
107 |
113 |
114 |
115 |
116 | )}
117 |
118 |
119 | );
120 | };
121 |
122 | export async function getStaticPaths() {
123 | return {
124 | paths: [],
125 | fallback: "blocking",
126 | };
127 | }
128 |
129 | export async function getStaticProps({ params: { input } }) {
130 | const user = (await client.getUser(input)) ?? null;
131 | let txs = [];
132 |
133 | // redirect if the user cannot be found and if it is not and address either
134 | if (!isAddress(input) && !user)
135 | return {
136 | redirect: {
137 | destination: "/404",
138 | permanent: false,
139 | },
140 | };
141 |
142 | if (user && input !== user.username)
143 | return {
144 | redirect: {
145 | destination: `/@${user.username}/transactions`,
146 | permanent: false,
147 | },
148 | };
149 |
150 | if (user) {
151 | for (const address of user.addresses)
152 | txs.push(...(await client.getTransactions(address)));
153 | } else txs.push(...(await client.getTransactions(input)));
154 |
155 | return { props: { user, input, txs }, revalidate: 1 };
156 | }
157 |
158 | export default Transactions;
159 |
--------------------------------------------------------------------------------
/src/store/actions.ts:
--------------------------------------------------------------------------------
1 | import { DisplayTheme } from "@verto/ui/dist/types";
2 | import { IAddressAction } from "./reducers/address";
3 | import { IThemeAction } from "./reducers/theme";
4 |
5 | export function updateAddress(address: string): IAddressAction {
6 | return {
7 | type: "UPDATE_ADDRESS",
8 | payload: {
9 | address,
10 | },
11 | };
12 | }
13 |
14 | export function updateTheme(theme: DisplayTheme | "System"): IThemeAction {
15 | return {
16 | type: "UPDATE_THEME",
17 | payload: { theme },
18 | };
19 | }
20 |
21 | export function signOutClear() {
22 | return {
23 | type: "USER_SIGNOUT",
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from "redux";
2 | import reducers from "./reducers";
3 |
4 | const store = createStore(reducers);
5 |
6 | export default store;
7 |
--------------------------------------------------------------------------------
/src/store/reducers.ts:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import addressReducer from "./reducers/address";
3 | import themeReducer from "./reducers/theme";
4 |
5 | export const plainReducers = {
6 | addressReducer,
7 | themeReducer,
8 | };
9 | const reducers = combineReducers(plainReducers);
10 |
11 | export default reducers;
12 | export type RootState = ReturnType;
13 |
--------------------------------------------------------------------------------
/src/store/reducers/address.ts:
--------------------------------------------------------------------------------
1 | export interface IAddressAction {
2 | type: "UPDATE_ADDRESS" | "USER_SIGNOUT";
3 | payload: {
4 | address: string;
5 | };
6 | }
7 |
8 | export default function addressReducer(
9 | state: string = null,
10 | action: IAddressAction
11 | ) {
12 | switch (action.type) {
13 | case "UPDATE_ADDRESS":
14 | return action.payload.address;
15 |
16 | case "USER_SIGNOUT":
17 | return null;
18 | }
19 |
20 | return state;
21 | }
22 |
--------------------------------------------------------------------------------
/src/store/reducers/theme.ts:
--------------------------------------------------------------------------------
1 | import { DisplayTheme } from "@verto/ui/dist/types";
2 |
3 | export interface IThemeAction {
4 | type: "UPDATE_THEME" | "USER_SIGNOUT";
5 | payload: {
6 | theme: DisplayTheme | "System";
7 | };
8 | }
9 |
10 | export default function themeReducer(
11 | state: DisplayTheme = "Light",
12 | action: IThemeAction
13 | ) {
14 | switch (action.type) {
15 | case "UPDATE_THEME":
16 | return action.payload.theme;
17 |
18 | case "USER_SIGNOUT":
19 | return "Light";
20 | }
21 |
22 | return state;
23 | }
24 |
--------------------------------------------------------------------------------
/src/styles/components/Balance.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .Balance
4 | display: flex
5 | align-items: stretch
6 | justify-content: space-between
7 | min-height: 130px
8 |
9 | .Data
10 | p
11 | font-size: .93em
12 | color: variables.$light-text
13 | text-transform: uppercase
14 | font-weight: 500
15 | margin: 0
16 | bottom: .25em
17 |
18 | &.Address
19 | display: flex
20 | align-items: center
21 | text-transform: none
22 |
23 | button
24 | +variables.resetButton
25 | display: flex
26 | align-items: center
27 | justify-content: center
28 | margin-left: .34em
29 | font-size: 1em
30 | color: variables.$light-text
31 | transition: all .23s ease-in-out
32 |
33 | svg
34 | +variables.sameSizeSvg
35 |
36 | &:hover
37 | opacity: .82
38 |
39 | &:active
40 | transform: scale(.82)
41 |
42 | h1
43 | font-size: variables.$title-font-size
44 | font-weight: 500
45 | color: variables.$foreground-color
46 | margin: 0
47 | line-height: 1em
48 |
49 | b
50 | font-size: .52em
51 | text-transform: uppercase
52 | font-weight: 500
53 |
54 | .Graph
55 | position: relative
56 | max-width: 250px
57 |
58 | @media screen and (max-width: variables.$mobile-screen)
59 | max-width: 170px
--------------------------------------------------------------------------------
/src/styles/components/ChangelogModal.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .Ok
4 | margin: 0 auto
5 |
6 | .ChangelogContainer
7 | position: relative
8 | height: 47.5vh
9 | overflow-y: auto
10 |
11 | h1, h2, h3, h4
12 | color: variables.$foreground-color
13 | font-weight: 600
14 |
15 | a
16 | text-decoration: underline
17 | color: variables.$foreground-color
18 |
19 | p
20 | font-size: 1em
21 | color: variables.$light-text
22 | font-weight: 500
23 |
24 | &::-webkit-scrollbar
25 | width: 4px
26 |
27 | &::-webkit-scrollbar-thumb
28 | background-color: rgba(#000, .25)
29 | border-radius: 2px
30 |
31 | &:hover
32 | background-color: rgba(#000, .4)
33 |
34 | &.Dark::-webkit-scrollbar-thumb
35 | background-color: rgba(#fff, .25)
36 |
37 | &:hover
38 | background-color: rgba(#fff, .4)
--------------------------------------------------------------------------------
/src/styles/components/Footer.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .Footer
4 | display: flex
5 | align-items: center
6 | justify-content: space-between
7 | padding: 1.32em variables.$nav-side-padding
8 | border-top: 1px solid #eaeaea
9 |
10 | @media screen and (max-width: variables.$mobile-screen)
11 | padding: 1.32em variables.$nav-side-padding / 2
12 |
13 | a
14 | text-decoration: none
15 | color: variables.$light-text
16 | transition: all .23s ease-in-out
17 |
18 | &:hover
19 | color: #000
20 |
21 | &.Dark
22 | border-top-color: rgba(156, 160, 177, .18)
23 |
24 | a:hover
25 | color: #fff
26 |
27 | .Title
28 | display: flex
29 | font-size: 1.29em
30 | color: variables.$light-text
31 | font-weight: 400
32 | margin: 0
33 |
34 | span
35 | color: variables.$foreground-color
36 |
37 | a
38 | font-size: 1em
39 |
40 | .Links
41 | display: flex
42 |
43 | a
44 | font-size: .95em
45 | margin: 0 .8em
46 |
47 | @media screen and (max-width: variables.$mobile-screen)
48 | margin: 0 .35em
49 |
50 | &:last-child
51 | margin-right: 0
--------------------------------------------------------------------------------
/src/styles/components/ListingModal.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 | @use "./SearchPopover" as search
3 |
4 | .Modal
5 | .Input
6 | +variables.modalInput
7 |
8 | .Submit
9 | margin: 0 auto
10 |
11 | .Label
12 | +variables.inputLabel
13 |
14 | .TokenLayoutPicker
15 | display: grid
16 | justify-content: space-between
17 | grid-template-columns: repeat(2, 47.5%)
18 | gap: 1.5em 0
19 |
20 | .TokenItem
21 | cursor: pointer
22 |
23 | .Layout
24 | border-radius: 11px
25 | border: 3px solid variables.$skeleton
26 | padding: 7px 10px
27 | margin-bottom: .8em
28 | transition: all .18s ease-in-out
29 |
30 | &.ActiveItem .Layout
31 | border-color: variables.$foreground-color
32 | box-shadow: variables.$lighter-shadow
33 |
34 | span
35 | display: block
36 | text-align: center
37 | font-weight: 500
38 |
39 | &.Name
40 | color: variables.$light-text
41 | text-transform: uppercase
42 | font-size: variables.$labelFontSize + .15em
43 |
44 | &.Description
45 | color: variables.$cec
46 | font-size: variables.$labelFontSize
47 | line-height: 1.2em
48 |
49 | svg
50 | width: 100%
51 |
52 | .SideAction
53 | color: variables.$light-text
54 | text-align: center
55 | margin: 0
56 |
57 | span
58 | text-decoration: underline
59 | cursor: pointer
60 | transition: all .23s ease-in-out
61 |
62 | &:hover
63 | opacity: .8
64 |
65 | .Textarea
66 | +variables.modalTextarea
67 |
68 | .ActionLabel
69 | +variables.flexCenterWithSpace
70 |
71 | div[class~=VertoPopoverWrapper]
72 | display: flex
73 | align-items: center
74 |
75 | div[class~=VertoTooltip]
76 | text-transform: none
77 | font-weight: 500
78 | font-size: .85em
79 |
80 | .AddAction
81 | display: flex
82 | align-items: center
83 | justify-content: center
84 | cursor: pointer
85 | transition: all .23s ease-in-out
86 |
87 | &:hover
88 | opacity: .8
89 |
90 | &:active
91 | transform: scale(.8)
92 |
93 | .PopoverText
94 | font-size: .9em
95 | font-weight: 500
96 | text-align: center
97 | text-transform: none
98 | margin: 0
99 |
100 | .Collaborators
101 | +variables.collaborators
102 |
103 | .SearchPopover
104 | +search.SearchPopover
105 |
106 | .Items
107 | $gap: 1em
108 | display: grid
109 | grid-template-columns: repeat(8, calc(12.5% - #{$gap}))
110 | gap: $gap
111 | justify-content: space-between
112 |
113 | .Item
114 | position: relative
115 | border-radius: 6px
116 | overflow: hidden
117 | cursor: pointer
118 |
119 | img
120 | display: block
121 | width: 100%
122 | user-select: none
123 |
124 | .RemoveItem
125 | position: absolute
126 | +variables.absoluteExtend
127 | background-color: rgba(#000, .5)
128 | opacity: 0
129 | z-index: 2
130 | transition: all .15s ease-in-out
131 |
132 | svg
133 | position: absolute
134 | color: #fff
135 | +variables.absoluteCenter
136 |
137 | &:hover .RemoveItem
138 | opacity: 1
--------------------------------------------------------------------------------
/src/styles/components/Nav.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .Nav
4 | position: relative
5 | display: flex
6 | align-items: center
7 | justify-content: space-between
8 | padding: 1em variables.$nav-side-padding
9 |
10 | @media screen and (max-width: variables.$mobile-screen)
11 | padding: 1em variables.$nav-side-padding / 2
12 |
13 | .Logo
14 | cursor: pointer
15 | box-shadow: 0px 10px 20px variables.$standard-shadow
16 | user-select: none
17 | transition: all .23s ease-in-out
18 |
19 | img
20 | display: block
21 | height: 2.4em
22 |
23 | &:hover
24 | opacity: .82
25 |
26 | &:active
27 | transform: scale(.89)
28 |
29 | .SearchAndConnect
30 | display: flex
31 | align-items: center
32 |
33 | .SearchIcon
34 | color: variables.$light-text
35 | width: 1.2em
36 | height: 1.2em
37 | font-size: 1.2em
38 | cursor: pointer
39 | transition: all .23s ease-in-out
40 |
41 | &:hover
42 | opacity: .8
43 |
44 | &:active
45 | transform: scale(.9)
46 |
47 | .Menu
48 | $menuItemFontSize: 1em
49 | position: absolute
50 | display: flex
51 | align-items: center
52 | top: 50%
53 | left: 50%
54 | width: max-content
55 | transform: translate(-50%, -50%)
56 |
57 | @media screen and (max-width: variables.$mobile-screen)
58 | display: none
59 |
60 | a
61 | font-size: $menuItemFontSize
62 | color: variables.$foreground-color
63 | font-weight: 500
64 | margin: 0 1.15em
65 | cursor: pointer
66 | z-index: 10
67 | text-decoration: none
68 | transition: all .13s ease-in-out
69 |
70 | &.Selected
71 | color: variables.$foreground-reverse-color
72 |
73 | .Selection
74 | position: absolute
75 | background-color: variables.$foreground-color
76 | border-radius: 8px
77 | box-shadow: variables.$standard-shadow
78 | height: $menuItemFontSize * 2.23
79 | min-width: $menuItemFontSize * 2
80 | z-index: 1
81 | top: 50%
82 | transform: translateY(-50%)
83 | transition: all .17s ease-in-out
84 |
85 | .UserPopover
86 | padding: .4em .5em !important
87 | right: 0
88 | margin-top: .65em
89 |
90 | .MenuItem
91 | display: flex
92 | align-items: center
93 | justify-content: left
94 | text-align: left
95 | padding: .7em .88em
96 | border-radius: 6px
97 | cursor: pointer
98 | font-size: 1.1em
99 | color: variables.$foreground-color
100 | font-weight: 500
101 | text-decoration: none
102 | transition: all .23s ease-in-out
103 |
104 | svg
105 | font-size: 1.25em
106 | width: 1.25em
107 | height: 1.25em
108 | margin-right: .55em
109 |
110 | &.DisabledMenuItem
111 | cursor: not-allowed
112 | opacity: .4
113 |
114 | &:hover:not(.DisabledMenuItem)
115 | background-color: rgba(0, 0, 0, .07)
116 |
117 | &.Dark:hover:not(.DisabledMenuItem)
118 | background-color: rgba(255, 255, 255, .07)
119 |
120 | &:active:not(.DisabledMenuItem)
121 | transform: scale(.94)
122 |
123 | .SignOutAlert
124 | color: variables.$light-text
125 | font-size: 1.1em
126 | text-align: center
127 | padding-bottom: 0
128 |
129 | .SignOutBtn
130 | margin: 0 auto
131 |
132 | .ModalInput
133 | div[class~=VertoInputWrapper]
134 | width: calc(100% - 6px)
135 |
136 | div[class~=VertoInputInlineLabel]
137 | min-width: 160px
138 | padding: 0
--------------------------------------------------------------------------------
/src/styles/components/PSTSwitcher.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .PSTSwitcher
4 | position: relative
5 | width: 36%
6 | flex: 1
7 |
8 | .Logo
9 | position: absolute
10 | cursor: pointer
11 |
12 | img
13 | border-radius: 11px
14 | box-shadow: variables.$standard-shadow
15 | max-width: 92px
16 | user-select: none
17 | animation: wobble 7s infinite
18 |
19 | @keyframes wobble
20 | 0%
21 | transform: translate(0%, 0%)
22 |
23 | 15%
24 | transform: translate(-1%, -1%)
25 |
26 | 30%
27 | transform: translate(-1.7%, -2%)
28 |
29 | 45%
30 | ransform: translate(-1%, -1.2%)
31 |
32 | 60%
33 | transform: translate(-.1%, -.47%)
34 |
35 | 75%
36 | transform: translate(.57%, -.1%)
37 |
38 | 100%
39 | transform: translate(0%, 0%)
--------------------------------------------------------------------------------
/src/styles/components/PeriodMenu.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | =periodMenu
4 | display: flex
5 | align-items: center
6 |
7 | span
8 | display: block
9 | text-align: center
10 | padding: .25rem .37rem .2rem
11 | margin: 0 .2rem
12 | cursor: pointer
13 | font-size: .85rem
14 | font-weight: 500
15 | line-height: 1rem
16 | color: variables.$foreground-color
17 | border-radius: 4px
18 | transition: all .23s ease-in-out
19 |
20 | &.Selected
21 | background-color: variables.$foreground-color
22 | color: variables.$foreground-reverse-color
23 | box-shadow: variables.$standard-shadow
--------------------------------------------------------------------------------
/src/styles/components/Search.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .SearchContent
4 | position: fixed
5 | +variables.absoluteExtend
6 | z-index: 1000
7 |
8 | .Overlay
9 | position: absolute
10 | +variables.absoluteExtend
11 | background-color: rgba(#000, .8)
12 |
13 | .Content
14 | position: absolute
15 | top: 1em
16 | left: 50%
17 | width: 44vw
18 | transform: translateX(-50%)
19 |
20 | @media screen and (max-width: variables.$mobile-screen)
21 | width: 90vw
22 |
23 | .LoadMore
24 | background-color: rgba(#000, .08)
25 | border-radius: 8px
26 | padding: .38em .94em
27 | cursor: pointer
28 | width: max-content
29 | font-size: 1.05em
30 | font-weight: 500
31 | color: variables.$light-text
32 | margin: 0 auto
33 | transition: all .23s ease-in-out
34 |
35 | &:hover
36 | background-color: rgba(#000, .14)
37 |
38 | .Results
39 | margin-top: 1.35em
40 | border-radius: .8em
41 | background: variables.$background-color
42 | overflow: hidden
43 | box-shadow: variables.$darker-shadow
44 | max-height: 67vh
45 | overflow-y: auto
46 |
47 | &::-webkit-scrollbar
48 | width: 6px
49 |
50 | &::-webkit-scrollbar-thumb
51 | background-color: rgba(#000, .65)
52 | border-radius: 4px
53 |
54 | p
55 | font-size: 1.1em
56 | font-weight: 400
57 | color: variables.$light-text
58 | text-align: center
59 | margin: 1em auto
60 |
61 | .ResultItem
62 | +variables.flexCenterWithSpace
63 | $sidePadding: 1.45em
64 | padding: .8em $sidePadding
65 | border-bottom: 1px solid #eaeaea
66 | cursor: pointer
67 | text-decoration: none
68 | transition: all .23s ease-in-out
69 |
70 | svg
71 | color: variables.$foreground-color
72 | font-size: 1em
73 |
74 | .TokenData
75 | +variables.flexCenterWithSpace
76 |
77 | img, .CollectionIcon, .NoAvatar
78 | $avatarWidth: 64px
79 | width: $avatarWidth
80 | height: $avatarWidth
81 | user-select: none
82 | margin-right: $sidePadding
83 | color: variables.$light-text
84 |
85 | &.UserAvatar, &.NoAvatar
86 | border-radius: 100%
87 |
88 | &.NoAvatar
89 | +variables.gradientAvatar($avatarWidth)
90 |
91 | h1, h2
92 | margin: 0
93 |
94 | h1
95 | font-size: 1.4em
96 | color: variables.$foreground-color
97 | font-weight: 600
98 | line-height: 1.15em
99 |
100 | h2
101 | color: variables.$light-text
102 | font-weight: 500
103 | font-size: 1em
104 | text-transform: uppercase
105 | line-height: 1em
106 |
107 | &.UserTag
108 | text-transform: none
109 |
110 | &:hover
111 | background-color: rgba(0, 0, 0, .07)
112 |
113 | &:last-child
114 | border-bottom: none
115 |
116 | &.DarkResults
117 | &::-webkit-scrollbar-thumb
118 | background-color: rgba(#fff, .65)
119 |
120 | .ResultItem
121 | border-bottom-color: rgba(156, 160, 177, .18)
122 |
123 | &:hover
124 | background-color: rgba(255, 255, 255, .07)
--------------------------------------------------------------------------------
/src/styles/components/SearchInput.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .SearchInput
4 | $sidePadding: 1.15em
5 | $topPadding: .55em
6 | position: relative
7 | +variables.flexCenterWithSpace
8 | padding: $topPadding $sidePadding
9 | border-radius: 2.5em
10 | border: 3.5px solid variables.$foreground-color
11 | background-color: variables.$background-color
12 | box-shadow: variables.$darker-shadow
13 | color: variables.$cec
14 | font-weight: 500
15 | font-size: 1.3em
16 | cursor: text
17 |
18 | p, svg
19 | font-size: 1em
20 | font-weight: 500
21 | margin: 0
22 | cursor: text
23 | transition: all .23s ease-in-out
24 |
25 | input
26 | position: absolute
27 | +variables.absoluteExtend
28 | padding: $topPadding $sidePadding
29 | width: calc(100% - #{$sidePadding * 2})
30 | outline: none
31 | background-color: transparent
32 | border: none
33 | font-size: 1em
34 | color: variables.$foreground-color
35 |
36 | &:focus-within svg
37 | color: variables.$foreground-color
--------------------------------------------------------------------------------
/src/styles/components/SearchPopover.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | =SearchPopover
4 | .SearchUser
5 | $sidePadding: .55em
6 | $iconSize: 1em
7 | position: relative
8 |
9 | input
10 | display: block
11 | outline: none
12 | border: none
13 | width: 180px
14 | background-color: rgba(0, 0, 0, .07)
15 | border-radius: 8px
16 | color: variables.$light-text
17 | font-weight: 500
18 | font-size: 1em
19 | padding: .3em $sidePadding + $iconSize .3em $sidePadding
20 |
21 | &.WithLeftIcon
22 | width: 160px
23 | padding: .3em $sidePadding * 2 + $iconSize
24 |
25 | svg
26 | position: absolute
27 | right: $sidePadding
28 | top: 50%
29 | z-index: 2
30 | color: variables.$light-text
31 | opacity: .7
32 | font-size: $iconSize + .2em
33 | width: $iconSize
34 | height: $iconSize
35 | transform: translateY(-50%)
36 |
37 | &.LeftIcon
38 | left: $sidePadding
39 | right: unset
40 |
41 | &.DarkSearchUser input
42 | background-color: rgba(255, 255, 255, .07)
43 |
44 | .Result
45 | display: flex
46 | align-items: center
47 | cursor: pointer
48 | margin-bottom: .35em
49 |
50 | &:last-child
51 | margin-bottom: 0
52 |
53 | &:hover
54 | img, .UserInfo
55 | opacity: .8
56 |
57 | $avatarSize: 32px
58 |
59 | img, .GradientAvatar
60 | width: $avatarSize
61 | height: $avatarSize
62 | border-radius: 100%
63 | user-select: none
64 | margin-right: .45em
65 | transition: all .23s ease-in-out
66 |
67 | &.Square
68 | border-radius: 4px
69 |
70 | .GradientAvatar
71 | +variables.gradientAvatar($avatarWidth: $avatarSize)
72 |
73 | .ResultInfo
74 | transition: all .23s ease-in-out
75 |
76 | h1, h2
77 | font-size: 1.05em
78 | font-weight: 500
79 | margin: 0
80 | line-height: 1em
81 | text-transform: none
82 |
83 | h1
84 | color: variables.$foreground-color
85 |
86 | h2
87 | font-size: .8em
--------------------------------------------------------------------------------
/src/styles/components/SetupModal.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .Description
4 | color: variables.$light-text
5 | font-size: 1.1em
6 | text-align: center
7 | padding-bottom: 0
8 |
9 | .Input
10 | div[class~=VertoInputWrapper]
11 | width: calc(100% - 6px)
12 |
13 | div[class~=VertoInputInlineLabel]
14 | min-width: 160px
15 | padding: 0
16 |
17 | .WithIconLabel div[class~=VertoInputWrapper] div[class~=VertoInputInlineLabel]
18 | min-width: 60px
19 |
20 | .Label
21 | display: block
22 | color: variables.$light-text
23 | text-transform: uppercase
24 | margin: 0
25 | bottom: .27em
26 | font-weight: 500
27 | font-size: 1.075em
28 |
29 | .Textarea
30 | +variables.modalTextarea
31 |
32 | .Pfp, .FileInput
33 | +variables.fileInput
34 |
35 | &.FileInput
36 | height: 120px
37 |
38 | .Address
39 | display: flex
40 | align-items: center
41 | justify-content: space-between
42 | font-size: 1em
43 | color: variables.$light-text
44 |
45 | p
46 | font-size: 1.1em
47 | font-weight: 500
48 | margin: 0
49 |
50 | svg
51 | cursor: pointer
52 | transition: all .23s ease-in-out
53 |
54 | &:hover
55 | opacity: .8
56 |
57 | .Advanced
58 | display: flex
59 | align-items: center
60 | color: variables.$light-text
61 | font-weight: 500
62 | font-weight: 1.1em
63 | cursor: pointer
64 | text-decoration: underline
65 |
66 | svg
67 | $sideMargin: .24em
68 | margin-left: $sideMargin
69 | font-size: 1.2em
70 | width: 1.2em
71 | height: 1.2em
72 |
73 | &.LeftIcon
74 | margin:
75 | right: $sideMargin
76 | left: 0
--------------------------------------------------------------------------------
/src/styles/components/Watchlist.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 | @use "./PeriodMenu"
3 |
4 | .Title
5 | display: flex
6 | align-items: center
7 |
8 | .PeriodMenu
9 | +PeriodMenu.periodMenu
10 |
11 | .WatchlistContainer
12 | $gap: 3.5em
13 | display: grid
14 | grid-template-columns: repeat(3, calc(33.333% - #{$gap}))
15 | gap: $gap
16 | justify-content: space-between
17 |
18 | @media screen and (max-width: variables.$mobile-screen)
19 | display: block
20 |
21 | .WatchlistItem
22 | position: relative
23 | $bottomMargin: 3.5em
24 |
25 | &:nth-child(3n+3)
26 | margin-bottom: $bottomMargin
27 |
28 | &:last-child
29 | margin-bottom: 0
30 |
31 | @media screen and (max-width: variables.$mobile-screen)
32 | margin-bottom: $bottomMargin
33 |
34 | .Data
35 | display: flex
36 | align-items: flex-start
37 | justify-content: space-between
38 |
39 | .Ticker, .Price
40 | font-size: 1.2em
41 | font-weight: 500
42 | text-align: left
43 | color: variables.$foreground-color
44 | margin: 0
45 |
46 | &.Price
47 | text-align: right
48 |
49 | span
50 | font-size: .735em
51 |
52 | &.Positive, &.Negative
53 | display: block
54 |
55 | &.Positive
56 | color: variables.$success
57 |
58 | &.Negative
59 | color: variables.$error
60 |
61 | .Graph
62 | position: relative
63 | min-height: 135px
64 | margin-top: .4em
65 |
66 | &.Edit::after
67 | content: "Click to remove"
68 | position: absolute
69 | display: flex
70 | align-items: center
71 | justify-content: center
72 | text-align: center
73 | top: 0
74 | left: 0
75 | right: 0
76 | bottom: 0
77 | background-color: variables.$background-color
78 | color: variables.$foreground-color
79 | opacity: 0
80 | backdrop-filter: blur(5px)
81 | z-index: 10
82 | cursor: pointer
83 | font-weight: 500
84 | transition: all .23s ease-in-out
85 |
86 | &.Edit:hover::after
87 | opacity: .5
88 |
89 | .SelectToken div[class~=VertoSelectWrapper]
90 | width: 100%
--------------------------------------------------------------------------------
/src/styles/global.sass:
--------------------------------------------------------------------------------
1 | @use "./variables"
2 |
3 | body
4 | font-size: 1rem
5 |
6 | @media screen and (min-device-width: 1200px) and (max-device-width: 1550px) and (-webkit-min-device-pixel-ratio: 1)
7 | font-size: .82rem
8 |
9 | @media screen and (max-width: variables.$mobile-screen)
10 | font-size: 1rem
11 |
12 | .Title
13 | display: flex
14 | align-items: center
15 | justify-content: space-between
16 | font-size: variables.$title-font-size
17 | font-weight: 500
18 | line-height: 1.1em
19 | margin: 0
20 |
21 | .Btn
22 | display: flex
23 | align-items: center
24 | color: variables.$light-text
25 | cursor: pointer
26 | font-size: 1em
27 | +variables.resetButton
28 | transition: all .23s ease-in-out
29 |
30 | svg
31 | +variables.sameSizeSvg
32 |
33 | &:hover
34 | opacity: .8
35 |
36 | .ActionSheet
37 | display: flex
38 | align-items: center
39 | justify-content: space-between
40 | font-size: .5em
41 |
42 | .VertoTooltip
43 | line-height: 1.1em
44 | font-size: .75em
45 | border-radius: 10px
46 |
47 | .ShowMore, .NoItemsText
48 | display: flex
49 | align-items: center
50 | justify-content: center
51 | text-align: center
52 | color: variables.$light-text
53 | width: max-content
54 | margin: 0 auto
55 | font-size: 1.08em
56 | text-decoration: none
57 | transition: all .23s ease-in-out
58 |
59 | &.ShowMore
60 | cursor: pointer
61 |
62 | &:hover
63 | opacity: .8
64 |
65 | svg
66 | margin-left: .1em
67 | +variables.sameSizeSvg
68 |
--------------------------------------------------------------------------------
/src/styles/progress.sass:
--------------------------------------------------------------------------------
1 | @use "./variables"
2 | @import "nprogress/nprogress.css"
3 |
4 | #nprogress
5 | .bar
6 | background: variables.$foreground-color !important
7 |
8 | .peg
9 | box-shadow: 0 0 10px variables.$foreground-color, 0 0 5px variables.$foreground-color
10 |
--------------------------------------------------------------------------------
/src/styles/variables.sass:
--------------------------------------------------------------------------------
1 | $background-color: var(--background-color)
2 | $lighter-background-color: var(--lighter-background-color)
3 | $foreground-color: var(--foreground-color)
4 | $foreground-reverse-color: var(--foreground-reverse-color)
5 | $light-text: var(--light-text)
6 | $cec: var(--cec)
7 | $skeleton: var(--skeleton)
8 | $skeleton-shine: var(--skeleton-shine)
9 | $modal-layer-dark: var(--modal-layer-dark)
10 | $success: var(--success)
11 | $warning: var(--warning)
12 | $error: var(--error)
13 |
14 | $standard-shadow: var(--standard-shadow)
15 | $darker-shadow: var(--darker-shadow)
16 | $lighter-shadow: var(--lighter-shadow)
17 | $standard-shadow-hover: var(--standard-shadow-hover)
18 |
19 | $nav-side-padding: 4em
20 | $title-font-size: 2.45em
21 |
22 | $mobile-screen: 720px
23 |
24 | $labelFontSize: 1.075em
25 |
26 | =sameSizeSvg
27 | font-size: 1.2em
28 | width: 1.2em
29 | height: 1.2em
30 |
31 | =resetButton
32 | padding: 0
33 | outline: none
34 | border: none
35 | cursor: pointer
36 | background-color: transparent
37 |
38 | =absoluteExtend
39 | top: 0
40 | left: 0
41 | right: 0
42 | bottom: 0
43 |
44 | =absoluteCenter
45 | top: 50%
46 | left: 50%
47 | transform: translate(-50%, -50%)
48 |
49 | =flexCenterWithSpace
50 | display: flex
51 | align-items: center
52 | justify-content: space-between
53 |
54 | =modalTextarea
55 | border: 3px solid $foreground-color
56 | padding: .8em .85em
57 | border-radius: 13px
58 | height: 150px
59 |
60 | textarea
61 | font-family: "JetBrainsMono", sans-serif
62 | font-weight: 500
63 | font-size: 1.5em
64 | color: $light-text
65 | height: 100%
66 | width: 100%
67 | padding: 0
68 | margin: 0
69 | border: none
70 | outline: none
71 | resize: none
72 | background: transparent
73 |
74 | &::placeholder
75 | opacity: .45
76 |
77 | =gradientAvatar($avatarWidth)
78 | position: relative
79 |
80 | span
81 | position: absolute
82 | top: 50%
83 | left: 50%
84 | color: #fff
85 | font-size: $avatarWidth / 3 * 2
86 | font-weight: 500
87 | text-align: center
88 | transform: translate(-50%, -50%)
89 |
90 | =collaborators($editable: true)
91 | .Collaborator
92 | $size: 32px
93 | position: relative
94 | display: inline-block
95 | transition: all .23s ease-in-out
96 |
97 | @if $editable
98 | cursor: pointer
99 |
100 | img, .AddCollaborator, .Gradient
101 | width: $size
102 | height: $size
103 | border-radius: 100%
104 | user-select: none
105 | z-index: 1
106 | border: 1px solid $background-color
107 | background-color: $background-color
108 |
109 | .Gradient
110 | +gradientAvatar($avatarWidth: $size)
111 |
112 | &.GradientBg::before
113 | position: absolute
114 | +absoluteExtend
115 | content: ""
116 | background-color: $background-color
117 | border-radius: 100%
118 |
119 | &:not(:first-child)
120 | margin-left: -($size / 5 * 3)
121 |
122 | .Remove
123 | position: absolute
124 | +absoluteExtend
125 | background-color: rgba(#000, .5)
126 | z-index: 2
127 | opacity: 0
128 | width: $size
129 | height: $size
130 | border-radius: 100%
131 | transition: all .23s ease-in-out
132 |
133 | svg
134 | position: absolute
135 | color: #fff
136 | +absoluteCenter
137 |
138 | &:hover .Remove
139 | @if $editable
140 | opacity: 1
141 |
142 | &:hover .Collaborator:not(:first-child)
143 | @if $editable
144 | margin-left: .2rem
145 |
146 | =modalInput
147 | div[class~=VertoInputWrapper]
148 | width: calc(100% - 6px)
149 |
150 | div[class~=VertoInputInlineLabel]
151 | min-width: 160px
152 | padding: 0
153 |
154 | =inputLabel
155 | display: block
156 | color: $light-text
157 | text-transform: uppercase
158 | margin: 0
159 | bottom: .27em
160 | font-weight: 500
161 | font-size: $labelFontSize
162 |
163 | =fileInput
164 | position: relative
165 | display: flex
166 | align-items: center
167 | justify-content: center
168 | border: 1px dashed $cec
169 | border-radius: 13px
170 | height: 175px
171 |
172 | &.Error
173 | border-color: #ff0000
174 |
175 | p
176 | position: relative
177 | font-size: 1em
178 | color: $light-text
179 | font-weight: 500
180 | text-align: center
181 | z-index: 1
182 | margin: 0
183 | max-width: 60%
184 |
185 | video, img
186 | $size: 100px
187 | position: absolute
188 | top: 50%
189 | left: calc(#{-$size} - .35em)
190 | width: $size
191 | transform: translateY(-50%)
192 |
193 | input
194 | position: absolute
195 | top: 0
196 | left: 0
197 | right: 0
198 | bottom: 0
199 | opacity: 0
200 | z-index: 10
201 | cursor: pointer
202 | width: 100%
203 |
204 | =markdownStyles
205 | text-align: justify
206 |
207 | a
208 | color: $foreground-color
209 | font-weight: 500
210 | text-decoration: underline
--------------------------------------------------------------------------------
/src/styles/views/404.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .Page
4 | padding: 0
5 |
6 | .Content
7 | position: absolute
8 | top: 45%
9 | left: 50%
10 | width: max-content
11 | transform: translate(-50%, -50%)
12 |
13 | .Title, .Subtitle, p
14 | text-align: center
15 | margin: 0 auto
16 | line-height: .94em
17 |
18 | .Title
19 | font-size: 8em
20 | font-weight: 600
21 | color: variables.$foreground-color
22 |
23 | .Subtitle
24 | font-size: 2.5em
25 | font-weight: 500
26 | color: variables.$light-text
27 |
28 | .SimulateInput
29 | width: 33vw
30 |
31 | @media screen and (max-width: variables.$mobile-screen)
32 | width: 70vw
33 |
34 | p
35 | font-size: 1.1em
36 | font-weight: 400
37 | color: variables.$light-text
38 |
39 | a
40 | color: variables.$foreground-color
41 | text-decoration: none
42 | font-weight: 500
--------------------------------------------------------------------------------
/src/styles/views/app.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .OwnedCollectibles
4 | $sidePadding: 2em
5 | border-radius: 18px
6 | box-shadow: variables.$darker-shadow
7 | padding: 1.4em $sidePadding $sidePadding
8 | background-color: variables.$foreground-color
9 | color: variables.$foreground-reverse-color
10 | overflow: hidden
11 |
12 | .OwnedList
13 | position: relative
14 | display: grid
15 | justify-content: space-between
16 | grid-template-columns: auto auto auto auto
17 |
18 | @media screen and (max-width: variables.$mobile-screen)
19 | grid-template-columns: auto
20 | gap: 2em
21 | justify-content: center
22 |
23 | .NoOwned
24 | position: absolute
25 | top: 50%
26 | left: 50%
27 | text-align: center
28 | transform: translate(-50%, -50%)
29 | margin: 0
30 | font-size: 1.1em
31 | color: variables.$foreground-reverse-color
32 |
33 | a
34 | text-decoration: underline
35 | color: variables.$foreground-reverse-color
36 |
37 | .ViewAll
38 | position: absolute
39 | display: flex
40 | align-items: center
41 | justify-content: center
42 | top: 0
43 | right: -$sidePadding
44 | bottom: 0
45 | width: 25%
46 | background: linear-gradient(to right, rgba(#000, .5), rgba(#000, .75) 8%, rgba(#000, .95) 27%, #000 42%)
47 |
48 | @media screen and (max-width: variables.$mobile-screen)
49 | position: relative
50 | top: unset
51 | right: unset
52 | bottom: unset
53 | background: none
54 | width: auto
55 | margin: 0 auto
56 |
57 | a
58 | color: variables.$foreground-reverse-color
59 | font-weight: 600
60 | font-size: 1.3em
61 | display: flex
62 | align-items: center
63 | width: max-content
64 | cursor: pointer
65 | text-decoration: none
66 | transition: all .23s ease-in-out
67 |
68 | svg
69 | +variables.sameSizeSvg
70 | margin-left: .24em
71 |
72 | &:hover
73 | opacity: .82
74 |
75 | &.DarkOwned
76 | background-color: variables.$lighter-background-color
77 | box-shadow: none
78 | color: variables.$foreground-color
79 |
80 | .OwnedList
81 | .ViewAll
82 | background: linear-gradient(to right, rgba(#060e2b, .5), rgba(#060e2b, .75) 8%, rgba(#060e2b, .95) 27%, #060e2b 42%)
83 |
84 | a
85 | color: variables.$foreground-color
86 |
87 | .NoOwned, .NoOwned a
88 | color: variables.$foreground-color
89 |
90 | .InviteModalText
91 | color: variables.$light-text
92 | font-size: 1.1em
93 | text-align: center
94 | padding-bottom: 0
95 |
96 | .InviteModalUser
97 | display: flex
98 | align-items: center
99 | justify-content: space-between
100 |
101 | .ViewBlock
102 | display: flex
103 | align-items: center
104 | font-size: 1.5em
105 | color: variables.$light-text
106 | cursor: pointer
107 | text-decoration: none
108 | transition: all .23s ease-in-out
109 |
110 | &:active
111 | transform: scale(.8)
112 |
113 | &:hover
114 | opacity: .8
115 |
116 | svg
117 | font-size: 1.5em
118 | width: 1em
119 | height: 1em
120 |
121 | .InviteModalActions
122 | display: flex
123 | align-items: center
124 | justify-content: space-around
--------------------------------------------------------------------------------
/src/styles/views/art.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | $formPadding: 1.35em
4 |
5 | .Title
6 | text-align: center
7 | margin: 0
8 | font-size: variables.$title-font-size
9 | color: variables.$foreground-color
10 | font-weight: 500
11 |
12 | @media screen and (max-width: variables.$mobile-screen)
13 | font-size: variables.$title-font-size - .3em
14 |
15 | .Actions
16 | position: absolute
17 | bottom: 1.5em
18 | right: 1.5em
19 |
20 | button
21 | position: relative
22 | display: block
23 | cursor: pointer
24 | color: #fff
25 | background-color: rgba(#000, .6)
26 | border-radius: 100%
27 | width: 34px
28 | height: 34px
29 | outline: none
30 | border: none
31 | margin-bottom: .65em
32 | transition: all .23s ease-in-out
33 |
34 | &:last-child
35 | margin-bottom: 0
36 |
37 | &:hover
38 | background-color: rgba(#000, .8)
39 |
40 | &:active
41 | transform: scale(.92)
42 |
43 | svg
44 | position: absolute
45 | top: 50%
46 | left: 50%
47 | font-size: 1.1em
48 | width: 66%
49 | height: 66%
50 | transform: translate(-50%, -50%)
51 |
52 | &.Octicon svg
53 | font-size: 1em
54 | width: 55%
55 | height: 55%
56 |
57 | &.ActionsDark button
58 | color: #000
59 | background-color: rgba(#fff, .8)
60 |
61 | &:hover
62 | background-color: rgba(#fff, .6)
63 |
64 | .Layout
65 | display: flex
66 | align-items: stretch
67 | justify-content: space-between
68 | $cardWidth: 48%
69 |
70 | @media screen and (max-width: variables.$mobile-screen)
71 | display: block
72 |
73 | .Form
74 | display: flex
75 | flex-direction: column
76 | justify-content: space-between
77 | width: calc(#{$cardWidth} - #{$formPadding * 2})
78 | padding: $formPadding
79 |
80 | @media screen and (max-width: variables.$mobile-screen)
81 | width: auto
82 | min-height: 55vh
83 |
84 | p, .FormTitle
85 | color: variables.$foreground-color
86 | font-size: 1.1em
87 | font-weight: 400
88 | margin: 0
89 |
90 | .FormTitle
91 | font-weight: 500
92 | color: variables.$light-text
93 |
94 | .Price
95 | margin: 0
96 | color: variables.$foreground-color
97 | font-weight: 500
98 |
99 | .FormTitle
100 | margin-left: .7em
101 | font-size: .5em
102 |
103 | .Description
104 | +variables.markdownStyles
105 |
106 | .FormBtn
107 | width: 100%
108 |
109 | .Avatar
110 | cursor: pointer
111 | transition: all .23s ease-in-out
112 |
113 | &:active
114 | transform: scale(.95)
115 |
116 | div[class~=VertoInputWrapper]
117 | width: calc(100% - 6px)
118 |
119 | div[class~=VertoInputInlineLabel]
120 | min-width: 160px
121 | padding: 0
122 |
123 | &.FullScreenLayout
124 | display: block
125 | position: relative
126 | background-color: variables.$background-color
127 |
128 | .Form, .Preview
129 | display: none
130 |
131 | .Preview
132 | position: relative
133 | width: $cardWidth
134 | padding: 0
135 | top: 50%
136 | background-color: variables.$foreground-color
137 | overflow: hidden
138 |
139 | @media screen and (max-width: variables.$mobile-screen)
140 | width: auto
141 | min-height: 30vh
142 | margin-bottom: 3em
143 |
144 | img, video
145 | position: absolute
146 | max-width: 100%
147 | max-height: 100%
148 | top: 50%
149 | left: 50%
150 | user-select: none
151 | transform: translate(-50%, -50%)
152 |
153 | .FullScreenPreview
154 | position: absolute
155 | max-width: 90%
156 | max-height: 90%
157 | top: 50%
158 | left: 50%
159 | user-select: none
160 | box-shadow: variables.$standard-shadow
161 | transform: translate(-50%, -50%)
162 |
163 | .Bits
164 | $childWidth: calc(50% - #{$formPadding})
165 | display: grid
166 | justify-content: space-between
167 | grid-template-columns: $childWidth $childWidth
168 |
169 | .NoSale
170 | text-align: center
171 | margin: 0
172 | color: variables.$light-text
173 | font-size: 1.1em
--------------------------------------------------------------------------------
/src/styles/views/collection.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 | @use "../components/SearchPopover" as search
3 |
4 | =basicEditStyles
5 | color: variables.$light-text
6 | cursor: pointer
7 | transition: all .23s ease-in-out
8 |
9 | &:hover
10 | opacity: .8
11 |
12 | &:active
13 | transform: scale(.8)
14 |
15 | .Title
16 | display: flex
17 | justify-content: space-between
18 | align-items: center
19 | font-size: variables.$title-font-size + .25em
20 |
21 | .EditIcon
22 | font-size: .5em
23 | width: 1em
24 | height: 1em
25 | +basicEditStyles
26 |
27 | .Subtitle
28 | font-size: 1.07em
29 | color: variables.$light-text
30 | font-weight: 400
31 | margin: 0
32 |
33 | .Collaborators
34 | position: relative
35 | +variables.collaborators($editable: false)
36 |
37 | &.EditCollaborators
38 | +variables.collaborators($editable: true)
39 |
40 | .AddCollaborator
41 | font-size: 1em
42 | width: 30px !important
43 | height: 30px !important
44 | color: variables.$light-text
45 | opacity: 0
46 | transition: all .23s ease-in-out
47 |
48 | &:active
49 | transform: scale(.8)
50 |
51 | &:hover .AddCollaborator
52 | opacity: 1
53 |
54 | .SaveCollaborators
55 | position: absolute
56 | right: 0
57 | top: 50%
58 | transform: translateY(-50%)
59 |
60 | svg
61 | font-size: 1em
62 | width: 1.5em
63 | height: 1.5em
64 | +basicEditStyles
65 |
66 | .Items
67 | display: grid
68 | justify-content: space-between
69 | grid-template-columns: auto auto auto auto
70 |
71 | @media screen and (max-width: variables.$mobile-screen)
72 | justify-content: center
73 | grid-template-columns: auto
74 |
75 | .Item
76 | $bottomMargin: 3.5em
77 | position: relative
78 |
79 | &:nth-child(4n+4)
80 | margin-bottom: $bottomMargin
81 |
82 | &:last-child
83 | margin-bottom: 0
84 |
85 | @media screen and (max-width: variables.$mobile-screen)
86 | margin-bottom: $bottomMargin
87 |
88 | .MinusIcon
89 | $size: 2em
90 | position: absolute
91 | background-color: #ff0000
92 | top: -($size / 3)
93 | right: -($size / 3)
94 | color: #fff
95 | border-radius: 100%
96 | cursor: pointer
97 | display: flex
98 | align-items: center
99 | justify-content: center
100 | box-shadow: variables.$lighter-shadow
101 | z-index: 100
102 | transition: all .23s ease-in-out
103 |
104 | &:active
105 | transform: scale(.8)
106 |
107 | svg
108 | font-size: 1.3em
109 | width: 1em
110 | height: 1em
111 |
112 | .SearchPopover
113 | +search.SearchPopover
114 |
115 | .AddNew
116 | margin: 0 auto
117 | color: variables.$light-text
118 | text-decoration: underline
119 | width: max-content
120 | text-align: center
121 | cursor: pointer
122 | transition: all .23s ease-in-out
123 |
124 | &:hover
125 | opacity: .83
126 |
127 | .ModalInput
128 | +variables.modalInput
129 |
130 | .ModalTextarea
131 | +variables.modalTextarea
132 |
133 | .InputLabel
134 | +variables.inputLabel
135 |
136 | .ActionSheet
137 | display: flex
138 | align-items: center
139 | justify-content: flex-end
140 |
141 | .ActionIcon
142 | width: 1.35em
143 | height: 1.35em
144 | margin-left: .45em
145 | +basicEditStyles
146 |
147 | &:first-child
148 | margin-left: 0
--------------------------------------------------------------------------------
/src/styles/views/community.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 | @use "../components/PeriodMenu"
3 |
4 | $detailsWidth: 62.5%
5 |
6 | .Wrapper
7 | display: flex
8 | align-items: flex-start
9 | justify-content: space-between
10 |
11 | @media screen and (max-width: variables.$mobile-screen)
12 | display: block
13 |
14 | .TokenDetails
15 | width: $detailsWidth
16 |
17 | @media screen and (max-width: variables.$mobile-screen)
18 | width: auto
19 | margin-bottom: 2em
20 |
21 | .Name
22 | font-size: variables.$title-font-size * .92
23 | font-weight: 500
24 | color: variables.$foreground-color
25 | line-height: 1em
26 | margin: 0
27 |
28 | span
29 | color: variables.$light-text
30 | text-transform: uppercase
31 | font-size: .5em
32 |
33 | .Price
34 | font-size: variables.$title-font-size * 1.575
35 | font-weight: 600
36 | color: variables.$foreground-color
37 | line-height: 1em
38 | margin: 0
39 |
40 | .PeriodMenu
41 | +PeriodMenu.periodMenu
42 |
43 | .Graph
44 | position: relative
45 | min-height: 285px
46 | width: 100%
47 |
48 | @media screen and (max-width: variables.$mobile-screen)
49 | min-height: 250px
50 |
51 | .Paragraph
52 | font-size: 1.1em
53 | color: variables.$light-text
54 | text-align: justify
55 | margin: 0
56 |
57 | ul li
58 | position: relative
59 | list-style: none
60 | margin: .2em 0
61 |
62 | &:last-child
63 | margin-bottom: 0
64 |
65 | &::before
66 | content: "-"
67 | position: absolute
68 | left: -1.25em
69 | color: variables.$foreground-color
70 |
71 | a
72 | color: variables.$foreground-color
73 | text-decoration: underline
74 |
75 | .ActionCard
76 | position: sticky !important
77 | top: 4em
78 | right: 0
79 | width: 100% - $detailsWidth - 8% !important
80 | min-width: unset !important
81 |
82 | @media screen and (max-width: variables.$mobile-screen)
83 | position: relative
84 | width: auto !important
85 |
86 | div[class~=VertoInputWrapper]
87 | width: calc(100% - 6px)
88 | min-width: unset
89 |
90 | div[class~=VertoInputInlineLabel]
91 | min-width: 130px
92 | padding: 0
93 |
94 | @media screen and (max-width: 1300px)
95 | min-width: 110px
96 |
97 | @media screen and (max-width: variables.$mobile-screen)
98 | min-width: 160px
--------------------------------------------------------------------------------
/src/styles/views/home.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | $titleSize: 2.7em
4 |
5 | .Landing
6 | display: flex
7 | align-items: center
8 | justify-content: space-between
9 |
10 | .Hero
11 | h1
12 | font-size: $titleSize
13 | font-weight: 400
14 | line-height: 1.15em
15 | margin: 0
16 |
17 | [class~=typed-cursor]
18 | font-weight: 300
19 | color: variables.$light-text
20 |
21 | p
22 | color: variables.$light-text
23 | font-weight: 400
24 | font-size: 1.13em
25 | margin: 0
26 |
27 | a
28 | color: inherit
29 |
30 | .HeroBtns
31 | display: flex
32 | align-items: center
33 |
34 | .FeaturedToken
35 | display: flex
36 | align-items: center
37 | width: 40%
38 |
39 | @media screen and (max-width: variables.$mobile-screen)
40 | display: none
41 |
42 | .Section
43 | .Title
44 | font-weight: 500
45 | font-size: $titleSize
46 | margin: 0
47 | bottom: .55em
48 |
49 | @media screen and (max-width: variables.$mobile-screen)
50 | margin-top: 1em
51 |
52 | .Description
53 | font-size: 1.1em
54 | font-weight: 400
55 | text-align: justify
56 |
57 | a
58 | color: inherit
59 | text-decoration: underline
60 |
61 | ul
62 | list-style-type: "-"
63 |
64 | li
65 | padding-left: .55em
66 |
67 | &::marker
68 | padding-right: .2em
69 |
70 | .PSTs
71 | display: flex
72 |
73 | @media screen and (max-width: variables.$mobile-screen)
74 | display: none
75 |
76 | .Text
77 | flex: 2
78 |
--------------------------------------------------------------------------------
/src/styles/views/orbit.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | $titleMarginLeft: .3em
4 |
5 | .OrbitTitle
6 | h1
7 | display: flex
8 | align-items: center
9 | font-size: variables.$title-font-size
10 | color: variables.$light-text
11 | font-weight: 500
12 | line-height: 1.1em
13 | margin: 0
14 | bottom: .2em
15 |
16 | .Type
17 | font-size: .6em
18 | text-transform: uppercase
19 | color: variables.$foreground-reverse-color
20 | background-color: variables.$foreground-color
21 | margin-left: .6em
22 | padding: .30em .55em
23 | line-height: 1em
24 | border-radius: 8px
25 | box-shadow: variables.$standard-shadow
26 |
27 | p
28 | display: flex
29 | align-items: center
30 | color: variables.$foreground-color
31 | font-size: .9em
32 | font-weight: 500
33 | margin: 0
34 |
35 | .Status
36 | $size: 8px
37 | display: block
38 | width: $size
39 | height: $size
40 | border-radius: 100%
41 | background-color: variables.$foreground-color
42 | opacity: .6
43 |
44 | &.Status_error
45 | opacity: 1
46 | background-color: variables.$error
47 |
48 | &.Status_pending
49 | opacity: 1
50 | background-color: variables.$warning
51 |
52 | &.Status_success
53 | opacity: 1
54 | background-color: variables.$success
55 |
56 | &.Owner
57 | @media screen and (max-width: variables.$mobile-screen)
58 | align-items: flex-start
59 |
60 | div[class~=VertoTooltip]
61 | text-transform: capitalize
62 |
63 | a
64 | color: variables.$light-text
65 | margin-left: $titleMarginLeft
66 | text-decoration: none
67 |
68 | &.TradingPostAddress
69 | color: variables.$foreground-color
70 |
71 | &:hover
72 | text-decoration: underline
73 |
74 | .TradingPostData
75 | display: grid
76 | grid-template-columns: repeat(4, 25%)
77 | margin-left: $titleMarginLeft
78 |
79 | @media screen and (max-width: variables.$mobile-screen)
80 | grid-template-columns: repeat(2, 50%)
81 |
82 | .Info
83 | color: variables.$foreground-color
84 | margin-bottom: 1em
85 | font-size: 1.45em
86 |
87 | @media screen and (max-width: variables.$mobile-screen)
88 | font-size: 1.3em
89 |
90 | span
91 | display: block
92 | font-size: .52em
93 | color: variables.$light-text
94 | line-height: 1em
--------------------------------------------------------------------------------
/src/styles/views/space.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .Featured
4 | $sidePadding: 3em
5 | $topPadding: 2.8em
6 | $bottomPadding: 3.2em
7 | position: relative
8 | background-color: variables.$foreground-color
9 | color: variables.$foreground-reverse-color
10 | border-radius: 15px
11 | box-shadow: variables.$standard-shadow
12 | height: 250px
13 | overflow: hidden
14 |
15 | @media screen and (max-width: variables.$mobile-screen)
16 | height: 450px
17 |
18 | .Paginator
19 | position: absolute
20 | display: flex
21 | bottom: 1.2em
22 | left: 50%
23 | transform: translateX(-50%)
24 |
25 | span
26 | margin: 0 .5em
27 | border: 1px solid variables.$foreground-reverse-color
28 | border-radius: 100%
29 | width: 6px
30 | height: 6px
31 | cursor: pointer
32 | transition: all .23s ease-in-out
33 |
34 | &.ActivePage
35 | background-color: variables.$foreground-reverse-color
36 |
37 | &:hover
38 | opacity: .8
39 |
40 | &:active
41 | transform: scale(.9)
42 |
43 | .FeaturedItem
44 | position: absolute
45 | display: flex
46 | align-items: center
47 | justify-content: space-between
48 | cursor: pointer
49 | top: 50%
50 | width: calc(100% - #{$sidePadding * 2})
51 | padding: $topPadding $sidePadding $bottomPadding
52 |
53 | @media screen and (max-width: variables.$mobile-screen)
54 | align-items: flex-start
55 |
56 | .TokenInfo
57 | display: flex
58 | align-items: center
59 |
60 | @media screen and (max-width: variables.$mobile-screen)
61 | display: block
62 |
63 | img
64 | height: 110px
65 | user-select: none
66 | margin-right: $sidePadding
67 | border-radius: 11px
68 |
69 | @media screen and (max-width: variables.$mobile-screen)
70 | margin:
71 | right: 0
72 | bottom: $sidePadding
73 |
74 | h1
75 | font-size: variables.$title-font-size
76 | font-weight: 500
77 | margin: 0
78 | line-height: 1em
79 |
80 | h2
81 | font-size: 1.5em
82 | text-transform: uppercase
83 | color: variables.$cec
84 | font-weight: 500
85 | line-height: 1.1em
86 | margin: 0
87 | bottom: .35em
88 |
89 | p
90 | font-size: 1em
91 | color: variables.$cec
92 | max-width: 300px
93 | margin: 0
94 |
95 | .PriceData
96 | h2
97 | font-size: 1.45em
98 | margin: 0
99 | bottom: .3em
100 | font-weight: 500
101 | text-align: right
102 | line-height: 1em
103 | color: variables.$cec
104 |
105 | .GraphData
106 | max-width: 200px
107 | min-height: 100px
108 |
109 | @media screen and (max-width: variables.$mobile-screen)
110 | display: none
111 |
112 | &.DarkFeatured
113 | background-color: variables.$lighter-background-color
114 | box-shadow: none
115 |
116 | .Paginator
117 | span
118 | border-color: variables.$foreground-color
119 |
120 | .ActivePage
121 | background-color: variables.$foreground-color
122 |
123 | .FeaturedItem .TokenInfo h1
124 | color: variables.$foreground-color
125 |
126 | .Cards
127 | display: grid
128 | justify-content: space-between
129 | grid-template-columns: auto auto auto auto
130 |
131 | @media screen and (max-width: variables.$mobile-screen)
132 | display: block
133 |
134 | .Card
135 | $marginBottom: 2.75em
136 |
137 | @media screen and (max-width: variables.$mobile-screen)
138 | display: flex
139 | justify-content: center
140 | margin-bottom: $marginBottom
141 |
142 | &.AllTokensCard
143 | margin-bottom: $marginBottom
144 |
145 | div[class~=VertoAssetCard]
146 | height: 100%
147 |
148 | .Search
149 | color: variables.$light-text
150 | font-size: .7em
151 | cursor: pointer
152 | transition: all .23s ease-in-out
153 |
154 | &:hover
155 | opacity: .8
156 |
157 | &:active
158 | transform: scale(.9)
--------------------------------------------------------------------------------
/src/styles/views/swap.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .SwapContent
4 | display: flex
5 | align-items: stretch
6 | justify-content: space-between
7 |
8 | @media screen and (max-width: variables.$mobile-screen)
9 | display: block
10 |
11 | .Graph
12 | position: relative
13 | width: 50%
14 |
15 | @media screen and (max-width: variables.$mobile-screen)
16 | width: 100%
17 | height: 25vh
18 | margin-bottom: 2em
19 |
20 | .GraphMode
21 | position: absolute
22 | top: 0
23 | right: 0
24 | display: flex
25 | align-items: center
26 |
27 | .PriceData
28 | margin-right: .65em
29 |
30 | span
31 | display: block
32 | color: variables.$light-text
33 | text-transform: uppercase
34 | font-size: .9em
35 | line-height: 1.25em
36 | text-align: right
37 |
38 | .SwapForm
39 | $sidePadding: 1.5em
40 | position: relative
41 | width: 39%
42 | padding: $sidePadding
43 |
44 | .SwapLogo
45 | cursor: pointer
46 | width: max-content
47 | margin:
48 | left: auto
49 | right: 2em
50 | transition: all .23s ease-in-out
51 |
52 | &:hover
53 | opacity: .7
54 |
55 | svg
56 | color: variables.$foreground-color
57 | font-size: 1.6em
58 | width: 1em
59 | height: 1em
60 | transform: rotate(90deg)
61 |
62 | .SwapInput
63 | div[class~=VertoInputWrapper]
64 | width: calc(100% - 6px)
65 |
66 | div[class~=VertoInputInlineLabel]
67 | min-width: 160px
68 | padding: 0
69 |
70 | .UnitSelect
71 | width: 100%
72 |
73 | div[class~=VertoSelectWrapper]
74 | width: 100%
75 |
76 | span[class~=VertoSelectArrow]
77 | right: 0
78 |
79 | .BlockedCountry
80 | position: absolute
81 | display: flex
82 | +variables.absoluteExtend
83 | z-index: 100
84 | background-color: rgba(#fff, .5)
85 | padding: $sidePadding
86 | align-items: center
87 | justify-content: center
88 | backdrop-filter: blur(5px)
89 |
90 | &.BlockedCountryDark
91 | background-color: rgba(#030a23, .67)
92 |
93 | h1, p
94 | text-align: center
95 | margin: 0
96 | color: variables.$foreground-color
97 |
98 | h1
99 | font-size: 2em
100 | font-weight: 700
101 |
102 | p
103 | font-size: 1.05em
104 | font-weight: 500
105 |
106 | .TradingPostSelect
107 | font-size: #{1em / variables.$title-font-size}em
108 | line-height: 1em
109 |
110 | .TradingPostInfo
111 | display: flex
112 | align-items: center
113 |
114 | a
115 | display: flex
116 | align-items: center
117 | cursor: pointer
118 | font-size: 1.15em
119 | text-decoration: none
120 | color: inherit
121 | transition: all .23s ease-in-out
122 |
123 | &:hover
124 | opacity: .88
125 |
126 | &:active
127 | transform: scale(.88)
128 |
129 | svg
130 | font-size: 1.35em
131 | width: 1em
132 | height: 1em
133 |
134 | .ConfirmModal
135 | p
136 | text-align: center
137 |
138 | .SubmitBtn
139 | margin: 0 auto
140 |
141 | .SwapIcon
142 | display: flex
143 | align-items: center
144 | justify-content: center
145 | font-size: 1.1em
146 |
147 | svg
148 | font-size: 1em
149 | width: 1em
150 | height: 1em
151 | transform: rotate(90deg)
--------------------------------------------------------------------------------
/src/styles/views/user.module.sass:
--------------------------------------------------------------------------------
1 | @use "../variables"
2 |
3 | .AvatarSection
4 | display: flex
5 | align-items: center
6 | justify-content: space-between
7 |
8 | @media screen and (max-width: variables.$mobile-screen)
9 | display: block
10 |
11 | .Avatar
12 | font-size: 1.3em
13 |
14 | h1
15 | div
16 | display: flex
17 | align-items: center
18 |
19 | &[class~=VertoTooltip]
20 | line-height: 1.15rem
21 | margin-bottom: .4em
22 |
23 | @media screen and (max-width: variables.$mobile-screen)
24 | margin-bottom: 1em
25 |
26 | h1
27 | margin-bottom: 0 !important
28 | font-size: 2em !important
29 |
30 | .Bio
31 | font-size: 1.1em
32 | color: variables.$light-text
33 | width: 60%
34 | margin-bottom: 2em
35 | +variables.markdownStyles
36 |
37 | @media screen and (max-width: variables.$mobile-screen)
38 | width: 100%
39 |
40 | .Links a
41 | color: variables.$light-text
42 | margin: 0 .75em
43 | font-size: 1.2em
44 | transition: all .23s ease-in-out
45 |
46 | &:hover
47 | opacity: .8
48 |
49 | svg
50 | font-size: 1em
51 | width: 1em
52 | height: 1em
53 |
54 | &:first-child
55 | margin-left: 0
56 |
57 | .Creations
58 | display: grid
59 | justify-content: space-between
60 | grid-template-columns: auto auto auto auto
61 |
62 | @media screen and (max-width: variables.$mobile-screen)
63 | justify-content: center
64 | grid-template-columns: auto
65 |
66 | .CreationItem
67 | $bottomMarginCreationItem: 3.5em
68 |
69 | &:nth-child(4n+4)
70 | margin-bottom: $bottomMarginCreationItem
71 |
72 | &:last-child
73 | margin-bottom: 0
74 |
75 | @media screen and (max-width: variables.$mobile-screen)
76 | margin-bottom: $bottomMarginCreationItem
77 |
78 | .Transactions
79 | width: 100%
80 | color: variables.$light-text
81 | font-size: 1.1em
82 | border-collapse: separate
83 | border-spacing: 0 1em
84 |
85 | .TxType
86 | background-color: variables.$foreground-color
87 | color: variables.$foreground-reverse-color
88 | padding: .13em .44em
89 | border-radius: 8px
90 | box-shadow: variables.$standard-shadow
91 | text-transform: uppercase
92 | font-weight: 500
93 | width: 42px
94 | text-align: center
95 |
96 | .TxID
97 | display: flex
98 | align-items: center
99 | padding-left: 1em
100 |
101 | a
102 | text-decoration: none
103 | color: variables.$light-text
104 | transition: all .23s ease-in-out
105 |
106 | &:hover
107 | opacity: .8
108 |
109 | .Status
110 | $statusSize: 8px
111 | display: block
112 | margin-left: $statusSize
113 | width: $statusSize
114 | height: $statusSize
115 | border-radius: 100%
116 | background-color: variables.$background-color
117 | opacity: .6
118 |
119 | &.Status_error
120 | opacity: 1
121 | background-color: variables.$error
122 |
123 | &.Status_pending
124 | opacity: 1
125 | background-color: variables.$warning
126 |
127 | &.Status_success
128 | opacity: 1
129 | background-color: variables.$success
130 |
131 | .StatusTooltip
132 | text-transform: capitalize
133 | font-size: .8em
134 | padding: .2em .6em
135 | border-radius: 10px
136 |
137 | .TxAmount
138 | text-align: right
--------------------------------------------------------------------------------
/src/utils/animations.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | /**
4 | * Animation standard for card lists
5 | *
6 | * @param i List item key
7 | *
8 | * @returns Animation bindings for framer-motion
9 | */
10 | export const cardListAnimation = (i: number) => ({
11 | initial: { opacity: 0, scale: 0.83 },
12 | animate: { opacity: 1, scale: 1 },
13 | exit: { opacity: 0, scale: 0.83 },
14 | transition: {
15 | duration: 0.23,
16 | ease: "easeInOut",
17 | delay: i * 0.023,
18 | },
19 | });
20 |
21 | /**
22 | * Animation standard for normal cards
23 | *
24 | * @param i Card key
25 | *
26 | * @returns Animation bindings for framer-motion
27 | */
28 | export const cardAnimation = (i: number) => ({
29 | initial: { opacity: 0, translateY: 5 },
30 | animate: { opacity: 1, translateY: 0 },
31 | exit: { opacity: 0, translateY: 5 },
32 | transition: {
33 | duration: 0.25,
34 | ease: "easeInOut",
35 | delay: i * 0.058,
36 | },
37 | });
38 |
39 | /**
40 | * Opacity animations
41 | *
42 | * @param i Optional item key
43 | *
44 | * @returns Animation bindings for framer-motion
45 | */
46 | export const opacityAnimation = (i = 0) => ({
47 | initial: { opacity: 0 },
48 | animate: { opacity: 1 },
49 | exit: { opacity: 0 },
50 | transition: {
51 | duration: 0.23,
52 | ease: "easeInOut",
53 | delay: i * 0.058,
54 | },
55 | });
56 |
57 | /**
58 | * Count up from a number, to a number
59 | */
60 | export const useCountUp = ({
61 | start = 0,
62 | end,
63 | duration = 1400,
64 | frameDuration = 1000 / 60,
65 | decimals = 5,
66 | }: CountUpProps) => {
67 | const [state, setState] = useState(start);
68 | const [toEnd, setToEnd] = useState(end);
69 | const [multiply, setMultiply] = useState(0);
70 |
71 | const decimalLength = (val: number) =>
72 | val.toString().split(".")[1]?.length ?? 0;
73 | const easeOut = (t: number) => t * (2 - t);
74 |
75 | useEffect(() => {
76 | const newVal =
77 | Math.round(end * Math.pow(10, decimals)) / Math.pow(10, decimals);
78 | setToEnd(newVal);
79 | setMultiply(Math.pow(10, decimalLength(newVal)) ?? 1);
80 | }, [end, decimals]);
81 |
82 | // TODO start from start param
83 | useEffect(() => {
84 | if (toEnd === 0) return;
85 |
86 | let frame = 0;
87 |
88 | const totalFrames = Math.round(duration / frameDuration);
89 | const counter = setInterval(() => {
90 | frame++;
91 | setState(
92 | Math.round(toEnd * multiply * easeOut(frame / totalFrames) + start)
93 | );
94 |
95 | if (frame === totalFrames) clearInterval(counter);
96 | }, frameDuration);
97 | }, [toEnd]);
98 |
99 | return state / multiply;
100 | };
101 |
102 | interface CountUpProps {
103 | start?: number;
104 | end: number;
105 | duration?: number;
106 | frameDuration?: number;
107 | decimals?: number;
108 | }
109 |
--------------------------------------------------------------------------------
/src/utils/arconnect.ts:
--------------------------------------------------------------------------------
1 | import { PermissionType } from "arconnect";
2 |
3 | export const permissions: PermissionType[] = [
4 | "ACCESS_ADDRESS",
5 | "ACCESS_ALL_ADDRESSES",
6 | "SIGN_TRANSACTION",
7 | ];
8 |
--------------------------------------------------------------------------------
/src/utils/arweave.ts:
--------------------------------------------------------------------------------
1 | import { GQLEdgeTransactionInterface } from "ardb/lib/faces/gql";
2 | import Arweave from "arweave";
3 | import axios from "axios";
4 | import moment from "moment";
5 | import ArDB from "ardb";
6 |
7 | export const client = new Arweave({
8 | host: "arweave.net",
9 | port: 443,
10 | protocol: "https",
11 | });
12 |
13 | const gql = new ArDB(client);
14 |
15 | export const ROOT_URL = "https://verto.exchange";
16 | export const CACHE_URL = "https://v2.cache.verto.exchange";
17 | export const COMMUNITY_CONTRACT = "t9T7DIOGxx4VWXoCEeYYarFYeERTpWIC1V3y-BPZgKE";
18 | export const COLLECTION_CONTRACT_SRC =
19 | "HGV5DRY45TPTUJ0GERYgaaJoTXdMpU_lhtnI0ehJbe4";
20 | export const COLLECTIBLE_CONTRACT_SRC =
21 | "K2s2nciTrl4pk2Nvlvzba-DnWrkm45bo4qMPR7zFpzI";
22 |
23 | export const balanceHistory = async (
24 | address: string
25 | ): Promise<{ [date: string]: number }> => {
26 | const inTxs = (await gql
27 | .search()
28 | .to(address)
29 | .limit(100)
30 | .find()) as GQLEdgeTransactionInterface[];
31 | const outTxs = (await gql
32 | .search()
33 | .from(address)
34 | .limit(100)
35 | .find()) as GQLEdgeTransactionInterface[];
36 |
37 | const txs = inTxs
38 | .concat(outTxs)
39 | .filter(({ node }) => !!node?.block?.timestamp)
40 | .sort((a, b) => b.node.block.timestamp - a.node.block.timestamp)
41 | .slice(0, 100);
42 | let balance = parseFloat(
43 | client.ar.winstonToAr(await client.wallets.getBalance(address))
44 | );
45 |
46 | const res = {
47 | [moment().format("MMM DD, YYYY - HH:mm")]: balance,
48 | };
49 |
50 | for (const { node } of txs) {
51 | balance += parseFloat(node.fee.ar);
52 |
53 | if (node.owner.address === address) {
54 | balance += parseFloat(node.quantity.ar);
55 | } else {
56 | balance -= parseFloat(node.quantity.ar);
57 | }
58 |
59 | res[
60 | moment(node.block.timestamp * 1000).format("MMM DD, YYYY - HH:mm")
61 | ] = balance;
62 | }
63 |
64 | return res;
65 | };
66 |
67 | export const arPrice = async (): Promise => {
68 | const { data: gecko } = await axios.get(
69 | "https://api.coingecko.com/api/v3/simple/price?ids=arweave&vs_currencies=usd"
70 | );
71 |
72 | return gecko.arweave.usd;
73 | };
74 |
75 | export const isAddress = (addr: string) => /[a-z0-9_-]{43}/i.test(addr);
76 |
--------------------------------------------------------------------------------
/src/utils/format.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Beautify addresses
3 | *
4 | * @param address Address to beautify
5 | *
6 | * @returns Formatted address
7 | */
8 | export function formatAddress(address: string, length = 26) {
9 | if (!address) return "";
10 |
11 | return (
12 | address.substring(0, length / 2) +
13 | "..." +
14 | address.substring(address.length - length / 2, address.length)
15 | );
16 | }
17 |
18 | /**
19 | * Format Arweave balance
20 | *
21 | * @param val Number to format
22 | * @param short Return an even shorter format
23 | *
24 | * @returns Formatted balance
25 | */
26 | export function formatArweave(val: number | string = 0, short = false) {
27 | if (Number(val) === 0 && !short) return "0".repeat(10);
28 | val = String(val);
29 | const full = val.split(".")[0];
30 | if (full.length >= 10) return full;
31 | if (short) {
32 | if (full.length >= 5) return full;
33 | else return val.slice(0, 5);
34 | }
35 | return val.slice(0, 10);
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/geofence.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 | import axios from "axios";
3 |
4 | const useGeofence = () => {
5 | const [state, setState] = useState(false);
6 |
7 | useEffect(() => {
8 | (async () => {
9 | const {
10 | data: { ip },
11 | } = await axios.get("https://api.my-ip.io/ip.json");
12 | const { data } = await axios.get(`https://ipwhois.app/json/${ip}`);
13 |
14 | if (data.country_code === "US") setState(true);
15 | })();
16 | }, []);
17 |
18 | return state;
19 | };
20 |
21 | export default useGeofence;
22 |
--------------------------------------------------------------------------------
/src/utils/graph.ts:
--------------------------------------------------------------------------------
1 | import { DisplayTheme } from "@verto/ui/dist/types";
2 | import dayjs from "dayjs";
3 | import isTomorrow from "dayjs/plugin/isTomorrow";
4 |
5 | dayjs.extend(isTomorrow);
6 |
7 | export const GraphDataConfig = {
8 | borderColor: "#000000",
9 | fill: false,
10 | };
11 |
12 | // graph data config with gradient background
13 | export const GraphDataConfigGradient = {
14 | ...GraphDataConfig,
15 | backgroundColor(context: any) {
16 | const gradient = context.chart.ctx.createLinearGradient(
17 | 0,
18 | 0,
19 | 0,
20 | context.chart.height
21 | );
22 |
23 | gradient.addColorStop(0, "rgba(0, 0, 0, .3)");
24 | gradient.addColorStop(1, "rgba(0, 0, 0, 0)");
25 |
26 | return gradient;
27 | },
28 | fill: true,
29 | };
30 |
31 | export function GraphOptions({
32 | ticks = false,
33 | tooltipText,
34 | tickText,
35 | theme,
36 | }: IGraphOptions) {
37 | const fontFamily = '"Poppins", sans-serif',
38 | fontStyle = {
39 | fontFamily,
40 | fontSize: 12,
41 | };
42 |
43 | return {
44 | responsive: true,
45 | maintainAspectRatio: true,
46 | elements: {
47 | point: { radius: 0 },
48 | line: {
49 | borderWidth: 3.5,
50 | borderCapStyle: "round",
51 | },
52 | },
53 | layout: {
54 | padding: {
55 | top: 3.5,
56 | right: 4,
57 | },
58 | },
59 | tooltips: {
60 | mode: "index",
61 | intersect: false,
62 | titleFontFamily: fontFamily,
63 | bodyFontColor: theme === "Light" ? "#d4d4d4" : "#666666",
64 | bodyFontFamily: fontFamily,
65 | titleFontColor: theme === "Light" ? "#ffffff" : "#000000",
66 | padding: 9,
67 | displayColors: false,
68 | backgroundColor: theme === "Light" ? "#000000" : "#ffffff",
69 | callbacks: {
70 | label: tooltipText ?? (({ value }: any) => value),
71 | },
72 | },
73 | hover: { mode: "nearest", intersect: true },
74 | legend: { display: false },
75 | scales: {
76 | xAxes: [
77 | {
78 | ticks: {
79 | display: ticks,
80 | ...fontStyle,
81 | },
82 | gridLines: { display: false },
83 | },
84 | ],
85 | yAxes: [
86 | {
87 | ticks: {
88 | display: ticks,
89 | ...fontStyle,
90 | callback: tickText ?? ((val) => val),
91 | },
92 | scaleLabel: { display: false },
93 | gridLines: { display: false },
94 | },
95 | ],
96 | },
97 | };
98 | }
99 |
100 | interface IGraphOptions {
101 | ticks?: boolean;
102 | tooltipText?: (tooltipItem?: any) => string;
103 | tickText?: (value: string, index: number) => string;
104 | theme: DisplayTheme;
105 | }
106 |
107 | export function filterGraphData(data: { [date: string]: number }) {
108 | let dates = Object.keys(data).reverse();
109 |
110 | dates = dates.filter((date) => !dayjs(new Date(date)).isTomorrow());
111 |
112 | return {
113 | dates,
114 | values: dates.map((date) => data[date]),
115 | };
116 | }
117 |
--------------------------------------------------------------------------------
/src/utils/infinite_scroll.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export default function useInfiniteScroll(
4 | loadMore: () => Promise,
5 | initialData?: T[]
6 | ) {
7 | const [loading, setLoading] = useState(false);
8 | const [data, setData] = useState(initialData ?? []);
9 | const [noMore, setNoMore] = useState(false);
10 |
11 | useEffect(() => {
12 | window.addEventListener("scroll", handleScroll);
13 |
14 | if (!initialData) setLoading(true);
15 |
16 | return () => {
17 | window.removeEventListener("scroll", handleScroll);
18 | };
19 | }, []);
20 |
21 | useEffect(() => {
22 | if (!loading || noMore) return;
23 | load();
24 | }, [loading]);
25 |
26 | async function handleScroll() {
27 | if (document.body.clientHeight - window.innerHeight < window.scrollY + 300)
28 | setLoading(true);
29 | }
30 |
31 | async function load() {
32 | setLoading(true);
33 |
34 | const lastScrollY = window.scrollY;
35 | const addData = await loadMore();
36 |
37 | if (addData.length === 0) {
38 | setLoading(false);
39 | return setNoMore(true);
40 | }
41 |
42 | setData((val) => [...val, ...addData]);
43 | setTimeout(() => setLoading(false), 350);
44 | window.scrollTo(window.scrollX, lastScrollY);
45 | }
46 |
47 | return { loading, data };
48 | }
49 |
--------------------------------------------------------------------------------
/src/utils/order.ts:
--------------------------------------------------------------------------------
1 | import { cancelCache } from "./storage_names";
2 |
3 | /**
4 | * Get the type of an order
5 | *
6 | * @param input Order input string
7 | *
8 | * @returns Buy / Sell
9 | */
10 | export const getType = (input: string): "buy" | "sell" => {
11 | if (input.split(" ")[input.split.length - 1] === "AR") return "buy";
12 | return "sell";
13 | };
14 |
15 | /**
16 | * Add an order to the cached cancelled orders list
17 | *
18 | * @param orderID Order cancelled
19 | */
20 | export const addToCancel = (orderID: string) => {
21 | localStorage.setItem(
22 | cancelCache,
23 | JSON.stringify([...getCancelledOrders(), orderID])
24 | );
25 | };
26 |
27 | /**
28 | * Get cancelled order ids
29 | *
30 | * @returns Array of order ids
31 | */
32 | export const getCancelledOrders = (): string[] => {
33 | const stored = localStorage.getItem(cancelCache);
34 | if (!stored) return [];
35 |
36 | return JSON.parse(stored);
37 | };
38 |
--------------------------------------------------------------------------------
/src/utils/storage_names.ts:
--------------------------------------------------------------------------------
1 | export const swapItems = "verto_swap_tokens";
2 | export const watchlistPeriod = "verto_watchlist_period";
3 | export const watchlist = "verto_watchlist";
4 | export const ignorePermissionWarning = "verto_ignore_permission_warning";
5 | export const theme = "verto_theme";
6 | export const cancelCache = "verto_orders_cancelled";
7 | export const lastViewedChangelog = "verto_last_viewed_changelog_version";
8 |
--------------------------------------------------------------------------------
/src/utils/user.ts:
--------------------------------------------------------------------------------
1 | import { UserInterface } from "@verto/js/dist/faces";
2 | import { generateAvatarGradient } from "@verto/ui";
3 |
4 | export function randomEmoji(size = 100) {
5 | const emojis = ["😂", "🥺", "😊", "🥰", "😃", "🤩", "🤔", "😏", "😷"];
6 | const emoji = emojis[Math.floor(Math.random() * emojis.length)];
7 |
8 | return `data:image/svg+xml,${emoji} `;
15 | }
16 |
17 | export interface Art {
18 | id: string;
19 | name: string;
20 | price?: number;
21 | owner: UserInterface;
22 | }
23 |
24 | export type TokenType = "community" | "art" | "collection" | "custom";
25 |
26 | export const fixUserImage = (user: UserInterface) => ({
27 | ...user,
28 | image: user?.image
29 | ? `https://arweave.net/${user.image}`
30 | : generateAvatarGradient(user.name || user.username || ""),
31 | });
32 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": false,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "env.d.ts"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "silent": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------